Files
Jakob Linke 8b1c7ea6f6 [clang] Complete fields in __builtin_offsetof designators (#195126)
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.

Context: #195126 and #194407

This work was AI assisted but human-reviewed.

The followup for the added FIXME is at
https://github.com/llvm/llvm-project/compare/main...schuay:llvm-project:refactor-offsetof-component-to-designation
2026-05-21 14:03:36 +03:00

129 lines
4.9 KiB
C++

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;
};
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.
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
// 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
// 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
// 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