Skip to content

fix(native): require a runtime provider for native identity - #2274

Merged
samchon merged 3 commits into
masterfrom
fix/metadata-identity-residuals
Jul 20, 2026
Merged

fix(native): require a runtime provider for native identity#2274
samchon merged 3 commits into
masterfrom
fix/metadata-identity-residuals

Conversation

@samchon

@samchon samchon commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Purpose

Closes #2200, which was removed from the consolidated campaign wave (#2266) because no in-repo fixture could express its premise. #2273 fixed that, and the work is now verifiable.

Problem

metadata_symbol_is_global_type decided native identity with checker.ResolveName(name, nil, SymbolFlagsType, false) == symbol. Winning the checker's global type table proves a declaration is global, not that a runtime provides the constructor, so a project whose only File or Blob declaration is its own declare global block won that same lookup. Its members were erased, input instanceof File replaced them, and the check throws ReferenceError wherever no runtime supplies that constructor.

One predicate owns the whole surface: it also gates Map/Set, WeakMap/WeakSet through iterate_metadata_native.go, and all four intersection identity sites.

Approach

Native identity now requires a runtime provider — a default-library declaration, a declaration inside the ambient node:buffer/buffer module, or a constructor binding that resolves into either. The third path is load-bearing rather than defensive: @types/node 25 declares the bare global as interface Blob extends _Blob {} plus var Blob: … typeof buffer.Blob in an ordinary package .d.ts, so nothing but the value binding carries its provenance.

Answering "is this a default library file" from the lib.*.d.ts base name would have made this worse than the bug it closes: under libReplacement real lib files resolve to node_modules/@typescript/lib-es2022/index.d.ts, so genuine Date, Uint8Array, and Map would be demoted to structural in any project using @typescript/lib-*. A checker-keyed MetadataDefaultLibrary registry — modelled on the existing MetadataDependency registry in the same package, registered from both program-loading entrypoints — answers it from prog.TSProgram.IsLibFile instead, the lesson cmd/ttsc-typia/transform.go already learned in #2108. IsLibFile was verified to be set membership (p.libFiles[sourceFile.Path()]), not a name test, so it is a genuinely stronger oracle. metadata_symbol_from_default_lib and its other callers are untouched.

What master already fixed

The original name-only classification was closed by edd86583f2, refined by #2239 5473622897 and #1568 c7f7939d2b. This closes the reopened residual — global-table resolution treated as proof of a runtime provider — not the original report.

Verification

Three regressions, all executing real transforms and then running the emitted validators in Node:

Regression What it pins
TestUserGlobalNativeIdentityTransform A user-authored global stays structural across is/assert/assertGuard/validate/equals/random, direct and factory forms, alias, re-export, branded intersection, union, and nested positions, plus the json/protobuf/llm schema surfaces a native classification removes entirely — while a third project where @types/node provides the bridge keeps every instanceof. 28 runtime cases.
TestDefaultLibrarySpoofNativeIdentityTransform A lib.*.d.ts-named package declaration is refused.
TestLibReplacementNativeIdentityTransform Genuine natives keep their identity under libReplacement.

The whole packages/typia/native/... tree passes locally.

Fail-before is documented rather than assumed: all three failed before #2273, and the instrumented evidence that the product code was already correct while the fixtures could not express their premise is on the #2200 and #2268 threads.

Two fixture premises corrected

Neither changes what the regression proves; both were stopping it from proving anything.

  • typia.llm.schema<File, "chatgpt">() is malformed regardless: the signature is schema<T, Config = {}>($defs), so the model string landed in Config, which must be a literal object type. The transform rejected the whole fixture, making every later assertion unreachable.
  • The runner's expected case count was 26 while its table holds 28 assertions — 11 for the validator matrix, 15 for the createIs forms, 2 near-miss name controls. That count is asserted instead of the exit code precisely so a row that stops running cannot pass as a row that ran and agreed, so a wrong count defeats the guard it exists to be.

