eliben: Thanks for the contribution, and apologies for the delay. I've been very busy lately, and expect to have some time for pycparser in the next few weeks.

diff --git a/README.rst b/README.rst
index df9025c..682abf7 100644
--- a/README.rst
+++ b/README.rst
@@ -161,6 +161,9 @@ See `this blog post
 <https://eli.thegreenplace.net/2015/on-parsing-c-type-declarations-and-fake-headers>`_
 for more details.
 
+Note that the fake headers are not included in the ``pip`` package nor installed
+via ``setup.py`` (`#224 <https://github.com/eliben/pycparser/issues/224>`_).
+
 Basic usage
 -----------
 
diff --git a/pycparser/c_parser.py b/pycparser/c_parser.py
index 4cf96fa..744ede8 100644
--- a/pycparser/c_parser.py
+++ b/pycparser/c_parser.py
@@ -1740,8 +1740,7 @@ class CParser(PLYParser):
         if len(p) == 2:
             p[0] = p[1]
         elif len(p) == 4:
-            field = c_ast.ID(p[3], self._token_coord(p, 3))
-            p[0] = c_ast.StructRef(p[1], p[2], field, p[1].coord)
+            p[0] = c_ast.StructRef(p[1], p[2], p[3], p[1].coord)
         elif len(p) == 5:
             p[0] = c_ast.ArrayRef(p[1], p[3], p[1].coord)
         else:

Incorrect AST structure when parsing `offsetof` expressions.
I am using the latest commit on `master` branch (`1166ea1`).

Take the following code as example:
```c
int x = offsetof(struct A, a.b);
```
The generated parse tree is:
```python
FileAST(ext=[Decl(name='x',
                  quals=[],
                  storage=[],
                  funcspec=[],
                  type=TypeDecl(declname='x',
                                quals=[],
                                type=IdentifierType(names=['int'])),
                  init=FuncCall(name=ID(name='offsetof'),
                                args=ExprList(exprs=[Typename(name=None,
                                                              quals=[],
                                                              type=TypeDecl(declname=None,
                                                                            quals=[],
                                                                            type=Struct(name='A',
                                                                                        decls=None))),
                                                     StructRef(name=ID(name='a'),
                                                               type='.',
                                                               field=ID(name=ID(name='b')))])),
                  bitsize=None)])
```
where the `field` attribute of `StructRef` has a value of `ID(name=ID(name='b'))` instead of just `ID(name='b')`.

I think the issue is with the production rules of `offsetof_member_designator` that was introduced in #145, namely:
https://github.com/eliben/pycparser/blob/1166ea11785ce12cdfd5e8bf8b3a69b5e6b76f9c/pycparser/c_parser.py#L1735-L1748
In the `len(p) == 4` case, `StructRef` should be created using `p[3]` directly, instead of constructing an `ID` first.

If you agree with my analysis, I'd be happy to create a patch.
diff --git a/tests/test_c_parser.py b/tests/test_c_parser.py
index 49cada3..b6ecdd5 100755
--- a/tests/test_c_parser.py
+++ b/tests/test_c_parser.py
@@ -529,6 +529,18 @@ class TestCParser_fundamentals(TestCParser_base):
                             ['IdentifierType', ['int']]]]]])
 
     def test_offsetof(self):
+        def expand_ref(n):
+            if isinstance(n, StructRef):
+                return ['StructRef', expand_ref(n.name), expand_ref(n.field)]
+            elif isinstance(n, ArrayRef):
+                return ['ArrayRef', expand_ref(n.name), expand_ref(n.subscript)]
+            elif isinstance(n, ID):
+                return ['ID', n.name]
+            elif isinstance(n, Constant):
+                return ['Constant', n.type, n.value]
+            else:
+                raise TypeError("Unexpected type " + n.__class__.__name__)
+
         e = """
             void foo() {
                 int a = offsetof(struct S, p);
@@ -546,8 +558,20 @@ class TestCParser_fundamentals(TestCParser_base):
         self.assertIsInstance(s1.args.exprs[1], ID)
         s3 = compound.block_items[2].init
         self.assertIsInstance(s3.args.exprs[1], StructRef)
+        self.assertEqual(expand_ref(s3.args.exprs[1]),
+            ['StructRef',
+                ['StructRef', ['ID', 'p'], ['ID', 'q']],
+                ['ID', 'r']])
         s4 = compound.block_items[3].init
         self.assertIsInstance(s4.args.exprs[1], ArrayRef)
+        self.assertEqual(expand_ref(s4.args.exprs[1]),
+            ['ArrayRef',
+                ['ArrayRef',
+                    ['StructRef',
+                        ['ArrayRef', ['ID', 'p'], ['Constant', 'int', '5']],
+                        ['ID', 'q']],
+                    ['Constant', 'int', '4']],
+                ['Constant', 'int', '5']])
 
     def test_compound_statement(self):
         e = """

