Skip to content

eval() support + co-locate SharpTS.dll only when needed (#107)#115

Merged
nickna merged 2 commits into
mainfrom
feature/eval-and-runtime-copy
Jun 6, 2026
Merged

eval() support + co-locate SharpTS.dll only when needed (#107)#115
nickna merged 2 commits into
mainfrom
feature/eval-and-runtime-copy

Conversation

@nickna
Copy link
Copy Markdown
Owner

@nickna nickna commented Jun 6, 2026

Summary

Two related changes:

  1. eval() support (closes Test262 #8: eval(...) rejected in property-accessors and elsewhere (~10+ tests, broader impact) #107) — real eval in both execution modes.
  2. Runtime-dependency signal + auto-copy — co-locate SharpTS.dll with compiled output only when a feature actually needs it at runtime.

1. eval() (#107)

eval is now registered as a global typed (s: string) => any (identifier + call paths), removing the categorical TypeCheckError floor that blocked any program referencing eval.

  • Interpreted: direct eval — Interpreter.Eval() lexes/parses/interprets the source against the caller's scope chain and returns the completion value. Not type-checked (matches tsc); non-string args returned unchanged (ECMA-262 §19.2.1); parse failures throw SyntaxError.
  • Compiled: indirect eval via EvalBridge in the SharpTS runtime, invoked through the Type.GetType("…,SharpTS") reflection pattern so the output DLL keeps no hard SharpTS reference. Global builtins resolve; compiled locals are not visible (no live interpreter behind native IL). Degrades to a deterministic "eval not supported" throw when SharpTS.dll is absent.

Acceptance verified

The issue's criterion — the property-accessor eval tests move off TypeCheckError — is confirmed: test/language/expressions/property-accessors/S11.2.1_A1.1.js now passes all 9 Unicode-whitespace checks (TAB, VT, FF, SP, NBSP, LF, CR, LS, PS) in both interpreted and compiled modes (TypeCheckError → Pass).

2. Co-locate SharpTS.dll only when needed

Some features (eval, Proxy, Intl, vm, dns, @DotNetType dynamic events) emit a Type.GetType("…,SharpTS") path whose normal execution needs SharpTS.dll present. A byte-scan for ,SharpTS is too coarse — the unconditional process helper bakes that string into every DLL, yet a trivial program runs fine standalone. So this adds a semantic signal:

  • EmittedRuntime.RequiredSharpTSRuntimeReasons + RequireSharpTSRuntime(reason), surfaced via ILCompiler.RequiredSharpTSRuntimeReasons (one set per compilation).
  • Recorded at the gates/call sites that emit a reachable late-binding path; not recorded for pure-BCL features (zlib, child_process, JSON) or unconditional graceful-fallback plumbing (process).
  • Program.cs copies SharpTS.dll next to the output after Save only when the reason set is non-empty — programs using none of these stay fully standalone.
  • New --compile … --standalone flag suppresses the copy (those features then throw their clear "not supported" error at runtime).

Verified end-to-end

  • Trivial program → no copy.
  • eval program → Copied SharpTS.dll … required at runtime by: eval(); co-located DLL runs.
  • Proxy program → copy with reason Proxy; runs.
  • --standalone → note printed, no copy.

Tests

  • EvalTests — both modes (whitespace tolerance, completion values, non-string passthrough, var decls, builtin calls) + an interpreter-only direct-eval local-capture case.
  • RuntimeDependencySignalTests — trivial = empty reasons; eval/Proxy/Intl flagged; eval does not false-positive other features.
  • Full suite: 10335 passed; only the 2 pre-existing PackagingTests.Pack_WithoutPackageJson_* failures remain (confirmed failing at baseline main, unrelated). Standalone-DLL guard green.

Known limitations / follow-ups (not in scope here)

  • Compiled eval is indirect-only (can't see compiled locals) — by design.
  • AbortSignal.any late-binds but is gated only at the coarse UsesAbortController level, so it's left unflagged to avoid penalizing common AbortController + fetch use.
  • The -t exe and NuGet packaging paths don't yet consume the runtime-dependency signal.

Closes #107.

nickna added 2 commits June 5, 2026 22:34
Register `eval` as a global typed `(s: string) => any` (identifier + call
paths), removing the categorical TypeCheckError floor that blocked any
program referencing eval.

Interpreted: direct eval — Interpreter.Eval() lexes/parses/interprets the
source against the caller's scope chain and returns the completion value.
Not type-checked (matches tsc); non-string args returned unchanged
(ECMA-262 19.2.1); parse failures throw SyntaxError.

Compiled: indirect eval via EvalBridge in the SharpTS runtime, invoked
through the Type.GetType("...,SharpTS") reflection pattern so the output
DLL keeps no hard SharpTS reference. Global builtins resolve; compiled
locals are not visible. Degrades to a deterministic "eval not supported"
throw when SharpTS.dll is absent. GlobalFunctionHandler added to the
standalone-DLL late-binding allowlist.

Tests: EvalTests covers both modes (whitespace tolerance, completion
values, non-string passthrough, var decls, builtin calls) plus an
interpreter-only direct-eval local-capture case.
Add a semantic "needs-SharpTS-at-runtime" signal so compiled output gains
the SharpTS.dll soft dependency only when it actually uses a feature whose
normal execution late-binds into the runtime — and stays fully standalone
otherwise. A byte-scan for "...,SharpTS" is too coarse: the unconditional
`process` helper bakes that string into every DLL, yet a trivial program
runs fine without SharpTS present.

Mechanism:
- EmittedRuntime.RequiredSharpTSRuntimeReasons + RequireSharpTSRuntime(reason),
  surfaced via ILCompiler.RequiredSharpTSRuntimeReasons (one set per compile).
- Recorded at the gates/call sites that emit a reachable Type.GetType("...,
  SharpTS"): eval (call site), Proxy / Intl / vm / dns (orchestrator gates),
  @DotNetType dynamic-event reflected path.
- Deliberately NOT recorded for pure-BCL features (zlib, child_process, JSON)
  or unconditional graceful-fallback plumbing (process).
- Program.cs CopySharpTSRuntimeIfNeeded copies SharpTS.dll next to the output
  after Save when the reason set is non-empty, printing the reasons.
- New `--compile --standalone` flag suppresses the copy (those features then
  throw their clear "not supported" error at runtime).

Known gaps (follow-ups): AbortSignal.any late-binds but is gated only at the
coarse UsesAbortController level, so it is left unflagged to avoid penalizing
common AbortController+fetch use; the -t exe and NuGet packaging paths do not
yet consume this signal.

Tests: RuntimeDependencySignalTests asserts trivial=empty, eval/Proxy/Intl
flagged, and eval does not false-positive other features. Docs updated in
CLAUDE.md (standalone-DLL section) and STATUS.md (eval entry).
@nickna
Copy link
Copy Markdown
Owner Author

nickna commented Jun 6, 2026

Follow-ups for the runtime-copy mechanism (out of scope for this PR), now tracked:

@nickna nickna merged commit 99b029b into main Jun 6, 2026
3 checks passed
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.

Test262 #8: eval(...) rejected in property-accessors and elsewhere (~10+ tests, broader impact)

1 participant