Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/typescript-parity-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ All test cases in the regression test suite (Corpus Test Suite) have been cross-
| **`node:test` / `test`** | `test`, `it`, `describe`, `suite`, `before`, `after`, `beforeEach`, `afterEach`, `mock`, `TestContext`, `SuiteContext`, subtests, TAP/spec reporting | ✅ Matches Node.js test runner core subset specification |
| **`node:dgram` / `dgram`** | `Socket` class (`bind`, `connect`, `disconnect`, `send`, `close`, `address`, `remoteAddress`, `setBroadcast`, `setTTL`, `setMulticastTTL`, `setMulticastLoopback`, `setMulticastInterface`, `addMembership`, `dropMembership`, `addSourceSpecificMembership`, `dropSourceSpecificMembership`, `setRecvBufferSize`, `setSendBufferSize`, `[Symbol.asyncDispose]`), `createSocket` | ✅ Matches Node.js dgram UDP core subset specification |
| **`node:http` & WHATWG Fetch** | `Server`, `createServer`, `IncomingMessage`, `OutgoingMessage`, `ServerResponse`, `ClientRequest`, `Agent`, `globalAgent`, `request`, `get`, `fetch`, `Request`, `Response`, `Headers`, `Response.formData`, `METHODS`, `STATUS_CODES`, `getStatusText`, `maxHeaderSize`, `validateHeaderName`, `validateHeaderValue` | ✅ Full Node.js HTTP server/client engine on `net.Server` / `net.Socket` and WHATWG Fetch |
| **WHATWG FormData** | `FormData` (`append`, `delete`, `get`, `getAll`, `has`, `set`, `forEach`, `entries`, `keys`, `values`, `[Symbol.iterator]()`), `FormDataEntryValue`, Blob/File normalization with filename override, and `Response.prototype.formData()` multipart/form-data and urlencoded parser | ✅ Full WHATWG standard FormData and Fetch multipart/urlencoded body decoder |
| **WHATWG FormData** | `FormData` (`append`, `delete`, `get`, `getAll`, `has`, `set`, `forEach`, `entries`, `keys`, `values`, `[Symbol.iterator]()`), `FormDataIterator<T>`, `FormDataEntryValue`, Blob/File normalization with filename override, and `Response.prototype.formData()` multipart/form-data and urlencoded parser | ✅ Full WHATWG standard FormData with `IterableIterator<T>` protocol, generic `for..of` and `Array.from` iterator support, and Fetch multipart/urlencoded body decoder |
| **`node:net`** | `isIP`, `isIPv4`, `isIPv6`, `Socket`, `Server`, `SocketAddress`, `createServer`, `createConnection`, `connect` | ✅ Matches Node.js Net POSIX TCP socket specification |
| **`Weak Collections, WeakRef & FinalizationRegistry`** | `WeakMap`, `WeakSet`, `WeakRef` (`.deref()`), `FinalizationRegistry` (`.register()`, `.unregister()`), `gc()`, Cycle-Aware Mark-and-Sweep Memory Management | ✅ 100% matches ECMAScript Weak Collections, weak references & finalizers |
| **`node:stream` / `stream`** | `Stream`, `Readable`, `Writable`, `Duplex`, `Transform`, `PassThrough`, `pipeline`, `finished`, `compose`, `addAbortSignal`, `getDefaultHighWaterMark`, `setDefaultHighWaterMark`, `isReadable`, `isWritable`, `isErrored`, `Readable.from`, `Readable.isDisturbed`, WebStreams interop (`fromWeb`, `toWeb`, `duplexFromWeb`, `duplexToWeb`), `promises`; `node:stream/consumers` (`buffer`, `text`, `json`, `arrayBuffer`, `blob`) | ✅ Matches Node.js Stream core subset specification |
Expand Down Expand Up @@ -383,7 +383,7 @@ Below is the detailed audit of all TypeScript/ECMAScript Abstract Syntax Tree (A
| **URL & URLSearchParams (`node:url`)** | ✅ Core APIs | `URL`, `URLSearchParams`, `Url`, `parse`, `format`, `resolve`, `fileURLToPath`, `pathToFileURL`, `urlToHttpOptions`, and iterator/property methods. Dummy `createObjectURL` and `domainToASCII`/`domainToUnicode` stubs removed. |
| **SQLite Embedded Database (`node:sqlite`)** | ✅ Full (32 / 32 APIs) | Full core subset parity: `DatabaseSync`, `StatementSync`, `Session`, `backup`, `constants` (`SQLITE_OPEN_*`, `SQLITE_CHANGESETAPPLY_*`), `exec`, `prepare`, `function`, `aggregate`, `loadExtension`, `enableLoadExtension`, `createSession`, `applyChangeset`, `all`, `get`, `run`, `iterate`, `columns`, `expandedSQL`, `sourceSQL`, `[Symbol.dispose]`. |
| **Global Identifiers & Web Globals (`node:globals`)** | ✅ Core APIs | Genuine Web & Node.js globals: `AbortController`, `AbortSignal`, `Blob`, `Buffer`, `ByteLengthQueuingStrategy`, `CompressionStream`, `CountQueuingStrategy`, `Crypto`, `CryptoKey`, `CustomEvent`, `DecompressionStream`, `Event`, `EventTarget`, `File`, `FormData`, `Headers`, `PerformanceEntry`, `PerformanceMark`, `PerformanceMeasure`, `PerformanceObserver`, `PerformanceObserverEntryList`, `PerformanceResourceTiming`, `ReadableByteStreamController`, `ReadableStream`, `ReadableStreamBYOBReader`, `ReadableStreamBYOBRequest`, `ReadableStreamDefaultController`, `ReadableStreamDefaultReader`, `Response`, `Request`, `SubtleCrypto`, `DOMException`, `TextDecoder`, `TextDecoderStream`, `TextEncoder`, `TextEncoderStream`, `TransformStream`, `TransformStreamDefaultController`, `URL`, `URLSearchParams`, `WebSocket`, `WritableStream`, `WritableStreamDefaultController`, `WritableStreamDefaultWriter`, `atob`, `btoa`, `clearImmediate`, `clearInterval`, `clearTimeout`, `queueMicrotask`, `require`, `setImmediate`, `setInterval`, `setTimeout`, `structuredClone`. Removed fake/unimplemented globals (`BroadcastChannel`, `EventSource`, `MessageChannel`, `MessageEvent`, `MessagePort`, `Navigator`, `Storage`, `WebAssembly`). |
| **WHATWG FormData** | ✅ Full Core API | WHATWG standard `FormData` implementation with `append`, `delete`, `get`, `getAll`, `has`, `set`, `forEach`, `entries`, `keys`, `values`, `[Symbol.iterator]()`, Blob-to-File normalization with filename override, and `Response.prototype.formData()` multipart/form-data and urlencoded parser. Seamlessly available globally (`new FormData()`). |
| **WHATWG FormData** | ✅ Full Core API | WHATWG standard `FormData` implementation with `append`, `delete`, `get`, `getAll`, `has`, `set`, `forEach`, `entries`, `keys`, `values`, `[Symbol.iterator]()` returning genuine `FormDataIterator<T>` conforming to ECMAScript `IterableIterator<T>` protocol, Blob-to-File normalization with filename override, and `Response.prototype.formData()` multipart/form-data and urlencoded parser. Seamlessly available globally (`new FormData()`). |
| **Virtual Machine & Sandbox (`node:vm`)** | ⚠️ Static Type Stubs / Dynamic Tier | Full TypeScript definitions and contract stubs for static compilation; dynamic runtime execution (`runInContext`, `compileFunction`, `evaluate`) deferred to `--dynamic` tier (QuickJS-ng). |
| **Readline CLI (`node:readline` & `node:readline/promises`)** | ✅ Full (30 / 30 APIs; 100.0%) | Full core subset parity: `Interface`, `createInterface`, `InterfaceConstructor`, `clearLine`, `clearScreenDown`, `cursorTo`, `moveCursor`, `emitKeypressEvents`, `[Symbol.asyncIterator]`, `[Symbol.dispose]`, promise-based `node:readline/promises` (`Interface`, `createInterface`, `Readline`), line buffering, keypress decoding, prompt, question, cursor management, and stream abort signals. |
| **DNS Resolution (`node:dns`)** | ✅ Core APIs | C runtime getaddrinfo / resolv bindings: `lookup`, `lookupService`, `resolve`, `resolve4`, `resolve6`, `resolveCname`, `resolveNs`, `resolvePtr`, `reverse`, `getDefaultResultOrder`, `setDefaultResultOrder`, `promises.*`. Hardcoded mock record resolvers (MX/TXT/SRV/SOA/CAA/NAPTR/TLSA) and dummy in-memory `Resolver`/`getServers`/`setServers` removed. |
Expand Down
290 changes: 274 additions & 16 deletions internal/lowering/builtins_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,32 +82,289 @@ func registerArrayBuiltins(m map[string]BuiltinIntrinsic) {
MinArgs: 1,
MaxArgs: 3,
Lower: func(call IntrinsicCall, intrinsic BuiltinIntrinsic) (string, ir.Type, error) {
var args []string
retType := ir.TypeNumberArray
for i, argExpr := range call.Expression.Arguments {
argVal, aType, err := call.LowerExpression(call.Path, argExpr, "", call.Function, call.Env, call.Counter, call.Shapes, call.Signatures)
if err != nil {
return "", "", err
argVal, aType, err := call.LowerExpression(call.Path, call.Expression.Arguments[0], "", call.Function, call.Env, call.Counter, call.Shapes, call.Signatures)
if err != nil {
return "", "", err
}

if isSetType(aType) {
elemType := ir.TypeUnknown
if after, ok := strings.CutPrefix(string(aType), "object:Set__"); ok {
elemType = toIRType(after)
} else if call.Expression.Arguments[0] != nil && strings.Contains(call.Expression.Arguments[0].InferredType, "<") {
inferred := strings.TrimSpace(call.Expression.Arguments[0].InferredType)
idx := strings.Index(inferred, "<")
inner := inferred[idx+1 : len(inferred)-1]
parts := splitTypeArguments(inner)
if len(parts) >= 1 {
elemType = toIRType(parts[0])
}
}
args = append(args, argVal)
if i == 0 {
if strings.HasSuffix(string(aType), "[]") || aType == ir.TypeNumberArray || aType == ir.TypeStringArray || aType == ir.TypeBoolArray || aType == ir.TypeBigIntArray {
retType = aType
} else if aType == ir.TypeString {
retType = ir.TypeStringArray
valuesRes := nextTemp(call.Counter)
valuesArrType := ir.Type(string(elemType) + "[]")
call.Function.Body = append(call.Function.Body, ir.Instruction{
Op: ir.OpCall,
Type: valuesArrType,
Result: valuesRes,
Callee: "__set.values",
Args: []string{argVal},
Span: toIRSpan(call.Path, call.Expression.Span),
})
call.Env[valuesRes] = valuesArrType
argVal = valuesRes
aType = valuesArrType
} else if isMapType(aType) {
keyType := ir.TypeString
valType := ir.TypeUnknown
if after, ok := strings.CutPrefix(string(aType), "object:Map__"); ok {
parts := strings.Split(after, "_")
if len(parts) >= 2 {
keyType = toIRType(parts[0])
valType = toIRType(strings.Join(parts[1:], "_"))
}
}
var fields []ir.Field
fields = append(fields, ir.Field{Name: "0", Type: keyType})
fields = append(fields, ir.Field{Name: "1", Type: valType})
entryShapeName := anonymousShapeName(fields)
registerAnonymousShape(entryShapeName, fields)
elemType := ir.Type("object:" + entryShapeName)
entriesRes := nextTemp(call.Counter)
entriesArrType := ir.Type(string(elemType) + "[]")
call.Function.Body = append(call.Function.Body, ir.Instruction{
Op: ir.OpCall,
Type: entriesArrType,
Result: entriesRes,
Callee: "__map.entries",
Args: []string{argVal},
Span: toIRSpan(call.Path, call.Expression.Span),
})
call.Env[entriesRes] = entriesArrType
argVal = entriesRes
aType = entriesArrType
}

shapeName := strings.TrimPrefix(string(aType), "object:")
isIter := false
if strings.HasPrefix(string(aType), "object:") {
if _, _, ok := findMethodInHierarchy(shapeName, "next", call.Signatures, classHierarchy); ok {
isIter = true
} else if _, _, ok := findMethodInHierarchy(shapeName, "Symbol.iterator", call.Signatures, classHierarchy); ok {
isIter = true
}
}
if !isIter && (strings.Contains(string(aType), "Generator") || strings.Contains(string(aType), "Iterator")) && !strings.Contains(string(aType), "MapIterator") && !strings.Contains(string(aType), "SetIterator") {
isIter = true
}

if isIter {
if fnIter, mangledIter, okIter := findMethodInHierarchy(shapeName, "Symbol.iterator", call.Signatures, classHierarchy); okIter {
iterRes := nextTemp(call.Counter)
call.Function.Body = append(call.Function.Body, ir.Instruction{
Op: ir.OpCall,
Type: fnIter.ReturnType,
Result: iterRes,
Callee: mangledIter,
Args: []string{argVal},
Span: toIRSpan(call.Path, call.Expression.Span),
})
argVal = iterRes
aType = fnIter.ReturnType
shapeName = strings.TrimPrefix(string(aType), "object:")
}

nextFn := shapeName + "_next"
targetNext, hasNext := call.Signatures[nextFn]
if !hasNext {
if fn, mangled, ok := findMethodInHierarchy(shapeName, "next", call.Signatures, classHierarchy); ok {
targetNext = fn
nextFn = mangled
hasNext = true
}
}

valType := ir.TypeNumber
resShapeName := ""
doneFieldIndex := 0
valFieldIndex := 1
if hasNext {
resShapeName = strings.TrimPrefix(string(targetNext.ReturnType), "object:")
if resShape, ok := call.Shapes[resShapeName]; ok {
for idx, f := range resShape.Fields {
if f.Name == "done" {
doneFieldIndex = idx
} else if f.Name == "value" {
valFieldIndex = idx
valType = f.Type
}
}
}
}
if (valType == ir.TypeNumber || valType == "") && call.Expression.Arguments[0] != nil {
inferred := strings.TrimSpace(call.Expression.Arguments[0].InferredType)
if idx := strings.Index(inferred, "<"); idx >= 0 && strings.HasSuffix(inferred, ">") {
inner := inferred[idx+1 : len(inferred)-1]
parts := splitTypeArguments(inner)
if len(parts) > 0 {
valType = toIRType(parts[0])
}
} else if idx := strings.Index(string(aType), "<"); idx >= 0 && strings.HasSuffix(string(aType), ">") {
inner := string(aType)[idx+1 : len(string(aType))-1]
parts := splitTypeArguments(inner)
if len(parts) > 0 {
valType = toIRType(parts[0])
}
}
}
if valType == "" {
valType = ir.TypeUnknown
}
retType := ir.Type(string(valType) + "[]")
if valType == ir.TypeString {
retType = ir.TypeStringArray
} else if valType == ir.TypeNumber {
retType = ir.TypeNumberArray
} else if valType == ir.TypeBool {
retType = ir.TypeBoolArray
} else if valType == ir.TypeBigInt {
retType = ir.TypeBigIntArray
}

arrRes := call.Result
if arrRes == "" {
arrRes = nextTemp(call.Counter)
}
call.Function.Body = append(call.Function.Body, ir.Instruction{
Op: ir.OpArray,
Type: retType,
Result: arrRes,
Args: nil,
Span: toIRSpan(call.Path, call.Expression.Span),
})
call.Env[arrRes] = retType

var mapVal string
if len(call.Expression.Arguments) >= 2 {
mv, _, err := call.LowerExpression(call.Path, call.Expression.Arguments[1], "", call.Function, call.Env, call.Counter, call.Shapes, call.Signatures)
if err != nil {
return "", "", err
}
mapVal = mv
}

condFunc := ir.Function{Name: "cond", ReturnType: ir.TypeBool}
condConst := nextTemp(call.Counter)
condFunc.Body = append(condFunc.Body, ir.Instruction{
Op: ir.OpConst, Type: ir.TypeBool, Result: condConst, Value: "true", Span: toIRSpan(call.Path, call.Expression.Span),
})

bodyBranch := ir.Function{Name: "body", ReturnType: call.Function.ReturnType}
resVal := nextTemp(call.Counter)
retNextType := ir.Type("object:" + resShapeName)
if hasNext {
retNextType = targetNext.ReturnType
}
bodyBranch.Body = append(bodyBranch.Body, ir.Instruction{
Op: ir.OpCall,
Type: retNextType,
Result: resVal,
Callee: nextFn,
Args: []string{argVal},
Span: toIRSpan(call.Path, call.Expression.Span),
})

doneVal := nextTemp(call.Counter)
bodyBranch.Body = append(bodyBranch.Body, ir.Instruction{
Op: ir.OpFieldGet,
Type: ir.TypeBool,
Result: doneVal,
Callee: resShapeName,
Field: "done",
FieldIndex: doneFieldIndex,
Args: []string{resVal},
Span: toIRSpan(call.Path, call.Expression.Span),
})

bodyBranch.Body = append(bodyBranch.Body, ir.Instruction{
Op: ir.OpIf,
Type: ir.TypeVoid,
Args: []string{doneVal},
Then: []ir.Instruction{
{Op: ir.OpBreak, Type: ir.TypeVoid, Span: toIRSpan(call.Path, call.Expression.Span)},
},
Span: toIRSpan(call.Path, call.Expression.Span),
})

valVal := nextTemp(call.Counter)
bodyBranch.Body = append(bodyBranch.Body, ir.Instruction{
Op: ir.OpFieldGet,
Type: valType,
Result: valVal,
Callee: resShapeName,
Field: "value",
FieldIndex: valFieldIndex,
Args: []string{resVal},
Span: toIRSpan(call.Path, call.Expression.Span),
})

pushVal := valVal
if mapVal != "" {
mappedVal := nextTemp(call.Counter)
bodyBranch.Body = append(bodyBranch.Body, ir.Instruction{
Op: ir.OpCall,
Type: valType,
Result: mappedVal,
Callee: mapVal,
Args: []string{valVal},
Span: toIRSpan(call.Path, call.Expression.Span),
})
pushVal = mappedVal
}

pushRes := nextTemp(call.Counter)
bodyBranch.Body = append(bodyBranch.Body, ir.Instruction{
Op: ir.OpCall,
Type: ir.TypeNumber,
Result: pushRes,
Callee: "__array.push",
Args: []string{arrRes, pushVal},
Span: toIRSpan(call.Path, call.Expression.Span),
})

call.Function.Body = append(call.Function.Body, ir.Instruction{
Op: ir.OpWhile,
Type: ir.TypeVoid,
Args: []string{condConst},
Cond: condFunc.Body,
Body: bodyBranch.Body,
Span: toIRSpan(call.Path, call.Expression.Span),
})

return arrRes, retType, nil
}

// Fast-path: Array or String slice
retType := ir.TypeNumberArray
if strings.HasSuffix(string(aType), "[]") || aType == ir.TypeNumberArray || aType == ir.TypeStringArray || aType == ir.TypeBoolArray || aType == ir.TypeBigIntArray {
retType = aType
} else if aType == ir.TypeString {
retType = ir.TypeStringArray
}
if len(args) >= 2 {

if len(call.Expression.Arguments) >= 2 {
rawArr := nextTemp(call.Counter)
call.Function.Body = append(call.Function.Body, ir.Instruction{
Op: ir.OpCall,
Type: retType,
Result: rawArr,
Callee: "__array.from",
Args: []string{args[0]},
Args: []string{argVal},
Span: toIRSpan(call.Path, call.Expression.Span),
})
mapVal, _, err := call.LowerExpression(call.Path, call.Expression.Arguments[1], "", call.Function, call.Env, call.Counter, call.Shapes, call.Signatures)
if err != nil {
return "", "", err
}
result := call.Result
if result == "" {
result = nextTemp(call.Counter)
Expand All @@ -117,11 +374,12 @@ func registerArrayBuiltins(m map[string]BuiltinIntrinsic) {
Type: retType,
Result: result,
Callee: "__array.map",
Args: []string{rawArr, args[1]},
Args: []string{rawArr, mapVal},
Span: toIRSpan(call.Path, call.Expression.Span),
})
return result, retType, nil
}

result := call.Result
if result == "" {
result = nextTemp(call.Counter)
Expand All @@ -131,7 +389,7 @@ func registerArrayBuiltins(m map[string]BuiltinIntrinsic) {
Type: retType,
Result: result,
Callee: "__array.from",
Args: args,
Args: []string{argVal},
Span: toIRSpan(call.Path, call.Expression.Span),
})
return result, retType, nil
Expand Down
1 change: 1 addition & 0 deletions internal/lowering/generics.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var (
// SpecializeGenerics monomorphizes generic functions and classes based on
// concrete type arguments at call sites, instantiations, and type annotations.
func SpecializeGenerics(program frontend.Program) (frontend.Program, error) {
initializeTypeAliases(program)
initializeClassIdentities(program)
buildClassHierarchy(program)
genericFuncs := map[string]typescriptgo.SyntaxStatement{}
Expand Down
Loading
Loading