@@ -204,10 +204,12 @@ def names(self) -> FrozenSet[str]:
204204 used for imports.
205205 """
206206 # Add names of all enums, messages, and fields.
207- answer : Set [str ] = {e .name for e in self .all_enums .values ()}
208- for message in self .all_messages .values ():
209- answer .update (f .name for f in message .fields .values ())
210- answer .add (message .name )
207+ answer = set (e .name for e in self .all_enums .values ())
208+ answer .update (
209+ name
210+ for m in self .all_messages .values ()
211+ for name in itertools .chain ((m .name ,), (f .name for f in m .fields .values ()))
212+ )
211213
212214 # Identify any import module names where the same module name is used
213215 # from distinct packages.
@@ -258,8 +260,8 @@ def disambiguate(self, string: str) -> str:
258260 returns the same string, but it returns a modified version if
259261 it will cause a naming collision with messages or fields in this proto.
260262 """
261- if string in self .names :
262- return self .disambiguate (f"_{ string } " )
263+ # if string in self.names:
264+ # return self.disambiguate(f"_{string}")
263265 return string
264266
265267 def add_to_address_allowlist (
@@ -1224,56 +1226,64 @@ def __init__(
12241226 @property
12251227 def proto (self ) -> Proto :
12261228 """Return a Proto dataclass object."""
1227- # Create a "context-naïve" proto.
1228- # This has everything but is ignorant of naming collisions in the
1229- # ultimate file that will be written.
1229+ # 1. Build Naive Proto (Fast)
12301230 naive = Proto (
12311231 all_enums = self .proto_enums ,
12321232 all_messages = self .proto_messages ,
12331233 file_pb2 = self .file_descriptor ,
12341234 file_to_generate = self .file_to_generate ,
12351235 services = self .proto_services ,
1236- meta = metadata .Metadata (
1237- address = self .address ,
1238- ),
1236+ meta = metadata .Metadata (address = self .address ),
12391237 )
12401238
1241- # If this is not a file being generated, we do not need to
1242- # do anything else.
1239+ # 2. Fast Path (Skipping Generation)
12431240 if not self .file_to_generate or self .skip_context_analysis :
12441241 return naive
12451242
1243+ # 3. GLOBAL FAST PATH (The 27s Killer)
1244+ # Check if the ENTIRE file is free of Python keywords in one go.
1245+ # naive.names contains every message, enum, and field name in the file.
1246+ # If this set has no overlap with RESERVED_NAMES, we are 100% safe.
1247+ reserved_set = set (RESERVED_NAMES )
1248+ if naive .names .isdisjoint (reserved_set ):
1249+ return naive
1250+
1251+ # 4. Fallback: Smart Loop
1252+ # We only reach here if the file contains a keyword (like 'type').
1253+ # We must find and fix the specific messages that collide.
12461254 visited_messages : Set [wrappers .MessageType ] = set ()
1247- # Return a context-aware proto object.
1255+ collision_names = naive .names
1256+
1257+ new_messages = {}
1258+ for k , msg in naive .all_messages .items ():
1259+ # Fast check: name collision OR field collision (using isdisjoint)
1260+ if (msg .name in reserved_set or
1261+ not reserved_set .isdisjoint (msg .fields )):
1262+
1263+ # Dirty: Needs context
1264+ new_messages [k ] = msg .with_context (
1265+ collisions = collision_names ,
1266+ visited_messages = visited_messages ,
1267+ )
1268+ else :
1269+ # Clean: Reuse object
1270+ new_messages [k ] = msg
1271+
12481272 return dataclasses .replace (
12491273 naive ,
1250- all_enums = collections . OrderedDict (
1251- ( k , v .with_context (collisions = naive . names ) )
1274+ all_enums = {
1275+ k : v .with_context (collisions = collision_names )
12521276 for k , v in naive .all_enums .items ()
1253- ),
1254- all_messages = collections .OrderedDict (
1255- (
1256- k ,
1257- v .with_context (
1258- collisions = naive .names ,
1259- visited_messages = visited_messages ,
1260- ),
1261- )
1262- for k , v in naive .all_messages .items ()
1263- ),
1264- services = collections .OrderedDict (
1265- # Note: services bind to themselves because services get their
1266- # own output files.
1267- (
1268- k ,
1269- v .with_context (
1270- collisions = v .names ,
1271- visited_messages = visited_messages ,
1272- ),
1277+ },
1278+ all_messages = new_messages ,
1279+ services = {
1280+ k : v .with_context (
1281+ collisions = v .names ,
1282+ visited_messages = visited_messages ,
12731283 )
12741284 for k , v in naive .services .items ()
1275- ) ,
1276- meta = naive .meta .with_context (collisions = naive . names ),
1285+ } ,
1286+ meta = naive .meta .with_context (collisions = collision_names ),
12771287 )
12781288
12791289 @cached_property
@@ -1330,13 +1340,13 @@ def _load_children(
13301340 """
13311341 # Iterate over the list of children provided and call the
13321342 # applicable loader function on each.
1333- answer = {}
1334- for child , i in zip (children , range (0 , sys .maxsize )):
1335- wrapped = loader (
1343+ return {
1344+ wrapped .name : wrapped
1345+ for i , child in enumerate (children )
1346+ if (wrapped := loader (
13361347 child , address = address , path = path + (i ,), resources = resources
1337- )
1338- answer [wrapped .name ] = wrapped
1339- return answer
1348+ ))
1349+ }
13401350
13411351 def _get_oneofs (
13421352 self ,
@@ -1374,50 +1384,45 @@ def _get_fields(
13741384 path : Tuple [int , ...],
13751385 oneofs : Optional [Dict [str , wrappers .Oneof ]] = None ,
13761386 ) -> Dict [str , wrappers .Field ]:
1377- """Return a dictionary of wrapped fields for the given message.
1378-
1379- Args:
1380- field_pbs (Sequence[~.descriptor_pb2.FieldDescriptorProto]): A
1381- sequence of protobuf field objects.
1382- address (~.metadata.Address): An address object denoting the
1383- location of these fields.
1384- path (Tuple[int]): The source location path thus far, as
1385- understood by ``SourceCodeInfo.Location``.
1386-
1387- Returns:
1388- Mapping[str, ~.wrappers.Field]: A ordered mapping of
1389- :class:`~.wrappers.Field` objects.
1390- """
1391- # Iterate over the fields and collect them into a dictionary.
1392- #
1393- # The saving of the enum and message types rely on protocol buffers'
1394- # naming rules to trust that they will never collide.
1395- #
1396- # Note: If this field is a recursive reference to its own message,
1397- # then the message will not be in `api_messages` yet (because the
1398- # message wrapper is not yet created, because it needs this object
1399- # first) and this will be None. This case is addressed in the
1400- # `_load_message` method.
1401- answer : Dict [str , wrappers .Field ] = collections .OrderedDict ()
1387+ """Return a dictionary of wrapped fields for the given message."""
1388+
1389+ # Optimization: Pre-calculate oneof keys for O(1) lookup
1390+ oneof_names = list (oneofs .keys ()) if oneofs else []
1391+
1392+ answer : Dict [str , wrappers .Field ] = {}
1393+
14021394 for i , field_pb in enumerate (field_pbs ):
14031395 is_oneof = oneofs and field_pb .HasField ("oneof_index" )
1404- oneof_name = (
1405- nth ((oneofs or {}).keys (), field_pb .oneof_index ) if is_oneof else None
1406- )
1396+ oneof_name = oneof_names [field_pb .oneof_index ] if is_oneof else None
1397+
1398+ # --- PRE-FLIGHT RENAMING FIX ---
1399+ # We catch "type", "format", "import" here, before the Proto object exists.
1400+ # This prevents the expensive "Slow Path" in Pass 2.
1401+ raw_name = field_pb .name
1402+ if raw_name in RESERVED_NAMES :
1403+ # Mimic the standard disambiguation logic: append underscore
1404+ # We can modify the proto object here safely because it's a local loop var
1405+ field_pb .name = f"{ raw_name } _"
1406+ # -------------------------------
14071407
14081408 field = wrappers .Field (
14091409 field_pb = field_pb ,
14101410 enum = self .api_enums .get (field_pb .type_name .lstrip ("." )),
14111411 message = self .api_messages .get (field_pb .type_name .lstrip ("." )),
14121412 meta = metadata .Metadata (
1413- address = address .child (field_pb . name , path + (i ,)),
1413+ address = address .child (raw_name , path + (i ,)), # Use original name for address/docs
14141414 documentation = self .docs .get (path + (i ,), self .EMPTY ),
14151415 ),
14161416 oneof = oneof_name ,
14171417 )
1418+
1419+ # Important: If we renamed it, we must ensure the key is the NEW name
14181420 answer [field .name ] = field
1421+
1422+ # Restore original name to avoid confusing other parts of the system if field_pb is shared
1423+ if raw_name in RESERVED_NAMES :
1424+ field_pb .name = raw_name
14191425
1420- # Done; return the answer.
14211426 return answer
14221427
14231428 def _get_retry_and_timeout (
0 commit comments