)]}'
{
  "commit": "f1fc0efe489ff122d4cd034bb6552c8153cec5d2",
  "tree": "2428bf7996029bc1400ea30bd905b9fc7392f9fd",
  "parents": [
    "b445fe13b1e9299abe28ccf457fb278e6a4496b6"
  ],
  "author": {
    "name": "Tobias Sargeant",
    "email": "tobias.sargeant@gmail.com",
    "time": "Tue Aug 25 10:34:30 2026 -0700"
  },
  "committer": {
    "name": "Copybara-Service",
    "email": "copybara-worker@google.com",
    "time": "Tue Aug 25 10:44:56 2026 -0700"
  },
  "message": "Python: don\u0027t emit ClassVar for a file\u0027s top-level extensions (#29222)\n\nFixes #29221.\n\n### Problem\n\n`PyiGenerator::PrintExtensions` is a template instantiated for two different scopes, and emits `_ClassVar[int]` for both:\n\n| call site | descriptor | output scope | `ClassVar` valid |\n| --- | --- | --- | --- |\n| `pyi_generator.cc:481` `PrintExtensions(message_descriptor)` | `Descriptor` | class body | yes |\n| `pyi_generator.cc:656` `PrintExtensions(*public_dep)` | `FileDescriptor` | module | **no** |\n| `pyi_generator.cc:669` `PrintExtensions(*file_)` | `FileDescriptor` | module | **no** |\n\nPEP 526 restricts `ClassVar` to class bodies, so a proto declaring a file-level extension generates a `.pyi` that does not type-check.\n\nGiven:\n\n```proto\nsyntax \u003d \"proto2\";\npackage probe;\nimport \"google/protobuf/descriptor.proto\";\n\nextend google.protobuf.FileOptions {\n  optional bool top_level_ext \u003d 50001;\n}\n\nmessage Holder {\n  extend google.protobuf.MessageOptions {\n    optional bool nested_ext \u003d 50002;\n  }\n}\n```\n\nbefore:\n\n```python\nDESCRIPTOR: _descriptor.FileDescriptor\nTOP_LEVEL_EXT_FIELD_NUMBER: _ClassVar[int]      # module scope\ntop_level_ext: _descriptor.FieldDescriptor\n\nclass Holder(_message.Message):\n    __slots__ \u003d ()\n    NESTED_EXT_FIELD_NUMBER: _ClassVar[int]     # class body\n    nested_ext: _descriptor.FieldDescriptor\n    def __init__(self) -\u003e None: ...\n```\n\n```\next_pb2.pyi:7:1 - error: \"ClassVar\" is not allowed in this context (reportInvalidTypeForm)\n```\n\nOnly the module-scope constant is flagged; the nested one is correct as-is.\n\n### Change\n\n`PrintEnumValues` in the same file already carries a `bool is_classvar \u003d false` parameter for exactly this distinction. This gives `PrintExtensions` the same parameter and passes `true` only from the message call site, so the two `FileDescriptor` instantiations fall through to a plain `int` annotation. No template specialisation needed.\n\nAfter:\n\n```python\nTOP_LEVEL_EXT_FIELD_NUMBER: int\n```\n\nThe nested case is unchanged.\n\n### Notes\n\n- Affects any proto with a top-level `extend`, not only extension-only files — the repro above also contains a message.\n- `.pyi` golden/expected outputs containing top-level extensions will need regenerating.\n- I have not built or run the test suite locally; relying on CI for that.\n\nCloses #29222\n\nCOPYBARA_INTEGRATE_REVIEW\u003dhttps://github.com/protocolbuffers/protobuf/pull/29222 from folded:pyi-no-classvar-for-toplevel-extensions 53f1b7bb99829727d012b0cf5567ff7217d5aaef\nFUTURE_COPYBARA_INTEGRATE_REVIEW\u003dhttps://github.com/protocolbuffers/protobuf/pull/29222 from folded:pyi-no-classvar-for-toplevel-extensions 53f1b7bb99829727d012b0cf5567ff7217d5aaef\nPiperOrigin-RevId: 970646537\n",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "e792566b55145f8e59ade51a2a68729cf57c5ceb",
      "old_mode": 33188,
      "old_path": "src/google/protobuf/compiler/python/plugin_unittest.cc",
      "new_id": "0b8ae6938f6ad277e95f400822782aee34a053c4",
      "new_mode": 33188,
      "new_path": "src/google/protobuf/compiler/python/plugin_unittest.cc"
    },
    {
      "type": "modify",
      "old_id": "541bec0aa91b48098b9ef086e2370d20c83159f5",
      "old_mode": 33188,
      "old_path": "src/google/protobuf/compiler/python/pyi_generator.cc",
      "new_id": "dfa4bc4256dc76efcb2c7a2331776f9db16eb58d",
      "new_mode": 33188,
      "new_path": "src/google/protobuf/compiler/python/pyi_generator.cc"
    },
    {
      "type": "modify",
      "old_id": "1beb4d11af39b640f5ff9458b9089f691e54b8e3",
      "old_mode": 33188,
      "old_path": "src/google/protobuf/compiler/python/pyi_generator.h",
      "new_id": "adf752c5b6ddaa38395401dd699a48a8d35b180e",
      "new_mode": 33188,
      "new_path": "src/google/protobuf/compiler/python/pyi_generator.h"
    }
  ]
}