Known residual, filed separately

metadata_symbol_from_default_lib still answers default-library provenance from the lib.*.d.ts base name for its remaining callers, including metadata_is_global_function_interface. This pull request routes only its own new predicate around that; the rest is untouched and left to its own change.

samchon added 3 commits July 21, 2026 06:29
Claim issues #2200 and #2238 together because both residuals cross shared metadata identity helpers and require one combined provenance, spelling, composition, and consumer matrix.

Constraint: Campaign pull requests cannot change versions, tags, releases, or publication state.

Rejected: Separate helper edits | independent fixes at one shared classifier would duplicate integration risk and miss cross-effects.

Confidence: high

Scope-risk: broad

Directive: Preserve issue invariants while treating every proposed metadata classification mechanism as a hypothesis until the complete matrices execute.

Tested: Independent real-transform reproductions, expanded consequence matrices, and issue critic reviews passed on ce88a88.

Not-tested: Product implementation and integration gates are intentionally pending.

Related: #2200 #2238
`metadata_symbol_is_global_type` decided native identity with
`checker.ResolveName(name, nil, SymbolFlagsType, false) == symbol`, so a
project whose only `File`/`Blob` declaration is its own `declare global`
block won that lookup. Global-table resolution proves a declaration is
global, not that a runtime provides the constructor.

The predicate now requires a runtime provider: a default-library
declaration, a declaration inside an ambient `node:buffer`/`buffer`
module, or a constructor binding that resolves into either. The third
path is load-bearing because @types/node 25 declares the bare global as
`interface Blob extends _Blob {}` plus `var Blob: typeof buffer.Blob` in
an ordinary package `.d.ts`, so only the value binding carries provenance.

The same predicate gates `Map`/`Set`, `WeakMap`/`WeakSet` through
`iterate_metadata_native.go`, and all four intersection identity sites,
so one predicate owns the whole consequence surface.

`metadata_symbol_from_default_lib` answers default-library provenance
from the `lib.*.d.ts` base name, which `libReplacement` defeats: real lib
files resolve to `node_modules/@typescript/lib-es2022/index.d.ts`. A gate
built on it would demote genuine `Date`/`Uint8Array`/`Map` to structural
in any project using `@typescript/lib-*`. The new checker-keyed
`MetadataDefaultLibrary` registry, modelled on the existing
`MetadataDependency` registry and registered from both program-loading
entrypoints, answers it from `prog.TSProgram.IsLibFile` instead, the same
lesson `cmd/ttsc-typia/transform.go` already learned in #2108. The old
helper and its other callers are left untouched.

NOT COMPILED OR EXECUTED: written under a maintainer stop on all local
builds and tests. CI is the first thing to compile or run any of it.
Neither correction changes what the regression proves; both were making
it unable to prove anything.

`typia.llm.schema<File, "chatgpt">()` is malformed regardless of this
issue: the signature is `schema<T, Config = {}>($defs)`, so the model
string landed in `Config`, which must be a literal object type. The
transform rejected the whole fixture with a diagnostic, so every
assertion after it was unreachable.

The runner's expected case count was 26 while its table contains 28
assertions: 11 for the is/assert/assertGuard/validate/equals/random
matrix, 15 for the createIs forms across direct, alias, re-export,
branded intersection, union, and nested positions, and 2 near-miss name
controls. The count is asserted rather than the exit code precisely so a
row that stops running cannot pass as a row that ran and agreed, so a
wrong count defeats the guard it exists to be.
@samchon
samchon merged commit 4f48945 into master Jul 20, 2026
8 checks passed
@samchon
samchon deleted the fix/metadata-identity-residuals branch July 20, 2026 21:49
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.

A user type is classified as a built-in native by name alone: is<File>/<Blob>/<Date>… emit instanceof and reject valid values (or throw)

1 participant