Repository: eliben/pycparser

Issue:
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.

Comments:

Patch:
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:


Test 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 = """