Skip to content

[5.1][CodeCompletion] Grab bag of recent changes in code completion #23577

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
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
1 change: 1 addition & 0 deletions include/swift/IDE/CodeCompletion.h
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ enum class CompletionKind {
TypeSimpleBeginning,
TypeIdentifierWithDot,
TypeIdentifierWithoutDot,
CaseStmtKeyword,
CaseStmtBeginning,
CaseStmtDotPrefix,
NominalMemberBeginning,
Expand Down
3 changes: 3 additions & 0 deletions include/swift/Parse/CodeCompletionCallbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ class CodeCompletionCallbacks {
/// Complete a given type-identifier when there is no trailing dot.
virtual void completeTypeIdentifierWithoutDot(IdentTypeRepr *ITR) {};

/// Complete the beginning of a case statement at the top of switch stmt.
virtual void completeCaseStmtKeyword() {};

/// Complete at the beginning of a case stmt pattern.
virtual void completeCaseStmtBeginning() {};

Expand Down
134 changes: 96 additions & 38 deletions lib/IDE/CodeCompletion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1185,8 +1185,6 @@ void CodeCompletionString::getName(raw_ostream &OS) const {
}
}
}
assert((TextSize > 0) &&
"code completion string should have non-empty name!");
}

void CodeCompletionContext::sortCompletionResults(
Expand Down Expand Up @@ -1348,6 +1346,7 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks {
void completeTypeIdentifierWithDot(IdentTypeRepr *ITR) override;
void completeTypeIdentifierWithoutDot(IdentTypeRepr *ITR) override;

void completeCaseStmtKeyword() override;
void completeCaseStmtBeginning() override;
void completeCaseStmtDotPrefix() override;
void completeDeclAttrKeyword(Decl *D, bool Sil, bool Param) override;
Expand Down Expand Up @@ -1823,6 +1822,32 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
llvm_unreachable("unhandled kind");
}

void addValueBaseName(CodeCompletionResultBuilder &Builder,
DeclBaseName Name) {
auto NameStr = Name.userFacingName();
bool shouldEscapeKeywords;
if (Name.isSpecial()) {
// Special names (i.e. 'init') are always displayed as its user facing
// name.
shouldEscapeKeywords = false;
} else if (ExprType) {
// After dot. User can write any keyword after '.' except for `init` and
// `self`. E.g. 'func `init`()' must be called by 'expr.`init`()'.
shouldEscapeKeywords = NameStr == "self" || NameStr == "init";
} else {
// As primary expresson. We have to escape almost every keywords except
// for 'self' and 'Self'.
shouldEscapeKeywords = NameStr != "self" && NameStr != "Self";
}

if (!shouldEscapeKeywords) {
Builder.addTextChunk(NameStr);
} else {
SmallString<16> buffer;
Builder.addTextChunk(Builder.escapeKeyword(NameStr, true, buffer));
}
}

void addLeadingDot(CodeCompletionResultBuilder &Builder) {
if (NeedOptionalUnwrap) {
Builder.setNumBytesToErase(NumBytesToEraseForOptionalUnwrap);
Expand Down Expand Up @@ -1995,7 +2020,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
VD->shouldHideFromEditor())
return;

StringRef Name = VD->getName().get();
Identifier Name = VD->getName();
assert(!Name.empty() && "name should not be empty");

CommandWordsPairs Pairs;
Expand All @@ -2005,15 +2030,15 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
getSemanticContext(VD, Reason), ExpectedTypes);
Builder.setAssociatedDecl(VD);
addLeadingDot(Builder);
Builder.addTextChunk(Name);
addValueBaseName(Builder, Name);
setClangDeclKeywords(VD, Pairs, Builder);

if (!VD->hasValidSignature())
return;

// Add a type annotation.
Type VarType = getTypeOfMember(VD);
if (VD->getName() != Ctx.Id_self && VD->isInOut()) {
if (Name != Ctx.Id_self && VD->isInOut()) {
// It is useful to show inout for function parameters.
// But for 'self' it is just noise.
VarType = InOutType::get(VarType);
Expand Down Expand Up @@ -2222,14 +2247,8 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
else
Builder.addAnnotatedLeftParen();

bool anyParam = addCallArgumentPatterns(Builder, AFT->getParams(),
declParams, includeDefaultArgs);

if (HaveLParen && !anyParam) {
// Empty result, don't add it.
Builder.cancel();
return;
}
addCallArgumentPatterns(Builder, AFT->getParams(), declParams,
includeDefaultArgs);

// The rparen matches the lparen here so that we insert both or neither.
if (!HaveLParen)
Expand Down Expand Up @@ -2319,7 +2338,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
return;
foundFunction(FD);

StringRef Name = FD->getName().get();
Identifier Name = FD->getName();
assert(!Name.empty() && "name should not be empty");

Type FunctionType = getTypeOfMember(FD);
Expand Down Expand Up @@ -2350,7 +2369,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
setClangDeclKeywords(FD, Pairs, Builder);
Builder.setAssociatedDecl(FD);
addLeadingDot(Builder);
Builder.addTextChunk(Name);
addValueBaseName(Builder, Name);
if (IsDynamicLookup)
Builder.addDynamicLookupMethodCallTail();
else if (FD->getAttrs().hasAttribute<OptionalAttr>())
Expand Down Expand Up @@ -2475,14 +2494,8 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
else
Builder.addAnnotatedLeftParen();

bool anyParam = addCallArgumentPatterns(
Builder, ConstructorType, CD->getParameters(), includeDefaultArgs);

if (HaveLParen && !anyParam) {
// Empty result, don't add it.
Builder.cancel();
return;
}
addCallArgumentPatterns(Builder, ConstructorType, CD->getParameters(),
includeDefaultArgs);

// The rparen matches the lparen here so that we insert both or neither.
if (!HaveLParen)
Expand Down Expand Up @@ -2680,8 +2693,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
Builder.setAssociatedDecl(EED);
setClangDeclKeywords(EED, Pairs, Builder);
addLeadingDot(Builder);

Builder.addTextChunk(EED->getName().str());
addValueBaseName(Builder, EED->getName());

// Enum element is of function type; (Self.type) -> Self or
// (Self.Type) -> (Args...) -> Self.
Expand All @@ -2705,7 +2717,8 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
void addKeyword(StringRef Name, Type TypeAnnotation = Type(),
SemanticContextKind SK = SemanticContextKind::None,
CodeCompletionKeywordKind KeyKind
= CodeCompletionKeywordKind::None) {
= CodeCompletionKeywordKind::None,
unsigned NumBytesToErase = 0) {
CodeCompletionResultBuilder Builder(
Sink,
CodeCompletionResult::ResultKind::Keyword, SK, ExpectedTypes);
Expand All @@ -2714,6 +2727,8 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
Builder.setKeywordKind(KeyKind);
if (TypeAnnotation)
addTypeAnnotation(Builder, TypeAnnotation);
if (NumBytesToErase > 0)
Builder.setNumBytesToErase(NumBytesToErase);
}

void addKeyword(StringRef Name, StringRef TypeAnnotation,
Expand Down Expand Up @@ -2759,7 +2774,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {

// Base name
addLeadingDot(Builder);
Builder.addTextChunk(AFD->getBaseName().userFacingName());
addValueBaseName(Builder, AFD->getBaseName());

// Add the argument labels.
auto ArgLabels = AFD->getFullName().getArgumentNames();
Expand Down Expand Up @@ -3600,6 +3615,15 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
return false;
}

if (T->getOptionalObjectType() &&
VD->getModuleContext()->isStdlibModule()) {
// In optional context, ignore '.init(<some>)', 'init(nilLiteral:)',
if (isa<ConstructorDecl>(VD))
return false;
// TODO: Ignore '.some(<Wrapped>)' and '.none' too *in expression
// context*. They are useful in pattern context though.
}

// Enum element decls can always be referenced by implicit member
// expression.
if (isa<EnumElementDecl>(VD))
Expand Down Expand Up @@ -3670,6 +3694,14 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
// If this is optional type, perform completion for the object type.
// i.e. 'let _: Enum??? = .enumMember' is legal.
getUnresolvedMemberCompletions(objT->lookThroughAllOptionalTypes());

// Add 'nil' keyword with erasing '.' instruction.
unsigned bytesToErase = 0;
auto &SM = CurrDeclContext->getASTContext().SourceMgr;
if (DotLoc.isValid())
bytesToErase = SM.getByteDistance(DotLoc, SM.getCodeCompletionLoc());
addKeyword("nil", T, SemanticContextKind::ExpressionSpecific,
CodeCompletionKeywordKind::kw_nil, bytesToErase);
}
getUnresolvedMemberCompletions(T);
}
Expand Down Expand Up @@ -4417,6 +4449,11 @@ void CodeCompletionCallbacksImpl::completeTypeIdentifierWithoutDot(
CurDeclContext = P.CurDeclContext;
}

void CodeCompletionCallbacksImpl::completeCaseStmtKeyword() {
Kind = CompletionKind::CaseStmtKeyword;
CurDeclContext = P.CurDeclContext;
}

void CodeCompletionCallbacksImpl::completeCaseStmtBeginning() {
assert(!InEnumElementRawValue);

Expand Down Expand Up @@ -4598,6 +4635,11 @@ static void addStmtKeywords(CodeCompletionResultSink &Sink, bool MaybeFuncBody)
#include "swift/Syntax/TokenKinds.def"
}

static void addCaseStmtKeywords(CodeCompletionResultSink &Sink) {
addKeyword(Sink, "case", CodeCompletionKeywordKind::kw_case);
addKeyword(Sink, "default", CodeCompletionKeywordKind::kw_default);
}

static void addLetVarKeywords(CodeCompletionResultSink &Sink) {
addKeyword(Sink, "let", CodeCompletionKeywordKind::kw_let);
addKeyword(Sink, "var", CodeCompletionKeywordKind::kw_var);
Expand Down Expand Up @@ -4687,6 +4729,10 @@ void CodeCompletionCallbacksImpl::addKeywords(CodeCompletionResultSink &Sink,
addAnyTypeKeyword(Sink);
break;

case CompletionKind::CaseStmtKeyword:
addCaseStmtKeywords(Sink);
break;

case CompletionKind::PostfixExpr:
case CompletionKind::PostfixExprParen:
case CompletionKind::SuperExpr:
Expand Down Expand Up @@ -4954,19 +5000,30 @@ void CodeCompletionCallbacksImpl::doneParsing() {
bool OnRoot = !KPE->getComponents().front().isValid();
Lookup.setIsSwiftKeyPathExpr(OnRoot);

auto ParsedType = BGT->getGenericArgs()[1];
auto Components = KPE->getComponents();
if (Components.back().getKind() ==
KeyPathExpr::Component::Kind::OptionalWrap) {
Type baseType = BGT->getGenericArgs()[OnRoot ? 0 : 1];
if (OnRoot && baseType->is<UnresolvedType>()) {
// Infer the root type of the keypath from the context type.
ExprContextInfo ContextInfo(CurDeclContext, ParsedExpr);
for (auto T : ContextInfo.getPossibleTypes()) {
if (auto unwrapped = T->getOptionalObjectType())
T = unwrapped;
if (!T->getAnyNominal() || !T->getAnyNominal()->getKeyPathTypeKind() ||
T->hasUnresolvedType() || !T->is<BoundGenericType>())
continue;
// Use the first KeyPath context type found.
baseType = T->castTo<BoundGenericType>()->getGenericArgs()[0];
break;
}
}
if (!OnRoot && KPE->getComponents().back().getKind() ==
KeyPathExpr::Component::Kind::OptionalWrap) {
// KeyPath expr with '?' (e.g. '\Ty.[0].prop?.another').
// Althogh expected type is optional, we should unwrap it because it's
// unwrapped.
ParsedType = ParsedType->getOptionalObjectType();
baseType = baseType->getOptionalObjectType();
}

// The second generic type argument of KeyPath<Root, Value> should be
// the value we pull code completion results from.
Lookup.getValueExprCompletions(ParsedType);
Lookup.getValueExprCompletions(baseType);
break;
}

Expand Down Expand Up @@ -5189,12 +5246,13 @@ void CodeCompletionCallbacksImpl::doneParsing() {
}
}
break;
case CompletionKind::AfterIfStmtElse:
// Handled earlier by keyword completions.
break;
case CompletionKind::PrecedenceGroup:
Lookup.getPrecedenceGroupCompletions(SyntxKind);
break;
case CompletionKind::AfterIfStmtElse:
case CompletionKind::CaseStmtKeyword:
// Handled earlier by keyword completions.
break;
}

for (auto &Request: Lookup.RequestedCachedResults) {
Expand Down
9 changes: 4 additions & 5 deletions lib/IDE/CodeCompletionResultBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,8 @@ class CodeCompletionResultBuilder {
addTypeAnnotation(Annotation);
}

StringRef escapeArgumentLabel(StringRef Word,
bool escapeAllKeywords,
llvm::SmallString<16> &EscapedKeyword) {
StringRef escapeKeyword(StringRef Word, bool escapeAllKeywords,
llvm::SmallString<16> &EscapedKeyword) {
bool shouldEscape = false;
if (escapeAllKeywords) {
#define KEYWORD(kw) .Case(#kw, true)
Expand Down Expand Up @@ -325,15 +324,15 @@ class CodeCompletionResultBuilder {
llvm::SmallString<16> EscapedKeyword;
addChunkWithText(
CodeCompletionString::Chunk::ChunkKind::CallParameterName,
escapeArgumentLabel(Name.str(), false, EscapedKeyword));
escapeKeyword(Name.str(), false, EscapedKeyword));
addChunkWithTextNoCopy(
CodeCompletionString::Chunk::ChunkKind::CallParameterColon, ": ");
} else if (!LocalName.empty()) {
// Use local (non-API) parameter name if we have nothing else.
llvm::SmallString<16> EscapedKeyword;
addChunkWithText(
CodeCompletionString::Chunk::ChunkKind::CallParameterInternalName,
escapeArgumentLabel(LocalName.str(), false, EscapedKeyword));
escapeKeyword(LocalName.str(), false, EscapedKeyword));
addChunkWithTextNoCopy(
CodeCompletionString::Chunk::ChunkKind::CallParameterColon, ": ");
}
Expand Down
4 changes: 2 additions & 2 deletions lib/IDE/ExprContextAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ class ExprFinder : public ASTWalker {
Expr *get() const { return FoundExpr; }

std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
if (TargetRange == E->getSourceRange() && !isa<ImplicitConversionExpr>(E) &&
!isa<AutoClosureExpr>(E) && !isa<ConstructorRefCallExpr>(E)) {
if (TargetRange == E->getSourceRange() && !E->isImplicit() &&
!isa<ConstructorRefCallExpr>(E)) {
assert(!FoundExpr && "non-nullptr for found expr");
FoundExpr = E;
return {false, nullptr};
Expand Down
5 changes: 5 additions & 0 deletions lib/Parse/ParseStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2273,6 +2273,11 @@ Parser::parseStmtCases(SmallVectorImpl<ASTNode> &cases, bool IsActive) {
if (auto PDD = PoundDiagnosticResult.getPtrOrNull()) {
cases.emplace_back(PDD);
}
} else if (Tok.is(tok::code_complete)) {
if (CodeCompletion)
CodeCompletion->completeCaseStmtKeyword();
consumeToken(tok::code_complete);
return makeParserCodeCompletionStatus();
} else {
// If there are non-case-label statements at the start of the switch body,
// raise an error and recover by discarding them.
Expand Down
11 changes: 10 additions & 1 deletion lib/Sema/LookupVisibleDecls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,12 +388,18 @@ static void lookupDeclsFromProtocolsBeingConformedTo(
NominalTypeDecl *CurrNominal = BaseTy->getAnyNominal();
if (!CurrNominal)
return;
ModuleDecl *Module = FromContext->getParentModule();

for (auto Conformance : CurrNominal->getAllConformances()) {
auto Proto = Conformance->getProtocol();
if (!Proto->isAccessibleFrom(FromContext))
continue;

// Skip unsatisfied conditional conformances.
if (Conformance->getConditionalRequirementsIfAvailable() &&
!Module->conformsToProtocol(BaseTy, Proto))
continue;

DeclVisibilityKind ReasonForThisProtocol;
if (Reason == DeclVisibilityKind::MemberOfCurrentNominal)
ReasonForThisProtocol =
Expand Down Expand Up @@ -794,7 +800,10 @@ class OverrideFilteringConsumer : public VisibleDeclConsumer {
OtherSignature, OtherSignatureType,
/*wouldConflictInSwift5*/nullptr,
/*skipProtocolExtensionCheck*/true)) {
if (VD->getFormalAccess() > OtherVD->getFormalAccess()) {
if (VD->getFormalAccess() > OtherVD->getFormalAccess() ||
//Prefer available one.
(!AvailableAttr::isUnavailable(VD) &&
AvailableAttr::isUnavailable(OtherVD))) {
PossiblyConflicting.erase(I);
PossiblyConflicting.insert(VD);

Expand Down
Loading