-
Notifications
You must be signed in to change notification settings - Fork 23
fix: Enforce the 512-character cap on a let binding identifier #348
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a6a1c88
ab33552
c476cf8
344135c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -921,6 +921,11 @@ class ScriptInterpreter(str, Enum): | |
| LET_MAX_BINDINGS = 50 | ||
| _LET_NAME_RE = re.compile(r"^[a-z_][A-Za-z0-9_]*$") | ||
|
|
||
| # §3.6.1: maximum length of a `let` binding's `<UserIdentifier>`. Flat, so not | ||
| # the §7.1 cap NameIdentifierLengthMixin applies: that one is 64 without | ||
| # FEATURE_BUNDLE_1, and a 512-character name must be accepted with EXPR alone. | ||
| LET_MAX_IDENTIFIER_LEN = 512 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scope question on the cap: If the intent is "enforce the §3.6.1 identifier cap," the two forms should agree; if the intent is narrower — only the
|
||
|
|
||
|
|
||
| def parse_let_bindings(value: Any) -> list[tuple[str, str]]: | ||
| """Parse a ``let`` field value (list of ``"name = expression"`` strings) | ||
|
|
@@ -941,6 +946,14 @@ def parse_let_bindings(value: Any) -> list[tuple[str, str]]: | |
| expr = expr.strip() | ||
| if not _LET_NAME_RE.match(name): | ||
| raise ValueError(f"A 'let' binding name must be a valid identifier: {name!r}") | ||
| # Truncated rather than omitted: the caller is a field_validator on the | ||
| # whole list, so the error path is `let` with no index to identify which | ||
| # binding is over. | ||
| if len(name) > LET_MAX_IDENTIFIER_LEN: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The length check is placed after the So Moving the length check above the regex check fixes both: nothing over 512 characters ever reaches an interpolation site. if len(name) > LET_MAX_IDENTIFIER_LEN:
raise ValueError(
f"A 'let' binding name must be at most {LET_MAX_IDENTIFIER_LEN} "
f"characters long: {name[:32]!r}... ({len(name)} characters)"
)
if not _LET_NAME_RE.match(name):
raise ValueError(f"A 'let' binding name must be a valid identifier: {name!r}")Worth noting |
||
| raise ValueError( | ||
| f"A 'let' binding name must be at most {LET_MAX_IDENTIFIER_LEN} " | ||
| f"characters long: {name[:32]!r}... ({len(name)} characters)" | ||
| ) | ||
| if not expr: | ||
| raise ValueError(f"A 'let' binding must define an expression: {binding!r}") | ||
| result.append((name, expr)) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parity note: this cap lands only in the v0 pure-Python path, and there is no corresponding coverage on the Rust-backed side.
test/openjd/model_v1/test_let_bindings.pyhas no length-boundary test, andtest_known_gaps.pyrecords no gap for it — so ifopenjd-modeldoes not enforce a 512-character<UserIdentifier>cap, the two implementations now silently disagree on a template the spec section this PR cites is specifically about.AGENTS.md calls out reference parity as a tracked artifact and
test_known_gaps.pyas the place divergences get recorded (xfail, driven to zero). Either a matching v1 test or aknown_gapsentry would keep this from drifting unnoticed; a v0-only cap with nothing on the v1 side is the shape that parity tracking exists to catch.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack and there is a rust PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parity is covered by OpenJobDescription/openjd-rs#358, which adds the same cap on the Rust side with its own seven tests, so the two implementations agree rather than diverging.
On the tracking ask specifically: not adding a
known_gapsentry, because there is no gap left to record once #358 merges, and an xfail that never fails is worse than nothing. Not adding a v1 test either, since the v1 surface isevaluate_let_bindings, which runs on already-validated templates and does not perform this check in either implementation.The honest residue is a release-ordering window: this repo's cap lands with this PR, the Rust one lands with #358, and they release independently, so between the two releases the pinned behaviour differs by version. The conformance fixture that covers this is parked in
proposed/in openjd-specifications#164 for exactly that reason and moves out once both have shipped. Happy to add a tracking entry instead if you would rather that window were recorded here.