Skip to content

Fix Python 3.11+ incompatibility: size int-like instruction args as one byte#151

Open
HansGR wants to merge 1 commit into
ff6wc:devfrom
HansGR:bugfix/size-int-like-instruction
Open

Fix Python 3.11+ incompatibility: size int-like instruction args as one byte#151
HansGR wants to merge 1 commit into
ff6wc:devfrom
HansGR:bugfix/size-int-like-instruction

Conversation

@HansGR

@HansGR HansGR commented Jun 4, 2026

Copy link
Copy Markdown

On Python 3.11+, -ruin/door-rando seeds (and any build whose event
layout places a multi-bit IntFlag arg before a forward label) can fail at
the final ROM write with:

TypeError: 'NoneType' object cannot be interpreted as an integer

Root cause: Python 3.11 added __len__ to enum.IntFlag, which returns
the number of set flags (popcount) instead of raising TypeError. The
byte-counting helpers in memory/space.py size flattened values with
try: n += len(value) except: n += 1, relying on len() raising for
scalar byte values. Instruction args that are IntFlag members are int
subclasses representing a single byte, but on 3.11+ len() now returns
their popcount, e.g.:

len(Flash.WHITE)  # 0xE0, 3 bits -> 3   (was: TypeError -> counted as 1)
len(Flash.NONE)   # 0x00, 0 bits -> 0   (was: TypeError -> counted as 1)

Flash is an IntFlag and is passed straight through to FlashScreen, so
field.FlashScreen(field.Flash.WHITE) makes _parse_labels over-count the
byte offset by 2. Every subsequent label pointer in that block is then
written two bytes past its placeholder, leaving unresolved None bytes in
the ROM data that surface at bytearray(self.data). (IntEnum, e.g.
Status, is unaffected -- only IntFlag gained __len__.)

Fix: treat int instances (which include IntEnum/IntFlag) as a single
byte in the three byte-counting sites -- _parse_labels, clear, and the
Write size pre-pass -- so counts match the bytes actually emitted on every
interpreter. No behavior change on 3.10; 3.11 and 3.12 now produce output
identical to each other.

Repro of the underlying enum change (no game data needed):

python3.10 -c "from enum import IntFlag; \
  class F(IntFlag): A=0x80;B=0x40;C=0x20; \
  print(len(F.A|F.B|F.C))"   # TypeError
python3.11 -c "from enum import IntFlag; \
  class F(IntFlag): A=0x80;B=0x40;C=0x20; \
  print(len(F.A|F.B|F.C))"   # 3

https://claude.ai/code/session_01Tvbr7PfXazsR3SAPXUMKmc

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates memory/space.py to ensure compatibility with Python 3.11+, where IntFlag objects implement len() to return a popcount. By introducing explicit isinstance(value, int) checks, the code avoids incorrect length calculations for integer-like values. The review feedback recommends replacing a bare except: clause in the clear method with specific exception handling (TypeError and ZeroDivisionError) to prevent catching system-interrupting exceptions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread memory/space.py
Comment on lines +95 to +98
try:
values = [value] * (len(self) // len(value))
except:
values = [value] * len(self)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid using a bare except: clause as it can catch unexpected exceptions like SystemExit or KeyboardInterrupt, making debugging harder and preventing proper program interruption. Since len(value) can raise a TypeError (if the object has no length) or ZeroDivisionError (if the length is zero), it is better to catch these specific exceptions explicitly.

Suggested change
try:
values = [value] * (len(self) // len(value))
except:
values = [value] * len(self)
try:
values = [value] * (len(self) // len(value))
except (TypeError, ZeroDivisionError):
values = [value] * len(self)
References
  1. PEP 8 recommends avoiding bare except clauses because they catch SystemExit and KeyboardInterrupt exceptions, making it harder to interrupt a program with Control-C. (link)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant