Capture Proxy [[Construct]] at creation per ProxyCreate#2669
Merged
Conversation
lahma
enabled auto-merge (squash)
July 13, 2026 12:54
Per https://tc39.es/ecma262/#sec-proxycreate, a proxy exotic object only gets a [[Construct]] internal method when its target has one at creation time - a handler "construct" trap cannot confer constructability. Jint's JsProxy.IsConstructor instead probed the handler for a "construct" property, which made `new (new Proxy(() => {}, { construct: ... }))()` succeed where the spec requires a TypeError, and the handler probe via TryGetValue could fire user-defined getters as a side effect of a mere IsConstructor check (e.g. from Reflect.construct newTarget validation or `class extends`). IsConstructor is now captured into a readonly field in the constructor (mirroring the existing IsCallable capture), making probes side-effect free and keeping the verdict stable after revocation nulls the target - matching the spec model where the internal-method slots themselves survive revocation. Also fixes error ordering in the [[Call]]/[[Construct]] paths: the revocation check now runs before the callable/constructor check, so invoking a revoked function proxy reports "Cannot perform 'apply'/ 'construct' on a proxy that has been revoked" instead of the misleading "(intermediate value) is not a function/constructor" (the defensive checks now use the captured slots and real messages). typeof on revoked proxies is unaffected since IsCallable stays a captured value. Cross-checked against Node.js: non-constructor target + construct trap throws TypeError, constructor target + trap returns the trap result, Reflect.construct rejects a non-constructor proxy newTarget, and revoked proxies keep typeof "function" while apply/construct throw the revoked TypeError with identical wording. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EsHuKuE4UihKZp5HYapDHE
lahma
force-pushed
the
proxy-isconstructor
branch
from
July 13, 2026 14:04
08526c3 to
771ec15
Compare
This was referenced Jul 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Per ProxyCreate, a proxy has [[Construct]] iff its target is a constructor at creation time — a handler
constructtrap cannot confer constructability.JsProxy.IsConstructoradditionally probed the handler for aconstructproperty, so:The probe (
handler.TryGetValue("construct")) could also fire user getters as a side effect of mereIsConstructorchecks (e.g.Reflect.constructnewTarget validation).Changes:
_isConstructorcaptured once in the constructor fromtarget.IsConstructor(mirrors howIsCallableis already captured); the property returns the field.[[Construct]]/[[Call]]now check revocation first, so calling/constructing a revoked proxy throws the correctCannot perform 'construct'/'apply' on a proxy that has been revoked(character-identical to V8) instead of"(intermediate value) is not a constructor/function"; the defensive not-a-constructor/function guards remain (JsProxy always implements the call interfaces, so callers' casts pass even for non-callable targets) with real messages.typeofon revoked proxies is unaffected (IsCallablestays a captured value, per spec: revocation does not remove the slots).Cross-checked against node 24 / V8 on all new tests. Observable behavior change (construct-trap-on-non-constructor now correctly throws) — worth a release-note line.
🤖 Generated with Claude Code
https://claude.ai/code/session_01EsHuKuE4UihKZp5HYapDHE