https://github.com/schuay updated https://github.com/llvm/llvm-project/pull/195126
>From a804b4d51fc4720e54d894dab4fc99938cdf2a0b Mon Sep 17 00:00:00 2001 From: Jakob Linke <[email protected]> Date: Thu, 30 Apr 2026 18:46:52 +0200 Subject: [PATCH 1/2] [clang] Complete fields in __builtin_offsetof designators Code completion was a no-op inside __builtin_offsetof: a cursor at __builtin_offsetof(T, ^) or __builtin_offsetof(T, a.^) fell through to ordinary-name completion instead of suggesting fields. Route the code_completion token to a new SemaCodeCompletion entry point that walks the designator path so far, resolves the subobject's type, and enumerates its members. Methods are filtered out, inherited fields are included, indirect fields from anonymous unions and structs are peeled, and `using Base::field` resolves through its UsingShadowDecl. A code_completion token past a complete component (right after `]` or at the end of the chain) is dropped rather than offering fields the user can't paste without first typing `.`. The offsetof and designated-initializer type walkers are folded into one helper parameterized by a field-lookup callback, which incidentally fixes reference-field and indirect-field traversal in designated-initializer completion too. Tests: lit cases in offsetof.cpp covering empty/dot/array/inheritance/ reference/anonymous/using-shadow/macro forms, extended desig-init.cpp walker cases, and a clangd unit test exercising the IDE path. --- .../clangd/unittests/CodeCompleteTests.cpp | 39 ++++++ clang/include/clang/Sema/SemaCodeCompletion.h | 3 + clang/lib/Parse/ParseExpr.cpp | 40 +++++- clang/lib/Sema/SemaCodeComplete.cpp | 127 +++++++++++++++--- clang/test/CodeCompletion/desig-init.cpp | 34 +++++ clang/test/CodeCompletion/offsetof.cpp | 95 +++++++++++++ 6 files changed, 317 insertions(+), 21 deletions(-) create mode 100644 clang/test/CodeCompletion/offsetof.cpp diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 386ffb54924a7..109156fa4d176 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -2539,6 +2539,45 @@ TEST(CompletionTest, CodeCompletionContext) { EXPECT_THAT(Results.Context, CodeCompletionContext::CCC_DotMemberAccess); } +TEST(CompletionTest, OffsetOfDesignator) { + auto Results = completions(R"cpp( + struct S { int field; int other; void fieldFn(); }; + int x = __builtin_offsetof(S, fiel^d); + )cpp"); + EXPECT_THAT( + Results.Completions, + ElementsAre(AllOf(named("field"), kind(CompletionItemKind::Field)))); + EXPECT_THAT(Results.Context, CodeCompletionContext::CCC_DotMemberAccess); + + Results = completions(R"cpp( + struct Inner { int field; void fieldFn(); }; + struct Outer { Inner inner; }; + int x = __builtin_offsetof(Outer, inner.fiel^d); + )cpp"); + EXPECT_THAT( + Results.Completions, + ElementsAre(AllOf(named("field"), kind(CompletionItemKind::Field)))); + + Results = completions(R"cpp( + struct Inner { int field; void fieldFn(); }; + struct Outer { Inner inner[2]; }; + int i; + int x = __builtin_offsetof(Outer, inner[i].fiel^d); + )cpp"); + EXPECT_THAT( + Results.Completions, + ElementsAre(AllOf(named("field"), kind(CompletionItemKind::Field)))); + + Results = completions(R"cpp( + struct Base { int field; void fieldFn(); }; + struct Derived : Base {}; + int x = __builtin_offsetof(Derived, fiel^d); + )cpp"); + EXPECT_THAT( + Results.Completions, + ElementsAre(AllOf(named("field"), kind(CompletionItemKind::Field)))); +} + TEST(CompletionTest, FixItForArrowToDot) { MockFS FS; MockCompilationDatabase CDB; diff --git a/clang/include/clang/Sema/SemaCodeCompletion.h b/clang/include/clang/Sema/SemaCodeCompletion.h index abdfb51900318..7203b19d58898 100644 --- a/clang/include/clang/Sema/SemaCodeCompletion.h +++ b/clang/include/clang/Sema/SemaCodeCompletion.h @@ -154,6 +154,9 @@ class SemaCodeCompletion : public SemaBase { void CodeCompleteDesignator(const QualType BaseType, llvm::ArrayRef<Expr *> InitExprs, const Designation &D); + /// Trigger code completion for a position inside a __builtin_offsetof + /// member designator (after the type's `,`, or after a `.`). + void CodeCompleteOffsetOfDesignator(QualType BaseType, const Designation &D); void CodeCompleteKeywordAfterIf(bool AfterExclaim) const; void CodeCompleteAfterIf(Scope *S, bool IsBracedThen); diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index c3ac8d7e6eb74..34e0d7905cd23 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -2407,7 +2407,18 @@ ExprResult Parser::ParseBuiltinPrimaryExpression() { return ExprError(); } + auto TriggerCompletion = [&](const Designation &D) { + cutOffParsing(); + Actions.CodeCompletion().CodeCompleteOffsetOfDesignator( + Actions.GetTypeFromParser(Ty.get()), D); + }; + // We must have at least one identifier here. + Designation D; + if (Tok.is(tok::code_completion)) { + TriggerCompletion(D); + return ExprError(); + } if (Tok.isNot(tok::identifier)) { Diag(Tok, diag::err_expected) << tok::identifier; SkipUntil(tok::r_paren, StopAtSemi); @@ -2415,12 +2426,18 @@ ExprResult Parser::ParseBuiltinPrimaryExpression() { } // Keep track of the various subcomponents we see. + // FIXME: Comps and D below carry the same designator chain in two + // different shapes. ActOnBuiltinOffsetOf should be taught to accept a + // Designation directly so this duplication can go away. SmallVector<Sema::OffsetOfComponent, 4> Comps; Comps.push_back(Sema::OffsetOfComponent()); Comps.back().isBrackets = false; Comps.back().U.IdentInfo = Tok.getIdentifierInfo(); - Comps.back().LocStart = Comps.back().LocEnd = ConsumeToken(); + Comps.back().LocStart = Comps.back().LocEnd = Tok.getLocation(); + D.AddDesignator(Designator::CreateFieldDesignator( + Tok.getIdentifierInfo(), SourceLocation(), Tok.getLocation())); + ConsumeToken(); // FIXME: This loop leaks the index expressions on error. while (true) { @@ -2430,13 +2447,20 @@ ExprResult Parser::ParseBuiltinPrimaryExpression() { Comps.back().isBrackets = false; Comps.back().LocStart = ConsumeToken(); + if (Tok.is(tok::code_completion)) { + TriggerCompletion(D); + return ExprError(); + } if (Tok.isNot(tok::identifier)) { Diag(Tok, diag::err_expected) << tok::identifier; SkipUntil(tok::r_paren, StopAtSemi); return ExprError(); } Comps.back().U.IdentInfo = Tok.getIdentifierInfo(); - Comps.back().LocEnd = ConsumeToken(); + Comps.back().LocEnd = Tok.getLocation(); + D.AddDesignator(Designator::CreateFieldDesignator( + Tok.getIdentifierInfo(), Comps.back().LocStart, Tok.getLocation())); + ConsumeToken(); } else if (Tok.is(tok::l_square)) { if (CheckProhibitedCXX11Attribute()) return ExprError(); @@ -2456,7 +2480,19 @@ ExprResult Parser::ParseBuiltinPrimaryExpression() { ST.consumeClose(); Comps.back().LocEnd = ST.getCloseLocation(); + Designator ArrayD = + Designator::CreateArrayDesignator(Res.get(), Comps.back().LocStart); + ArrayD.setRBracketLoc(Comps.back().LocEnd); + D.AddDesignator(ArrayD); } else { + // A code-completion token here (e.g. cursor right after `]`) is past + // the point where a field can be applied without a leading `.`. Drop + // it on the floor rather than leak into outer-scope completion or + // emit field suggestions that wouldn't compose. + if (Tok.is(tok::code_completion)) { + cutOffParsing(); + return ExprError(); + } if (Tok.isNot(tok::r_paren)) { PT.consumeClose(); Res = ExprError(); diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp index 0cd9819dc2964..b8d2a80001613 100644 --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -407,6 +407,7 @@ class ResultBuilder { bool IsNamespaceOrAlias(const NamedDecl *ND) const; bool IsType(const NamedDecl *ND) const; bool IsMember(const NamedDecl *ND) const; + bool IsField(const NamedDecl *ND) const; bool IsObjCIvar(const NamedDecl *ND) const; bool IsObjCMessageReceiver(const NamedDecl *ND) const; bool IsObjCMessageReceiverOrLambdaCapture(const NamedDecl *ND) const; @@ -445,8 +446,12 @@ void PreferredTypeBuilder::enterVariableInit(SourceLocation Tok, Decl *D) { ExpectedLoc = Tok; } -static QualType getDesignatedType(QualType BaseType, const Designation &Desig, - HeuristicResolver &Resolver); +static const FieldDecl *lookupDirectField(RecordDecl *RD, const Designator &D); +static QualType getDesignatedType( + ASTContext &Context, QualType BaseType, const Designation &Desig, + HeuristicResolver &Resolver, + llvm::function_ref<const FieldDecl *(RecordDecl *, const Designator &)> + LookupField); void PreferredTypeBuilder::enterDesignatedInitializer(SourceLocation Tok, QualType BaseType, @@ -455,7 +460,7 @@ void PreferredTypeBuilder::enterDesignatedInitializer(SourceLocation Tok, return; ComputeType = nullptr; HeuristicResolver Resolver(*Ctx); - Type = getDesignatedType(BaseType, D, Resolver); + Type = getDesignatedType(*Ctx, BaseType, D, Resolver, lookupDirectField); ExpectedLoc = Tok; } @@ -1670,6 +1675,11 @@ bool ResultBuilder::IsMember(const NamedDecl *ND) const { isa<ObjCPropertyDecl>(ND); } +bool ResultBuilder::IsField(const NamedDecl *ND) const { + ND = ND->getUnderlyingDecl(); + return isa<FieldDecl>(ND) || isa<IndirectFieldDecl>(ND); +} + static bool isObjCReceiverType(ASTContext &C, QualType T) { T = C.getCanonicalType(T); switch (T->getTypeClass()) { @@ -6731,35 +6741,63 @@ QualType SemaCodeCompletion::ProduceTemplateArgumentSignatureHelp( /*Braced=*/false); } -static QualType getDesignatedType(QualType BaseType, const Designation &Desig, - HeuristicResolver &Resolver) { +// Direct member lookup, used by designated initializers: only fields declared +// in `RD` itself (including indirect fields from anonymous members) are valid. +static const FieldDecl *lookupDirectField(RecordDecl *RD, const Designator &D) { + for (const auto *Member : RD->lookup(D.getFieldDecl())) { + if (const auto *FD = llvm::dyn_cast<FieldDecl>(Member)) + return FD; + if (const auto *IFD = llvm::dyn_cast<IndirectFieldDecl>(Member)) + return IFD->getAnonField(); + } + return nullptr; +} + +static QualType getDesignatedType( + ASTContext &Context, QualType BaseType, const Designation &Desig, + HeuristicResolver &Resolver, + llvm::function_ref<const FieldDecl *(RecordDecl *, const Designator &)> + LookupField) { for (unsigned I = 0; I < Desig.getNumDesignators(); ++I) { if (BaseType.isNull()) break; - QualType NextType; + const auto &D = Desig.getDesignator(I); if (D.isArrayDesignator() || D.isArrayRangeDesignator()) { - if (BaseType->isArrayType()) - NextType = BaseType->getAsArrayTypeUnsafe()->getElementType(); - } else { - assert(D.isFieldDesignator()); - auto *RD = getAsRecordDecl(BaseType, Resolver); - if (RD && RD->isCompleteDefinition()) { - for (const auto *Member : RD->lookup(D.getFieldDecl())) - if (const FieldDecl *FD = llvm::dyn_cast<FieldDecl>(Member)) { - NextType = FD->getType(); - break; - } + if (BaseType->isDependentType()) { + BaseType = Context.DependentTy; + continue; } + const ArrayType *AT = Context.getAsArrayType(BaseType); + if (!AT) + return QualType(); + BaseType = AT->getElementType(); + continue; + } + + assert(D.isFieldDesignator()); + if (BaseType->isDependentType()) { + BaseType = Context.DependentTy; + continue; } - BaseType = NextType; + + RecordDecl *RD = getAsRecordDecl(BaseType, Resolver); + if (!RD || !RD->isCompleteDefinition()) + return QualType(); + + const FieldDecl *MemberDecl = LookupField(RD, D); + if (!MemberDecl) + return QualType(); + + BaseType = MemberDecl->getType().getNonReferenceType(); } return BaseType; } void SemaCodeCompletion::CodeCompleteDesignator( QualType BaseType, llvm::ArrayRef<Expr *> InitExprs, const Designation &D) { - BaseType = getDesignatedType(BaseType, D, Resolver); + BaseType = getDesignatedType(SemaRef.Context, BaseType, D, Resolver, + lookupDirectField); if (BaseType.isNull()) return; const auto *RD = getAsRecordDecl(BaseType, Resolver); @@ -6792,6 +6830,57 @@ void SemaCodeCompletion::CodeCompleteDesignator( Results.size()); } +void SemaCodeCompletion::CodeCompleteOffsetOfDesignator(QualType BaseType, + const Designation &D) { + // offsetof allows inherited fields and follows normal qualified name lookup, + // not the direct-member iteration used by designated initializers. + auto LookupQualified = [&](RecordDecl *RD, + const Designator &Des) -> const FieldDecl * { + LookupResult R(SemaRef, Des.getFieldDecl(), Des.getFieldLoc(), + Sema::LookupMemberName); + SemaRef.LookupQualifiedName(R, RD); + // Peel via getUnderlyingDecl so a field exposed by `using Base::f;` + // resolves through its UsingShadowDecl. + for (NamedDecl *ND : R) { + ND = ND->getUnderlyingDecl(); + if (auto *FD = dyn_cast<FieldDecl>(ND)) + return FD; + if (auto *IFD = dyn_cast<IndirectFieldDecl>(ND)) + return IFD->getAnonField(); + } + return nullptr; + }; + BaseType = getDesignatedType(SemaRef.Context, BaseType, D, Resolver, + LookupQualified); + if (BaseType.isNull()) + return; + + RecordDecl *RD = getAsRecordDecl(BaseType, Resolver); + if (!RD) + return; + + CodeCompletionContext CCC(CodeCompletionContext::CCC_DotMemberAccess, + BaseType); + ResultBuilder Results(SemaRef, CodeCompleter->getAllocator(), + CodeCompleter->getCodeCompletionTUInfo(), CCC, + &ResultBuilder::IsField); + + Results.EnterNewScope(); + CodeCompletionDeclConsumer Consumer(Results, RD, BaseType); + // LookupVisibleDecls traverses base classes (required for inherited fields) + // and dependent bases (best-effort for templates). Globals are skipped: + // offsetof designators name only members of the surrounding type. + SemaRef.LookupVisibleDecls(RD, Sema::LookupMemberName, Consumer, + /*IncludeGlobalScope=*/false, + /*IncludeDependentBases=*/true, + CodeCompleter->loadExternal()); + Results.ExitScope(); + + HandleCodeCompleteResults(&SemaRef, CodeCompleter, + Results.getCompletionContext(), Results.data(), + Results.size()); +} + void SemaCodeCompletion::CodeCompleteInitializer(Scope *S, Decl *D) { ValueDecl *VD = dyn_cast_or_null<ValueDecl>(D); if (!VD) { diff --git a/clang/test/CodeCompletion/desig-init.cpp b/clang/test/CodeCompletion/desig-init.cpp index ac250bc6d8bb3..d43f7123dfcd1 100644 --- a/clang/test/CodeCompletion/desig-init.cpp +++ b/clang/test/CodeCompletion/desig-init.cpp @@ -89,3 +89,37 @@ auto TestWithAnon = WithAnon { .inner = 2 }; // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):33 %s -o - -std=c++2a | FileCheck -check-prefix=CHECK-CC5 %s // CHECK-CC5: COMPLETION: inner : [#int#]inner // CHECK-CC5: COMPLETION: outer : [#int#]outer + +// Field designator that traverses an anonymous struct: the IndirectFieldDecl +// branch in the lookup callback is required to resolve `anon` here. +struct WithAnonRecord { + struct Inner { + int leaf; + int other; + }; + struct { + Inner anon; + }; +}; +auto TestWithAnonRecord = WithAnonRecord { .anon.leaf = 2 }; + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):50 %s -o - -std=c++2a | FileCheck -check-prefix=CHECK-CC6 %s + // CHECK-CC6: COMPLETION: leaf : [#int#]leaf + // CHECK-CC6: COMPLETION: other : [#int#]other + +// Array element traversal: `Context.getAsArrayType` strips sugar so the +// element type is reached cleanly. +struct WithArrayField { + Base bases[2]; +}; +auto TestWithArrayField = WithArrayField { .bases[0].t = 2 }; + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):54 %s -o - -std=c++2a | FileCheck -check-prefix=CHECK-CC8 %s + // CHECK-CC8: COMPLETION: t : [#int#]t + +// Reference-typed field: `getNonReferenceType()` on the resolved field type +// lets the path continue into the referent's record. +struct WithRefField { + Base &ref; +}; +auto TestWithRefField = WithRefField { .ref.t = 2 }; + // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):45 %s -o - -std=c++2a | FileCheck -check-prefix=CHECK-CC7 %s + // CHECK-CC7: COMPLETION: t : [#int#]t diff --git a/clang/test/CodeCompletion/offsetof.cpp b/clang/test/CodeCompletion/offsetof.cpp new file mode 100644 index 0000000000000..76bc05356ebe6 --- /dev/null +++ b/clang/test/CodeCompletion/offsetof.cpp @@ -0,0 +1,95 @@ +struct S { + int field; + int other; + void method(); +}; + +struct Inner { + int leaf; + int otherLeaf; + void method(); +}; + +struct Outer { + Inner inner; + Inner array[2]; +}; + +struct Base { + int inherited; +}; + +struct Derived : Base { + int direct; +}; + +struct RefOuter { + Inner &ref; +}; + +struct WithAnon { + int outer; + union { + int anonInt; + Inner anonInner; + }; +}; + +struct ShadowBase { + Inner shadowed; +}; + +struct ShadowDerived : ShadowBase { + using ShadowBase::shadowed; +}; + +#define offsetof(type, member) __builtin_offsetof(type, member) + +// Cursor immediately after the comma: empty designator path. +int empty = __builtin_offsetof(S, field); +// RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):35 %s -o - -std=c++17 | FileCheck -check-prefix=CHECK-S --implicit-check-not=method %s + +// Cursor after a dot: completion in the nested record's type. +int after_dot = __builtin_offsetof(Outer, inner.leaf); +// RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):49 %s -o - -std=c++17 | FileCheck -check-prefix=CHECK-INNER --implicit-check-not=method %s + +// Cursor after a dot following an array subscript. +int array = __builtin_offsetof(Outer, array[0].leaf); +// RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):48 %s -o - -std=c++17 | FileCheck -check-prefix=CHECK-INNER --implicit-check-not=method %s + +// Inherited fields participate in offsetof completion. +int inherited = __builtin_offsetof(Derived, inherited); +// RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):45 %s -o - -std=c++17 | FileCheck -check-prefix=CHECK-DERIVED %s + +// Reference field: dereferenced before continuing the path. +int ref = __builtin_offsetof(RefOuter, ref.leaf); +// RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):44 %s -o - -std=c++17 | FileCheck -check-prefix=CHECK-INNER --implicit-check-not=method %s + +// Empty path on a record with anonymous-member indirect fields. +int anon_empty = __builtin_offsetof(WithAnon, anonInt); +// RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):47 %s -o - -std=c++17 | FileCheck -check-prefix=CHECK-ANON %s + +// Designator that starts with an indirect field from an anonymous member. +int anon_nested = __builtin_offsetof(WithAnon, anonInner.leaf); +// RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):58 %s -o - -std=c++17 | FileCheck -check-prefix=CHECK-INNER --implicit-check-not=method %s + +// Field exposed via `using Base::...` resolves through its UsingShadowDecl. +int shadowed = __builtin_offsetof(ShadowDerived, shadowed.leaf); +// RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):59 %s -o - -std=c++17 | FileCheck -check-prefix=CHECK-INNER --implicit-check-not=method %s + +// Macro form expands to __builtin_offsetof with the same completion behavior. +int macro = offsetof(S, field); +// RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):25 %s -o - -std=c++17 | FileCheck -check-prefix=CHECK-S --implicit-check-not=method %s + +// CHECK-S-DAG: COMPLETION: field : [#int#]field +// CHECK-S-DAG: COMPLETION: other : [#int#]other + +// CHECK-INNER-DAG: COMPLETION: leaf : [#int#]leaf +// CHECK-INNER-DAG: COMPLETION: otherLeaf : [#int#]otherLeaf + +// CHECK-DERIVED-DAG: COMPLETION: direct : [#int#]direct +// CHECK-DERIVED-DAG: COMPLETION: inherited (InBase) : [#int#]inherited + +// CHECK-ANON-DAG: COMPLETION: anonInner : [#Inner#]anonInner +// CHECK-ANON-DAG: COMPLETION: anonInt : [#int#]anonInt +// CHECK-ANON-DAG: COMPLETION: outer : [#int#]outer >From 1e7f7e7f1bb8af41789600c0efc70fd8b28d9edd Mon Sep 17 00:00:00 2001 From: Jakob Linke <[email protected]> Date: Thu, 7 May 2026 13:00:40 +0200 Subject: [PATCH 2/2] [clang] Filter bit-fields from offsetof completion offsetof rejects bit-fields as the member designator (err_offsetof_bitfield), so don't suggest them in completion. Adds tests covering both direct bit-fields and bit-fields exposed through anonymous-member IndirectFieldDecls. --- clang/lib/Sema/SemaCodeComplete.cpp | 9 ++++++- clang/test/CodeCompletion/offsetof.cpp | 33 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp index b8d2a80001613..c2cb44e08e59e 100644 --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -1677,7 +1677,14 @@ bool ResultBuilder::IsMember(const NamedDecl *ND) const { bool ResultBuilder::IsField(const NamedDecl *ND) const { ND = ND->getUnderlyingDecl(); - return isa<FieldDecl>(ND) || isa<IndirectFieldDecl>(ND); + // offsetof rejects bit-fields (err_offsetof_bitfield), so don't suggest + // them in completion. For an IndirectFieldDecl reaching through anonymous + // members, check the leaf field that would actually be referenced. + if (const auto *FD = dyn_cast<FieldDecl>(ND)) + return !FD->isBitField(); + if (const auto *IFD = dyn_cast<IndirectFieldDecl>(ND)) + return !IFD->getAnonField()->isBitField(); + return false; } static bool isObjCReceiverType(ASTContext &C, QualType T) { diff --git a/clang/test/CodeCompletion/offsetof.cpp b/clang/test/CodeCompletion/offsetof.cpp index 76bc05356ebe6..e528f32d911b0 100644 --- a/clang/test/CodeCompletion/offsetof.cpp +++ b/clang/test/CodeCompletion/offsetof.cpp @@ -43,6 +43,21 @@ struct ShadowDerived : ShadowBase { using ShadowBase::shadowed; }; +struct WithBitField { + int regular; + int bit : 4; + int : 4; + int otherRegular; +}; + +struct WithAnonBitField { + int outer; + struct { + int anonRegular; + int anonBit : 4; + }; +}; + #define offsetof(type, member) __builtin_offsetof(type, member) // Cursor immediately after the comma: empty designator path. @@ -81,6 +96,18 @@ int shadowed = __builtin_offsetof(ShadowDerived, shadowed.leaf); int macro = offsetof(S, field); // RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):25 %s -o - -std=c++17 | FileCheck -check-prefix=CHECK-S --implicit-check-not=method %s +// Bit-fields are not valid as the offsetof member designator +// (err_offsetof_bitfield), so they should not be offered as completions. +// Unnamed bit-fields have no identifier and are filtered out of lookup +// independently. +int bf = __builtin_offsetof(WithBitField, regular); +// RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):43 %s -o - -std=c++17 | FileCheck -check-prefix=CHECK-BF --implicit-check-not=bit %s + +// Bit-fields exposed via an anonymous struct's IndirectFieldDecl are also +// filtered: the leaf field is what offsetof would name. +int abf = __builtin_offsetof(WithAnonBitField, outer); +// RUN: %clang_cc1 -fsyntax-only -code-completion-patterns -code-completion-at=%s:%(line-1):48 %s -o - -std=c++17 | FileCheck -check-prefix=CHECK-ABF --implicit-check-not=anonBit %s + // CHECK-S-DAG: COMPLETION: field : [#int#]field // CHECK-S-DAG: COMPLETION: other : [#int#]other @@ -93,3 +120,9 @@ int macro = offsetof(S, field); // CHECK-ANON-DAG: COMPLETION: anonInner : [#Inner#]anonInner // CHECK-ANON-DAG: COMPLETION: anonInt : [#int#]anonInt // CHECK-ANON-DAG: COMPLETION: outer : [#int#]outer + +// CHECK-BF-DAG: COMPLETION: regular : [#int#]regular +// CHECK-BF-DAG: COMPLETION: otherRegular : [#int#]otherRegular + +// CHECK-ABF-DAG: COMPLETION: outer : [#int#]outer +// CHECK-ABF-DAG: COMPLETION: anonRegular : [#int#]anonRegular _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