pip install -e .
parse_log_pytest
pytest
pytest
apt-get update apt-get install -y gcc
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
packaging @ file:///croot/packaging_1734472117206/work
pluggy @ file:///croot/pluggy_1733169602837/work
-e git+https://github.com/eliben/pycparser.git@1166ea11785ce12cdfd5e8bf8b3a69b5e6b76f9c#egg=pycparser
pytest @ file:///croot/pytest_1738938843180/work
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work

name: pycparser
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py39h06a4308_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
prefix: /opt/conda/envs/pycparser

tests/test_c_parser.py::TestCParser_fundamentals::test_offsetof
tests/test_c_parser.py::TestCParser_fundamentals::test_FileAST tests/test_c_parser.py::TestCParser_fundamentals::test_anonymous_struct_union tests/test_c_parser.py::TestCParser_fundamentals::test_compound_literals tests/test_c_parser.py::TestCParser_fundamentals::test_compound_statement tests/test_c_parser.py::TestCParser_fundamentals::test_coords tests/test_c_parser.py::TestCParser_fundamentals::test_decl_inits tests/test_c_parser.py::TestCParser_fundamentals::test_decl_named_inits tests/test_c_parser.py::TestCParser_fundamentals::test_duplicate_typedef tests/test_c_parser.py::TestCParser_fundamentals::test_empty_toplevel_decl tests/test_c_parser.py::TestCParser_fundamentals::test_enums tests/test_c_parser.py::TestCParser_fundamentals::test_forloop_coord tests/test_c_parser.py::TestCParser_fundamentals::test_func_decls_with_array_dim_qualifiers tests/test_c_parser.py::TestCParser_fundamentals::test_function_definitions tests/test_c_parser.py::TestCParser_fundamentals::test_initial_semi tests/test_c_parser.py::TestCParser_fundamentals::test_inline_specifier tests/test_c_parser.py::TestCParser_fundamentals::test_int128 tests/test_c_parser.py::TestCParser_fundamentals::test_invalid_multiple_types_error tests/test_c_parser.py::TestCParser_fundamentals::test_multi_decls tests/test_c_parser.py::TestCParser_fundamentals::test_nested_decls tests/test_c_parser.py::TestCParser_fundamentals::test_pragma tests/test_c_parser.py::TestCParser_fundamentals::test_pragmacomp_or_statement tests/test_c_parser.py::TestCParser_fundamentals::test_qualifiers_storage_specifiers tests/test_c_parser.py::TestCParser_fundamentals::test_simple_decls tests/test_c_parser.py::TestCParser_fundamentals::test_sizeof tests/test_c_parser.py::TestCParser_fundamentals::test_struct_bitfields tests/test_c_parser.py::TestCParser_fundamentals::test_struct_empty tests/test_c_parser.py::TestCParser_fundamentals::test_struct_members_namespace tests/test_c_parser.py::TestCParser_fundamentals::test_struct_union tests/test_c_parser.py::TestCParser_fundamentals::test_struct_with_extra_semis_inside tests/test_c_parser.py::TestCParser_fundamentals::test_struct_with_initial_semi tests/test_c_parser.py::TestCParser_fundamentals::test_tags_namespace tests/test_c_parser.py::TestCParser_fundamentals::test_typedef tests/test_c_parser.py::TestCParser_fundamentals::test_unified_string_literals tests/test_c_parser.py::TestCParser_fundamentals::test_unified_wstring_literals tests/test_c_parser.py::TestCParser_fundamentals::test_vla tests/test_c_parser.py::TestCParser_whole_code::test_empty_statements tests/test_c_parser.py::TestCParser_whole_code::test_expressions tests/test_c_parser.py::TestCParser_whole_code::test_for_statement tests/test_c_parser.py::TestCParser_whole_code::test_statements tests/test_c_parser.py::TestCParser_whole_code::test_switch_statement tests/test_c_parser.py::TestCParser_whole_code::test_whole_file tests/test_c_parser.py::TestCParser_whole_code::test_whole_file_with_stdio tests/test_c_parser.py::TestCParser_typenames::test_ambiguous_parameters tests/test_c_parser.py::TestCParser_typenames::test_innerscope_reuse_typedef_name tests/test_c_parser.py::TestCParser_typenames::test_innerscope_typedef tests/test_c_parser.py::TestCParser_typenames::test_nested_function_decls tests/test_c_parser.py::TestCParser_typenames::test_parameter_reuse_typedef_name tests/test_c_parser.py::TestCParser_typenames::test_samescope_reuse_name
README.rst pycparser/c_parser.py
README.rst pycparser/c_parser.py