diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -1950,7 +1950,8 @@ } bool MayBeDesignationStart(); ExprResult ParseBraceInitializer(); - ExprResult ParseInitializerWithPotentialDesignator(); + ExprResult ParseInitializerWithPotentialDesignator( + llvm::function_ref CodeCompleteCB); //===--------------------------------------------------------------------===// // clang Expressions diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -11525,6 +11525,12 @@ IdentifierInfo *II, SourceLocation OpenParLoc); void CodeCompleteInitializer(Scope *S, Decl *D); + /// Trigger code completion for a record of \p BaseType. \p InitExprs are + /// expressions in the initializer list seen so far and \p D is the current + /// Designation being parsed. + void CodeCompleteDesignator(const QualType BaseType, + llvm::ArrayRef InitExprs, + const Designation &D); void CodeCompleteAfterIf(Scope *S); void CodeCompleteQualifiedId(Scope *S, CXXScopeSpec &SS, bool EnteringContext, diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -2460,6 +2460,7 @@ InitializerScopeRAII InitScope(*this, D, ThisDecl); + PreferredType.enterVariableInit(Tok.getLocation(), ThisDecl); ExprResult Init(ParseBraceInitializer()); InitScope.pop(); diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp --- a/clang/lib/Parse/ParseExprCXX.cpp +++ b/clang/lib/Parse/ParseExprCXX.cpp @@ -1857,6 +1857,7 @@ && "Expected '(' or '{'!"); if (Tok.is(tok::l_brace)) { + PreferredType.enterTypeCast(Tok.getLocation(), TypeRep.get()); ExprResult Init = ParseBraceInitializer(); if (Init.isInvalid()) return Init; diff --git a/clang/lib/Parse/ParseInit.cpp b/clang/lib/Parse/ParseInit.cpp --- a/clang/lib/Parse/ParseInit.cpp +++ b/clang/lib/Parse/ParseInit.cpp @@ -10,11 +10,14 @@ // //===----------------------------------------------------------------------===// +#include "clang/Basic/TokenKinds.h" #include "clang/Parse/ParseDiagnostic.h" #include "clang/Parse/Parser.h" #include "clang/Parse/RAIIObjectsForParser.h" #include "clang/Sema/Designator.h" +#include "clang/Sema/Ownership.h" #include "clang/Sema/Scope.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" using namespace clang; @@ -154,7 +157,9 @@ /// initializer (because it is an expression). We need to consider this case /// when parsing array designators. /// -ExprResult Parser::ParseInitializerWithPotentialDesignator() { +/// \p CodeCompleteCB is called with Designation parsed so far. +ExprResult Parser::ParseInitializerWithPotentialDesignator( + llvm::function_ref CodeCompleteCB) { // If this is the old-style GNU extension: // designation ::= identifier ':' @@ -193,6 +198,11 @@ // designator: '.' identifier SourceLocation DotLoc = ConsumeToken(); + if (Tok.is(tok::code_completion)) { + CodeCompleteCB(Desig); + cutOffParsing(); + return ExprError(); + } if (Tok.isNot(tok::identifier)) { Diag(Tok.getLocation(), diag::err_expected_field_designator); return ExprError(); @@ -407,7 +417,6 @@ return ExprError(); } - /// ParseBraceInitializer - Called when parsing an initializer that has a /// leading open brace. /// @@ -444,6 +453,10 @@ Actions, EnterExpressionEvaluationContext::InitList); bool InitExprsOk = true; + auto CodeCompleteDesignation = [&](const Designation &D) { + Actions.CodeCompleteDesignator(PreferredType.get(T.getOpenLocation()), + InitExprs, D); + }; while (1) { // Handle Microsoft __if_exists/if_not_exists if necessary. @@ -463,7 +476,7 @@ // initializer directly. ExprResult SubElt; if (MayBeDesignationStart()) - SubElt = ParseInitializerWithPotentialDesignator(); + SubElt = ParseInitializerWithPotentialDesignator(CodeCompleteDesignation); else SubElt = ParseInitializer(); @@ -543,13 +556,17 @@ return false; } + auto CodeCompleteDesignation = [&](const Designation &D) { + Actions.CodeCompleteDesignator(PreferredType.get(Braces.getOpenLocation()), + InitExprs, D); + }; while (!isEofOrEom()) { trailingComma = false; // If we know that this cannot be a designation, just parse the nested // initializer directly. ExprResult SubElt; if (MayBeDesignationStart()) - SubElt = ParseInitializerWithPotentialDesignator(); + SubElt = ParseInitializerWithPotentialDesignator(CodeCompleteDesignation); else SubElt = ParseInitializer(); diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -13,6 +13,8 @@ #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/QualTypeNames.h" @@ -23,11 +25,14 @@ #include "clang/Lex/MacroInfo.h" #include "clang/Lex/Preprocessor.h" #include "clang/Sema/CodeCompleteConsumer.h" +#include "clang/Sema/Designator.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/Overload.h" #include "clang/Sema/Scope.h" #include "clang/Sema/ScopeInfo.h" +#include "clang/Sema/Sema.h" #include "clang/Sema/SemaInternal.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/SmallBitVector.h" #include "llvm/ADT/SmallPtrSet.h" @@ -36,6 +41,7 @@ #include "llvm/ADT/StringSwitch.h" #include "llvm/ADT/Twine.h" #include "llvm/ADT/iterator_range.h" +#include "llvm/Support/Casting.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include @@ -4723,6 +4729,23 @@ } } +// Returns the RecordDecl inside the BaseType, falling back to primary template +// in case of specializations. Since we might not have a decl for the +// instantiation/specialization yet, e.g. dependent code. +static RecordDecl *getAsRecordDecl(const QualType BaseType) { + if (auto *RD = BaseType->getAsRecordDecl()) + return RD; + + if (const auto *TST = BaseType->getAs()) { + if (const auto *TD = dyn_cast_or_null( + TST->getTemplateName().getAsTemplateDecl())) { + return TD->getTemplatedDecl(); + } + } + + return nullptr; +} + void Sema::CodeCompleteMemberReferenceExpr(Scope *S, Expr *Base, Expr *OtherOpBase, SourceLocation OpLoc, bool IsArrow, @@ -4771,6 +4794,8 @@ Base = ConvertedBase.get(); QualType BaseType = Base->getType(); + if (BaseType.isNull()) + return false; ExprValueKind BaseKind = Base->getValueKind(); if (IsArrow) { @@ -4783,23 +4808,9 @@ return false; } - if (const RecordType *Record = BaseType->getAs()) { + if (RecordDecl *RD = getAsRecordDecl(BaseType)) { AddRecordMembersCompletionResults(*this, Results, S, BaseType, BaseKind, - Record->getDecl(), - std::move(AccessOpFixIt)); - } else if (const auto *TST = - BaseType->getAs()) { - TemplateName TN = TST->getTemplateName(); - if (const auto *TD = - dyn_cast_or_null(TN.getAsTemplateDecl())) { - CXXRecordDecl *RD = TD->getTemplatedDecl(); - AddRecordMembersCompletionResults(*this, Results, S, BaseType, BaseKind, - RD, std::move(AccessOpFixIt)); - } - } else if (const auto *ICNT = BaseType->getAs()) { - if (auto *RD = ICNT->getDecl()) - AddRecordMembersCompletionResults(*this, Results, S, BaseType, BaseKind, - RD, std::move(AccessOpFixIt)); + RD, std::move(AccessOpFixIt)); } else if (!IsArrow && BaseType->isObjCObjectPointerType()) { // Objective-C property reference. AddedPropertiesSet AddedProperties; @@ -5286,6 +5297,44 @@ return QualType(); } +void Sema::CodeCompleteDesignator(const QualType BaseType, + llvm::ArrayRef InitExprs, + const Designation &D) { + if (BaseType.isNull()) + return; + // FIXME: Handle nested designations, e.g. : .x.^ + if (!D.empty()) + return; + + const auto *RD = getAsRecordDecl(BaseType); + if (!RD) + return; + if (const auto *CTSD = llvm::dyn_cast(RD)) { + // Template might not be instantiated yet, fall back to primary template in + // such cases. + if (CTSD->getTemplateSpecializationKind() == TSK_Undeclared) + RD = CTSD->getSpecializedTemplate()->getTemplatedDecl(); + } + if (RD->fields().empty()) + return; + + CodeCompletionContext CCC(CodeCompletionContext::CCC_DotMemberAccess, + BaseType); + ResultBuilder Results(*this, CodeCompleter->getAllocator(), + CodeCompleter->getCodeCompletionTUInfo(), CCC); + + Results.EnterNewScope(); + for (const auto *FD : RD->fields()) { + // FIXME: Make use of previous designators to mark any fields before those + // inaccessible, and also compute the next initializer priority. + ResultBuilder::Result Result(FD, Results.getBasePriority(FD)); + Results.AddResult(Result, CurContext, /*Hiding=*/nullptr); + } + Results.ExitScope(); + HandleCodeCompleteResults(this, CodeCompleter, Results.getCompletionContext(), + Results.data(), Results.size()); +} + void Sema::CodeCompleteInitializer(Scope *S, Decl *D) { ValueDecl *VD = dyn_cast_or_null(D); if (!VD) { diff --git a/clang/test/CodeCompletion/desig-init.cpp b/clang/test/CodeCompletion/desig-init.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeCompletion/desig-init.cpp @@ -0,0 +1,54 @@ +struct Base { + int t; +}; +struct Foo : public Base { + int x; + Base b; + void foo(); +}; + +void foo() { + Foo F{.x = 2, .b.t = 0}; + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:11:10 %s -o - -std=c++2a | FileCheck -check-prefix=CHECK-CC1 %s + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:11:18 %s -o - -std=c++2a | FileCheck -check-prefix=CHECK-CC1 %s + // CHECK-CC1: COMPLETION: b : [#Base#]b + // CHECK-CC1-NEXT: COMPLETION: x : [#int#]x + // CHECK-CC1-NOT: foo + // CHECK-CC1-NOT: t + + // FIXME: Handle nested designators + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:11:20 %s -o - | count 0 + + Base B = {.t = 2}; + auto z = [](Base B) {}; + z({.t = 1}); + z(Base{.t = 2}); + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:22:14 %s -o - -std=c++2a | FileCheck -check-prefix=CHECK-CC2 %s + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:24:7 %s -o - -std=c++2a | FileCheck -check-prefix=CHECK-CC2 %s + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:25:11 %s -o - -std=c++2a | FileCheck -check-prefix=CHECK-CC2 %s + // CHECK-CC2: COMPLETION: t : [#int#]t +} + +// Handle templates +template +struct Test { T x; }; +template <> +struct Test { + int x; + char y; +}; +void bar() { + Test T{.x = 2}; + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:41:17 %s -o - -std=c++2a | FileCheck -check-prefix=CHECK-CC3 %s + // CHECK-CC3: COMPLETION: x : [#T#]x + Test X{.x = 2}; + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:44:16 %s -o - -std=c++2a | FileCheck -check-prefix=CHECK-CC4 %s + // CHECK-CC4: COMPLETION: x : [#int#]x + // CHECK-CC4-NEXT: COMPLETION: y : [#char#]y +} + +template +void aux() { + Test X{.x = T(2)}; + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:52:14 %s -o - -std=c++2a | FileCheck -check-prefix=CHECK-CC3 %s +}