From e72b3d7a0ff916af5998ec415e3e056d01cd69a1 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 4 Jun 2026 00:46:18 +0000 Subject: [PATCH 1/8] Resolve the audited TODO(port)/TODO(b2)/PORT NOTE backlog Drain the Rust port's marker backlog: every audited TODO(port)/TODO(b2-*) marker and PORT NOTE comment was individually resolved - fixed, deleted as stale, untagged to a plain comment, or split into a tracked follow-up work order. ~495 real code fixes, each citing the Zig sibling for intended semantics; the 101 markers that remain are claimed by written work orders for changes too risky to land in a bulk branch (GC rooting, arena lifetime threading, cross-thread ownership restructures). Verified: full debug build + smoke tests on linux-x64, cross-target cargo check on windows/macos/linux x64/aarch64. --- Cargo.lock | 3 + src/analytics/lib.rs | 142 +++-- src/analytics/schema.rs | 17 +- src/api/lib.rs | 11 +- src/ast/Cargo.toml | 1 + src/ast/ast_result.rs | 8 +- src/ast/b.rs | 21 +- src/ast/char_freq.rs | 15 +- src/ast/e.rs | 28 +- src/ast/expr.rs | 29 +- src/ast/g.rs | 2 +- src/ast/import_record.rs | 9 +- src/ast/known_global.rs | 2 +- src/ast/lib.rs | 205 ++++-- src/ast/loader.rs | 4 +- src/ast/new_store.rs | 48 +- src/ast/nodes.rs | 14 +- src/ast/op.rs | 2 - src/ast/runtime.rs | 20 +- src/ast/scope.rs | 2 +- src/ast/server_component_boundary.rs | 8 +- src/ast/stmt.rs | 21 +- src/ast/symbol.rs | 16 +- src/ast/target.rs | 5 +- src/ast/ts.rs | 20 +- src/base64/lib.rs | 26 +- src/brotli/lib.rs | 45 +- src/brotli_sys/brotli_c.rs | 5 +- src/bun.js.rs | 1 - src/bun_alloc/MaxHeapAllocator.rs | 5 +- src/bun_alloc/NullableAllocator.rs | 8 +- src/bun_alloc/fallback/z.rs | 2 - src/bun_alloc/heap_breakdown.rs | 14 +- src/bun_alloc/lib.rs | 30 +- src/bun_alloc/maybe_owned.rs | 2 +- src/bun_alloc/memory.rs | 4 +- src/bun_bin/lib.rs | 2 +- src/bun_core/Global.rs | 7 +- src/bun_core/Progress.rs | 53 +- src/bun_core/bounded_array.rs | 32 +- src/bun_core/deprecated.rs | 68 +- src/bun_core/env.rs | 4 +- src/bun_core/env_var.rs | 47 +- src/bun_core/external_shared.rs | 4 +- src/bun_core/fmt.rs | 107 ++-- src/bun_core/lib.rs | 18 +- src/bun_core/output.rs | 150 +++-- src/bun_core/result.rs | 18 +- src/bun_core/string/MutableString.rs | 35 +- src/bun_core/string/SmolStr.rs | 8 +- src/bun_core/string/StringBuilder.rs | 27 +- src/bun_core/string/StringJoiner.rs | 11 +- src/bun_core/string/escapeRegExp.rs | 8 +- src/bun_core/string/identifier.rs | 16 +- src/bun_core/string/immutable.rs | 74 +-- .../string/immutable/exact_size_matcher.rs | 9 +- src/bun_core/string/immutable/unicode.rs | 22 +- src/bun_core/string/mod.rs | 44 +- src/bun_core/string/wtf.rs | 4 +- src/bun_core/tty.rs | 1 - src/bun_core/util.rs | 118 +++- src/bun_core/wtf.rs | 2 +- src/bundler/AstBuilder.rs | 30 +- src/bundler/BundleThread.rs | 43 +- src/bundler/Cargo.toml | 5 +- src/bundler/Chunk.rs | 83 ++- src/bundler/DeferredBatchTask.rs | 6 +- src/bundler/Graph.rs | 10 +- src/bundler/HTMLScanner.rs | 8 +- src/bundler/IndexStringMap.rs | 3 - src/bundler/LinkerContext.rs | 133 ++-- src/bundler/LinkerGraph.rs | 42 +- src/bundler/OutputFile.rs | 17 +- src/bundler/ParseTask.rs | 102 +-- src/bundler/ServerComponentParseTask.rs | 6 +- src/bundler/ThreadPool.rs | 51 +- src/bundler/analyze_transpiled_module.rs | 9 +- src/bundler/barrel_imports.rs | 30 +- src/bundler/bundle_v2.rs | 171 +++-- src/bundler/bundled_ast.rs | 11 +- src/bundler/cache.rs | 23 +- src/bundler/defines.rs | 11 +- src/bundler/entry_points.rs | 44 +- src/bundler/lib.rs | 2 +- src/bundler/linker.rs | 46 +- src/bundler/linker_context/MetafileBuilder.rs | 10 +- .../linker_context/OutputFileListBuilder.rs | 4 +- .../linker_context/StaticRouteVisitor.rs | 6 +- src/bundler/linker_context/computeChunks.rs | 36 +- .../computeCrossChunkDependencies.rs | 28 +- .../linker_context/convertStmtsForChunk.rs | 6 +- .../convertStmtsForChunkForDevServer.rs | 2 +- src/bundler/linker_context/doStep5.rs | 29 +- .../findAllImportedPartsInJSOrder.rs | 7 +- .../findImportedFilesInCSSOrder.rs | 38 +- .../generateChunksInParallel.rs | 40 +- .../generateCodeForFileInChunkJS.rs | 21 +- .../generateCodeForLazyExport.rs | 12 +- .../generateCompileResultForCssChunk.rs | 1 - .../generateCompileResultForHtmlChunk.rs | 10 +- .../generateCompileResultForJSChunk.rs | 27 +- .../linker_context/postProcessCSSChunk.rs | 6 +- .../linker_context/postProcessHTMLChunk.rs | 7 +- .../linker_context/postProcessJSChunk.rs | 37 +- .../linker_context/prepareCssAstsForChunk.rs | 16 +- .../linker_context/renameSymbolsInChunk.rs | 17 +- .../linker_context/scanImportsAndExports.rs | 39 +- .../linker_context/writeOutputFilesToDisk.rs | 18 +- src/bundler/options.rs | 111 ++-- src/bundler/transpiler.rs | 147 +++-- src/bundler_jsc/analyze_jsc.rs | 8 +- src/bunfig/arguments.rs | 11 +- src/bunfig/bunfig.rs | 8 +- src/cares_sys/c_ares.rs | 66 +- src/clap/args.rs | 32 +- src/clap/comptime.rs | 6 +- src/clap/lib.rs | 40 +- src/clap/streaming.rs | 51 +- src/collections/StaticHashMap.rs | 122 +++- src/collections/array_hash_map.rs | 8 +- src/collections/array_list.rs | 89 ++- src/collections/bit_set.rs | 62 +- src/collections/comptime_string_map.rs | 53 +- src/collections/hive_array.rs | 16 +- src/collections/lib.rs | 8 +- src/collections/linear_fifo.rs | 221 +++++-- src/collections/pool.rs | 48 +- src/collections/string_map.rs | 12 +- src/crash_handler/CPUFeatures.rs | 3 - src/crash_handler/handle_oom.rs | 2 +- src/crash_handler/lib.rs | 86 +-- src/csrf/lib.rs | 11 +- src/css/context.rs | 12 +- src/css/css_modules.rs | 17 +- src/css/css_parser.rs | 195 +++--- src/css/declaration.rs | 47 +- src/css/dependencies.rs | 26 +- src/css/error.rs | 2 - src/css/generics.rs | 22 +- src/css/lib.rs | 8 +- src/css/media_query.rs | 43 +- src/css/printer.rs | 43 +- src/css/properties/align.rs | 35 +- src/css/properties/animation.rs | 29 +- src/css/properties/background.rs | 28 +- src/css/properties/border.rs | 38 +- src/css/properties/border_image.rs | 49 +- src/css/properties/border_radius.rs | 20 +- src/css/properties/box_shadow.rs | 15 +- src/css/properties/css_modules.rs | 6 +- src/css/properties/custom.rs | 19 +- src/css/properties/display.rs | 7 +- src/css/properties/flex.rs | 33 +- src/css/properties/font.rs | 64 +- src/css/properties/grid.rs | 28 +- src/css/properties/margin_padding.rs | 22 +- src/css/properties/masking.rs | 30 +- src/css/properties/mod.rs | 19 +- src/css/properties/overflow.rs | 2 - src/css/properties/prefix_handler.rs | 16 +- src/css/properties/properties_generated.rs | 26 +- src/css/properties/properties_impl.rs | 15 +- src/css/properties/shape.rs | 6 +- src/css/properties/size.rs | 29 +- src/css/properties/text.rs | 8 +- src/css/properties/transform.rs | 59 +- src/css/properties/transition.rs | 31 +- src/css/properties/ui.rs | 12 +- src/css/rules/container.rs | 19 +- src/css/rules/counter_style.rs | 5 +- src/css/rules/document.rs | 1 - src/css/rules/font_face.rs | 19 +- src/css/rules/font_palette_values.rs | 10 +- src/css/rules/import.rs | 8 +- src/css/rules/keyframes.rs | 21 +- src/css/rules/layer.rs | 12 +- src/css/rules/media.rs | 7 +- src/css/rules/mod.rs | 73 ++- src/css/rules/namespace.rs | 6 +- src/css/rules/nesting.rs | 1 - src/css/rules/page.rs | 22 +- src/css/rules/property.rs | 24 +- src/css/rules/scope.rs | 10 +- src/css/rules/starting_style.rs | 1 - src/css/rules/style.rs | 35 +- src/css/rules/supports.rs | 56 +- src/css/rules/unknown.rs | 5 +- src/css/rules/viewport.rs | 6 +- src/css/selectors/builder.rs | 21 +- src/css/selectors/parser.rs | 188 ++++-- src/css/selectors/selector.rs | 19 +- src/css/small_list.rs | 1 - src/css/sourcemap.rs | 8 +- src/css/targets.rs | 17 +- src/css/values/alpha.rs | 11 +- src/css/values/angle.rs | 1 - src/css/values/calc.rs | 36 +- src/css/values/color.rs | 31 +- src/css/values/easing.rs | 6 +- src/css/values/gradient.rs | 2 - src/css/values/ident.rs | 57 +- src/css/values/image.rs | 30 +- src/css/values/length.rs | 17 +- src/css/values/percentage.rs | 34 +- src/css/values/position.rs | 7 +- src/css/values/ratio.rs | 1 - src/css/values/rect.rs | 7 +- src/css/values/size.rs | 19 +- src/css/values/syntax.rs | 25 +- src/css/values/time.rs | 7 - src/css/values/url.rs | 14 +- src/css_jsc/color_js.rs | 22 +- src/css_jsc/css_internals.rs | 17 +- src/dns/lib.rs | 23 +- src/dotenv/env_loader.rs | 63 +- src/errno/lib.rs | 32 +- src/event_loop/AnyEventLoop.rs | 31 +- src/event_loop/AnyTaskWithExtraContext.rs | 28 +- src/event_loop/AutoFlusher.rs | 12 +- src/event_loop/ConcurrentTask.rs | 20 +- src/event_loop/DeferredTaskQueue.rs | 19 +- src/event_loop/EventLoopTimer.rs | 11 +- src/event_loop/ManagedTask.rs | 6 +- src/event_loop/MiniEventLoop.rs | 46 +- src/event_loop/SpawnSyncEventLoop.rs | 18 +- src/event_loop/lib.rs | 6 +- src/exe_format/elf.rs | 11 +- src/exe_format/macho.rs | 12 +- src/exe_format/pe.rs | 5 +- src/glob/GlobWalker.rs | 69 +- src/glob/lib.rs | 5 +- src/glob/matcher.rs | 6 +- src/highway/lib.rs | 4 +- src/http/AsyncHTTP.rs | 67 +- src/http/CertificateInfo.rs | 6 +- src/http/Decompressor.rs | 20 +- src/http/H2Client.rs | 14 +- src/http/H3Client.rs | 7 +- src/http/HTTPCertError.rs | 13 +- src/http/HTTPContext.rs | 48 +- src/http/HTTPRequestBody.rs | 14 +- src/http/HTTPThread.rs | 45 +- src/http/HeaderBuilder.rs | 9 +- src/http/HeaderValueIterator.rs | 7 +- src/http/Headers.rs | 12 +- src/http/InternalState.rs | 37 +- src/http/ProxyTunnel.rs | 16 +- src/http/SendFile.rs | 4 +- src/http/Signals.rs | 4 +- src/http/ThreadSafeStreamBuffer.rs | 7 +- src/http/h2_client/ClientSession.rs | 33 +- src/http/h2_client/PendingConnect.rs | 4 +- src/http/h2_client/Stream.rs | 6 +- src/http/h2_client/dispatch.rs | 13 +- src/http/h2_client/encode.rs | 8 +- src/http/h3_client/AltSvc.rs | 28 +- src/http/h3_client/ClientSession.rs | 6 +- src/http/h3_client/encode.rs | 2 +- src/http/lib.rs | 98 ++- src/http/lshpack.rs | 46 +- src/http/ssl_config.rs | 9 +- src/http/websocket.rs | 3 - src/http/zlib.rs | 6 +- src/http_jsc/headers_jsc.rs | 16 +- src/http_jsc/method_jsc.rs | 3 +- src/http_jsc/websocket_client.rs | 81 ++- src/http_jsc/websocket_client/CppWebSocket.rs | 3 +- .../websocket_client/WebSocketDeflate.rs | 20 +- .../websocket_client/WebSocketProxy.rs | 4 +- .../websocket_client/WebSocketProxyTunnel.rs | 10 +- .../WebSocketUpgradeClient.rs | 119 ++-- src/http_types/ETag.rs | 18 +- src/http_types/Method.rs | 5 - src/http_types/MimeType.rs | 37 +- src/http_types/URLPath.rs | 5 +- src/http_types/h2.rs | 13 +- src/ini/lib.rs | 46 +- src/install/ConfigVersion.rs | 2 +- src/install/NetworkTask.rs | 69 +- src/install/PackageInstall.rs | 56 +- src/install/PackageInstaller.rs | 84 ++- src/install/PackageManager.rs | 64 +- .../PackageManager/CommandLineArguments.rs | 11 +- .../PackageManager/PackageJSONEditor.rs | 11 +- .../PackageManagerDirectories.rs | 29 +- .../PackageManager/PackageManagerEnqueue.rs | 85 +-- .../PackageManager/PackageManagerLifecycle.rs | 18 +- .../PackageManager/PackageManagerOptions.rs | 35 +- .../PackageManagerResolution.rs | 7 +- .../PackageManager/PopulateManifestCache.rs | 12 +- src/install/PackageManager/ProgressStrings.rs | 6 +- src/install/PackageManager/UpdateRequest.rs | 11 +- .../WorkspacePackageJSONCache.rs | 7 +- .../PackageManager/install_with_manager.rs | 37 +- src/install/PackageManager/patchPackage.rs | 21 +- .../PackageManager/processDependencyList.rs | 24 +- src/install/PackageManager/runTasks.rs | 49 +- .../PackageManager/security_scanner.rs | 25 +- .../updatePackageJSONAndInstall.rs | 24 +- src/install/PackageManagerTask.rs | 13 +- src/install/PackageManifestMap.rs | 17 +- src/install/TarballStream.rs | 5 +- src/install/auto_installer.rs | 2 +- src/install/bin.rs | 47 +- src/install/dependency.rs | 13 +- src/install/extract_tarball.rs | 4 +- src/install/hoisted_install.rs | 25 +- src/install/hosted_git_info.rs | 81 +-- src/install/integrity.rs | 5 +- src/install/isolated_install.rs | 50 +- src/install/isolated_install/FileCloner.rs | 8 +- src/install/isolated_install/FileCopier.rs | 10 +- src/install/isolated_install/Hardlinker.rs | 32 +- src/install/isolated_install/Installer.rs | 41 +- src/install/isolated_install/Store.rs | 23 +- src/install/isolated_install/Symlinker.rs | 6 +- src/install/lib.rs | 17 +- src/install/lifecycle_script_runner.rs | 30 +- src/install/lockfile.rs | 144 +++-- src/install/lockfile/Buffers.rs | 63 +- src/install/lockfile/CatalogMap.rs | 61 +- src/install/lockfile/OverrideMap.rs | 32 +- src/install/lockfile/Package.rs | 70 +-- src/install/lockfile/Package/Meta.rs | 7 +- src/install/lockfile/Package/Scripts.rs | 15 +- src/install/lockfile/Package/WorkspaceMap.rs | 13 +- src/install/lockfile/Tree.rs | 45 +- src/install/lockfile/bun.lock.rs | 33 +- src/install/lockfile/bun.lockb.rs | 60 +- .../lockfile_json_stringify_for_debugging.rs | 9 +- src/install/lockfile/printer/Yarn.rs | 25 +- src/install/lockfile/printer/tree_printer.rs | 17 +- src/install/migration.rs | 154 ++--- src/install/npm.rs | 191 ++++-- src/install/padding_checker.rs | 33 +- src/install/patch_install.rs | 55 +- src/install/pnpm.rs | 28 +- src/install/postinstall_optimizer.rs | 56 +- src/install/repository.rs | 31 +- src/install/resolution.rs | 11 +- src/install/resolvers/folder_resolver.rs | 21 +- src/install/windows-shim/BinLinkingShim.rs | 34 +- src/install/windows-shim/bun_shim_impl.rs | 63 +- src/install/yarn.rs | 108 ++-- src/install_jsc/dependency_jsc.rs | 4 +- src/install_jsc/hosted_git_info_jsc.rs | 8 +- src/install_jsc/ini_jsc.rs | 8 +- src/install_jsc/install_binding.rs | 4 +- src/install_jsc/lib.rs | 15 +- src/install_jsc/npm_jsc.rs | 8 +- src/install_jsc/update_request_jsc.rs | 5 +- src/install_types/NodeLinker.rs | 10 +- src/install_types/resolver_hooks.rs | 2 +- src/io/ParentDeathWatchdog.rs | 13 +- src/io/PipeReader.rs | 59 +- src/io/PipeWriter.rs | 56 +- src/io/heap.rs | 2 +- src/io/keep_alive.rs | 5 +- src/io/lib.rs | 47 +- src/io/openForWriting.rs | 4 +- src/io/pipes.rs | 2 +- src/io/posix_event_loop.rs | 28 +- src/io/source.rs | 2 +- src/io/windows_event_loop.rs | 20 +- src/js_parser/fold.rs | 8 +- src/js_parser/lexer.rs | 83 ++- src/js_parser/lib.rs | 371 +---------- src/js_parser/lower/lower_decorators.rs | 6 +- src/js_parser/lower/lower_esm_exports_hmr.rs | 26 +- src/js_parser/p.rs | 149 +++-- src/js_parser/parse/mod.rs | 42 +- src/js_parser/parse/parse_entry.rs | 78 ++- src/js_parser/parse/parse_fn.rs | 15 +- src/js_parser/parse/parse_import_export.rs | 6 +- src/js_parser/parse/parse_jsx.rs | 7 +- src/js_parser/parse/parse_prefix.rs | 19 +- src/js_parser/parse/parse_property.rs | 6 +- src/js_parser/parse/parse_skip_typescript.rs | 19 +- src/js_parser/parse/parse_stmt.rs | 13 +- src/js_parser/parse/parse_suffix.rs | 16 +- src/js_parser/parse/parse_typescript.rs | 9 +- src/js_parser/parser.rs | 57 +- src/js_parser/repl_transforms.rs | 2 +- src/js_parser/scan/scan_imports.rs | 31 +- src/js_parser/scan/scan_side_effects.rs | 14 +- src/js_parser/scan/scan_symbols.rs | 16 +- src/js_parser/typescript.rs | 11 +- src/js_parser/visit/mod.rs | 77 +-- src/js_parser/visit/visit_binary.rs | 20 +- src/js_parser/visit/visit_expr.rs | 37 +- src/js_parser/visit/visit_stmt.rs | 33 +- src/js_parser_jsc/Macro.rs | 55 +- src/js_parser_jsc/expr_jsc.rs | 56 +- src/js_printer/lib.rs | 528 ++++++---------- src/js_printer/renamer.rs | 63 +- src/jsc/AbortSignal.rs | 23 +- src/jsc/AsyncModule.rs | 92 +-- src/jsc/BuildMessage.rs | 1 - src/jsc/BunCPUProfiler.rs | 8 +- src/jsc/BunHeapProfiler.rs | 11 +- src/jsc/CachedBytecode.rs | 4 +- src/jsc/CallFrame.rs | 2 - src/jsc/CommonAbortReason.rs | 2 - src/jsc/CommonStrings.rs | 13 +- src/jsc/ConcurrentPromiseTask.rs | 3 +- src/jsc/ConsoleObject.rs | 56 +- src/jsc/Counters.rs | 13 +- src/jsc/CppTask.rs | 1 - src/jsc/CustomGetterSetter.rs | 1 - src/jsc/DOMFormData.rs | 10 +- src/jsc/DOMURL.rs | 6 +- src/jsc/Debugger.rs | 36 +- src/jsc/DeprecatedStrong.rs | 35 +- src/jsc/EventType.rs | 2 +- src/jsc/Exception.rs | 1 - src/jsc/FFI.rs | 7 +- src/jsc/FetchHeaders.rs | 9 +- src/jsc/GarbageCollectionController.rs | 9 +- src/jsc/GetterSetter.rs | 1 - src/jsc/HTTPServerAgent.rs | 4 +- src/jsc/JSArray.rs | 3 +- src/jsc/JSArrayIterator.rs | 1 - src/jsc/JSBigInt.rs | 2 - src/jsc/JSCell.rs | 28 +- src/jsc/JSErrorCode.rs | 2 +- src/jsc/JSFunction.rs | 2 - src/jsc/JSGlobalObject.rs | 37 +- src/jsc/JSMap.rs | 2 - src/jsc/JSModuleLoader.rs | 2 - src/jsc/JSObject.rs | 23 +- src/jsc/JSPromise.rs | 15 +- src/jsc/JSPropertyIterator.rs | 9 +- src/jsc/JSRef.rs | 20 +- src/jsc/JSRuntimeType.rs | 6 +- src/jsc/JSSecrets.rs | 2 - src/jsc/JSString.rs | 2 - src/jsc/JSType.rs | 8 +- src/jsc/JSUint8Array.rs | 5 +- src/jsc/JSValue.rs | 11 +- src/jsc/MarkedArgumentBuffer.rs | 5 +- src/jsc/ModuleLoader.rs | 38 +- src/jsc/NodeModuleModule.rs | 2 - src/jsc/PluginRunner.rs | 4 - src/jsc/PosixSignalHandle.rs | 3 +- src/jsc/RefString.rs | 2 - src/jsc/RegularExpression.rs | 3 - src/jsc/ResolveMessage.rs | 11 +- src/jsc/ResolvedSource.rs | 8 +- src/jsc/RuntimeTranspilerCache.rs | 58 +- src/jsc/RuntimeTranspilerStore.rs | 75 +-- src/jsc/SavedSourceMap.rs | 29 +- src/jsc/SourceProvider.rs | 1 - src/jsc/StringBuilder.rs | 6 +- src/jsc/Strong.rs | 18 +- src/jsc/SystemError.rs | 3 +- src/jsc/Task.rs | 12 +- src/jsc/TextCodec.rs | 3 +- src/jsc/URL.rs | 13 +- src/jsc/URLSearchParams.rs | 6 +- src/jsc/VM.rs | 8 +- src/jsc/VirtualMachine.rs | 247 ++++---- src/jsc/WTF.rs | 13 +- src/jsc/Weak.rs | 4 +- src/jsc/WorkTask.rs | 9 +- src/jsc/ZigErrorType.rs | 2 +- src/jsc/ZigException.rs | 46 +- src/jsc/ZigStackFrameCode.rs | 12 +- src/jsc/ZigStackFramePosition.rs | 7 +- src/jsc/ZigStackTrace.rs | 8 +- src/jsc/ZigString.rs | 23 +- src/jsc/array_buffer.rs | 26 +- src/jsc/bindgen.rs | 47 +- src/jsc/bindgen_test.rs | 4 +- src/jsc/btjs.rs | 13 +- src/jsc/bun_string_jsc.rs | 21 +- src/jsc/codegen.rs | 10 +- src/jsc/comptime_string_map_jsc.rs | 10 +- src/jsc/event_loop.rs | 50 +- src/jsc/generated.rs | 16 +- src/jsc/host_fn.rs | 51 +- src/jsc/hot_reloader.rs | 97 +-- src/jsc/ipc.rs | 50 +- src/jsc/javascript_core_c_api.rs | 10 +- src/jsc/lib.rs | 12 +- src/jsc/rare_data.rs | 63 +- src/jsc/resolver_jsc.rs | 7 +- src/jsc/uuid.rs | 3 +- src/jsc/virtual_machine_exports.rs | 7 +- src/jsc/web_worker.rs | 45 +- src/jsc/webcore_types.rs | 6 +- src/libarchive/lib.rs | 54 +- src/libuv_sys/libuv.rs | 6 +- src/lolhtml_sys/lol_html.rs | 28 +- src/md/ansi_renderer.rs | 34 +- src/md/blocks.rs | 17 +- src/md/containers.rs | 11 +- src/md/entity.rs | 1 - src/md/helpers.rs | 38 +- src/md/html_renderer.rs | 8 +- src/md/inlines.rs | 14 +- src/md/links.rs | 11 +- src/md/parser.rs | 26 +- src/md/ref_defs.rs | 22 +- src/md/types.rs | 14 +- src/md/unicode.rs | 3 - src/mimalloc_sys/mimalloc.rs | 8 +- src/options_types/code_coverage_options.rs | 5 +- src/options_types/compile_target.rs | 16 +- src/options_types/context.rs | 27 +- src/options_types/jsx.rs | 2 +- src/parsers/json.rs | 106 ++-- src/parsers/json_lexer.rs | 22 +- src/parsers/toml.rs | 22 +- src/parsers/toml/lexer.rs | 19 +- src/parsers/yaml.rs | 281 ++++----- src/patch/lib.rs | 25 +- src/patch_jsc/testing.rs | 7 +- src/paths/EnvPath.rs | 7 +- src/paths/Path.rs | 83 ++- src/paths/lib.rs | 7 +- src/paths/path_buffer_pool.rs | 8 +- src/paths/resolve_path.rs | 81 ++- src/paths/string_paths.rs | 14 +- src/perf/generated_perf_trace_events.rs | 41 +- src/perf/lib.rs | 13 +- src/perf/system_timer.rs | 10 +- src/perf/tracy.rs | 105 +++- src/picohttp/lib.rs | 9 +- src/platform/darwin.rs | 1 - src/ptr/CowSlice.rs | 153 ++++- src/ptr/lib.rs | 2 - src/ptr/meta.rs | 103 --- src/ptr/owned.rs | 32 +- src/ptr/raw_ref_count.rs | 8 +- src/ptr/ref_count.rs | 141 +++-- src/ptr/shared.rs | 64 +- src/ptr/tagged_pointer.rs | 27 +- src/ptr/weak_ptr.rs | 7 +- src/resolve_builtins/HardcodedModule.rs | 53 +- src/resolver/data_url.rs | 137 +++- src/resolver/dir_info.rs | 20 +- src/resolver/fs.rs | 166 ++--- src/resolver/lib.rs | 60 +- src/resolver/node_fallbacks.rs | 34 +- src/resolver/package_json.rs | 179 ++---- src/resolver/resolver.rs | 263 +++++--- src/resolver/result.rs | 23 +- src/resolver/tsconfig_json.rs | 14 +- src/router/lib.rs | 90 ++- src/runtime/allocators/LinuxMemFdAllocator.rs | 20 +- src/runtime/api.rs | 2 - src/runtime/api/Archive.rs | 20 +- src/runtime/api/BunObject.rs | 92 +-- src/runtime/api/HashObject.rs | 9 +- src/runtime/api/JSBundler.rs | 18 +- src/runtime/api/JSON5Object.rs | 23 +- src/runtime/api/JSTranspiler.rs | 71 ++- src/runtime/api/MarkdownObject.rs | 55 +- src/runtime/api/NativePromiseContext.rs | 3 +- src/runtime/api/TOMLObject.rs | 1 - src/runtime/api/UnsafeObject.rs | 19 +- src/runtime/api/YAMLObject.rs | 10 +- src/runtime/api/bun/SSLContextCache.rs | 16 +- src/runtime/api/bun/SecureContext.rs | 7 +- src/runtime/api/bun/Terminal.rs | 39 +- src/runtime/api/bun/h2_frame_parser.rs | 59 +- src/runtime/api/bun/js_bun_spawn_bindings.rs | 47 +- src/runtime/api/bun/spawn/stdio.rs | 9 +- src/runtime/api/bun/subprocess.rs | 11 +- src/runtime/api/bun/subprocess/Readable.rs | 15 +- .../bun/subprocess/SubprocessPipeReader.rs | 18 +- src/runtime/api/bun/subprocess/Writable.rs | 13 +- src/runtime/api/bun/x509.rs | 1 - src/runtime/api/crash_handler_jsc.rs | 1 - src/runtime/api/cron.rs | 49 +- src/runtime/api/cron_parser.rs | 7 +- src/runtime/api/filesystem_router.rs | 59 +- src/runtime/api/glob.rs | 33 +- src/runtime/api/html_rewriter.rs | 80 +-- src/runtime/api/js_bundle_completion_task.rs | 60 +- src/runtime/api/output_file_jsc.rs | 11 +- src/runtime/api/standalone_graph_jsc.rs | 4 +- src/runtime/bake/DevServer.rs | 301 +++++---- .../bake/DevServer/ErrorReportRequest.rs | 21 +- src/runtime/bake/DevServer/HmrSocket.rs | 10 +- .../bake/DevServer/IncrementalGraph.rs | 2 +- src/runtime/bake/DevServer/memory_cost.rs | 90 ++- src/runtime/bake/FrameworkRouter.rs | 62 +- src/runtime/bake/bake_body.rs | 60 +- src/runtime/bake/dev_server/assets.rs | 27 +- .../bake/dev_server/incremental_graph.rs | 30 +- src/runtime/bake/dev_server/mod.rs | 43 +- src/runtime/bake/dev_server/packed_map.rs | 9 +- src/runtime/bake/dev_server/route_bundle.rs | 2 +- .../bake/dev_server/source_map_store.rs | 66 +- src/runtime/bake/mod.rs | 19 +- src/runtime/bake/production.rs | 52 +- src/runtime/cli/Arguments.rs | 10 +- src/runtime/cli/add_command.rs | 1 - src/runtime/cli/audit_command.rs | 16 +- src/runtime/cli/build_command.rs | 52 +- src/runtime/cli/bunx_command.rs | 54 +- src/runtime/cli/ci_info.rs | 6 +- src/runtime/cli/colon_list_type.rs | 24 +- .../cli/create/SourceFileProjectGenerator.rs | 11 +- src/runtime/cli/create_command.rs | 174 ++--- src/runtime/cli/discord_command.rs | 2 +- src/runtime/cli/exec_command.rs | 7 +- src/runtime/cli/filter_arg.rs | 64 +- src/runtime/cli/filter_run.rs | 38 +- src/runtime/cli/fuzzilli_command.rs | 2 +- src/runtime/cli/init_command.rs | 17 +- src/runtime/cli/install_command.rs | 7 +- .../cli/install_completions_command.rs | 9 +- src/runtime/cli/link_command.rs | 4 +- src/runtime/cli/mod.rs | 122 ++-- src/runtime/cli/multi_run.rs | 23 +- src/runtime/cli/open.rs | 34 +- src/runtime/cli/outdated_command.rs | 8 +- src/runtime/cli/pack_command.rs | 59 +- src/runtime/cli/package_manager_command.rs | 34 +- src/runtime/cli/pm_pkg_command.rs | 4 +- src/runtime/cli/pm_trusted_command.rs | 14 +- src/runtime/cli/pm_update_package_json.rs | 6 +- src/runtime/cli/pm_version_command.rs | 9 +- src/runtime/cli/pm_view_command.rs | 9 +- src/runtime/cli/pm_why_command.rs | 3 +- src/runtime/cli/publish_command.rs | 33 +- src/runtime/cli/remove_command.rs | 1 - src/runtime/cli/repl.rs | 32 +- src/runtime/cli/repl_command.rs | 59 +- src/runtime/cli/run_command.rs | 79 ++- src/runtime/cli/scan_command.rs | 4 +- src/runtime/cli/shell_completions.rs | 4 +- src/runtime/cli/test/ChangedFilesFilter.rs | 15 +- src/runtime/cli/test/Scanner.rs | 28 +- src/runtime/cli/test/parallel/Channel.rs | 25 +- src/runtime/cli/test/parallel/Coordinator.rs | 39 +- src/runtime/cli/test/parallel/Worker.rs | 58 +- src/runtime/cli/test/parallel/aggregate.rs | 19 +- src/runtime/cli/test/parallel/runner.rs | 37 +- src/runtime/cli/test_command.rs | 116 ++-- src/runtime/cli/unlink_command.rs | 6 +- src/runtime/cli/update_command.rs | 2 - src/runtime/cli/update_interactive_command.rs | 59 +- src/runtime/cli/upgrade_command.rs | 54 +- src/runtime/cli/why_command.rs | 17 +- src/runtime/crypto/CryptoHasher.rs | 19 +- src/runtime/crypto/EVP.rs | 12 +- src/runtime/crypto/HMAC.rs | 1 - src/runtime/crypto/PBKDF2.rs | 4 +- src/runtime/crypto/PasswordObject.rs | 11 +- src/runtime/crypto/mod.rs | 6 +- src/runtime/dispatch.rs | 17 +- src/runtime/dispatch_js2native.rs | 2 +- src/runtime/dns_jsc/cares_jsc.rs | 24 +- src/runtime/dns_jsc/dns.rs | 89 ++- src/runtime/dns_jsc/options_jsc.rs | 13 +- src/runtime/ffi/FFIObject.rs | 45 +- src/runtime/ffi/abi_type.rs | 5 - src/runtime/ffi/ffi_body.rs | 40 +- src/runtime/ffi/mod.rs | 178 +----- src/runtime/image/Image.rs | 36 +- src/runtime/image/backend_coregraphics.rs | 4 - src/runtime/image/backend_wic.rs | 14 +- src/runtime/image/codec_gif.rs | 2 +- src/runtime/image/codec_jpeg.rs | 1 - src/runtime/image/codec_png.rs | 1 - src/runtime/image/codec_webp.rs | 2 - src/runtime/image/codecs.rs | 24 +- src/runtime/image/quantize.rs | 12 +- src/runtime/image/thumbhash.rs | 6 - src/runtime/jsc_hooks.rs | 457 ++++++++------ src/runtime/napi/napi_body.rs | 34 +- src/runtime/node.rs | 44 +- src/runtime/node/Stat.rs | 23 +- src/runtime/node/StatFS.rs | 7 +- src/runtime/node/assert/myers_diff.rs | 30 +- src/runtime/node/buffer.rs | 7 +- src/runtime/node/dir_iterator.rs | 5 +- src/runtime/node/fs_events.rs | 14 +- src/runtime/node/net/BlockList.rs | 10 +- src/runtime/node/node_assert.rs | 6 +- src/runtime/node/node_cluster_binding.rs | 8 +- src/runtime/node/node_crypto_binding.rs | 11 +- src/runtime/node/node_error_binding.rs | 4 +- src/runtime/node/node_fs.rs | 200 +++--- src/runtime/node/node_fs_binding.rs | 4 +- src/runtime/node/node_fs_constant.rs | 9 +- src/runtime/node/node_fs_stat_watcher.rs | 3 +- src/runtime/node/node_fs_watcher.rs | 28 +- src/runtime/node/node_net_binding.rs | 1 - src/runtime/node/node_os.rs | 16 +- src/runtime/node/node_process.rs | 16 +- src/runtime/node/node_util_binding.rs | 2 +- src/runtime/node/node_zlib_binding.rs | 6 +- src/runtime/node/path.rs | 40 +- src/runtime/node/path_watcher.rs | 9 +- src/runtime/node/time_like.rs | 6 +- src/runtime/node/types.rs | 45 +- src/runtime/node/util/validators.rs | 6 +- src/runtime/node/win_watcher.rs | 22 +- src/runtime/node/zlib/NativeBrotli.rs | 18 +- src/runtime/node/zlib/NativeZlib.rs | 9 +- src/runtime/node/zlib/NativeZstd.rs | 2 +- src/runtime/server/AnyRequestContext.rs | 6 +- src/runtime/server/FileResponseStream.rs | 2 +- src/runtime/server/FileRoute.rs | 12 +- src/runtime/server/HTMLBundle.rs | 27 +- src/runtime/server/NodeHTTPResponse.rs | 18 +- src/runtime/server/RangeRequest.rs | 4 +- src/runtime/server/RequestContext.rs | 137 ++-- src/runtime/server/ServerConfig.rs | 39 +- src/runtime/server/ServerWebSocket.rs | 17 +- src/runtime/server/StaticRoute.rs | 4 +- src/runtime/server/WebSocketServerContext.rs | 60 +- src/runtime/server/mod.rs | 63 +- src/runtime/server/server_body.rs | 61 +- src/runtime/shell/Builtin.rs | 16 +- src/runtime/shell/EnvMap.rs | 8 +- src/runtime/shell/EnvStr.rs | 25 +- src/runtime/shell/IOReader.rs | 8 +- src/runtime/shell/IOWriter.rs | 22 +- src/runtime/shell/ParsedShellScript.rs | 12 +- src/runtime/shell/RefCountedStr.rs | 13 +- src/runtime/shell/Yield.rs | 4 +- src/runtime/shell/builtin/cat.rs | 11 +- src/runtime/shell/builtin/cp.rs | 11 +- src/runtime/shell/builtin/exit.rs | 7 +- src/runtime/shell/builtin/ls.rs | 3 +- src/runtime/shell/builtin/mkdir.rs | 13 +- src/runtime/shell/builtin/mv.rs | 2 +- src/runtime/shell/builtin/rm.rs | 22 +- src/runtime/shell/builtin/seq.rs | 11 +- src/runtime/shell/builtin/yes.rs | 6 +- src/runtime/shell/dispatch_tasks.rs | 4 + src/runtime/shell/interpreter.rs | 55 +- src/runtime/shell/shell_body.rs | 29 +- src/runtime/shell/states/Assigns.rs | 2 +- src/runtime/shell/states/Async.rs | 87 ++- src/runtime/shell/states/Base.rs | 7 +- src/runtime/shell/states/Cmd.rs | 20 +- src/runtime/shell/states/CondExpr.rs | 153 ++++- src/runtime/shell/states/If.rs | 5 +- src/runtime/shell/states/Script.rs | 2 - src/runtime/shell/states/Subshell.rs | 7 +- src/runtime/shell/subproc.rs | 40 +- src/runtime/socket/Handlers.rs | 19 +- src/runtime/socket/Listener.rs | 72 ++- src/runtime/socket/SSLConfig.rs | 3 +- src/runtime/socket/SocketAddress.rs | 72 ++- src/runtime/socket/UpgradedDuplex.rs | 9 +- src/runtime/socket/WindowsNamedPipe.rs | 20 +- src/runtime/socket/WindowsNamedPipeContext.rs | 7 +- src/runtime/socket/mod.rs | 2 +- src/runtime/socket/socket_body.rs | 51 +- src/runtime/socket/tls_socket_functions.rs | 35 +- src/runtime/socket/udp_socket.rs | 9 +- src/runtime/socket/uws_dispatch.rs | 4 +- src/runtime/socket/uws_handlers.rs | 4 +- src/runtime/socket/uws_jsc.rs | 5 +- src/runtime/test_runner/Collection.rs | 2 - src/runtime/test_runner/Execution.rs | 19 +- src/runtime/test_runner/Order.rs | 24 +- src/runtime/test_runner/ScopeFunctions.rs | 95 +-- src/runtime/test_runner/bun_test.rs | 69 +- src/runtime/test_runner/debug.rs | 9 +- .../test_runner/diff/diff_match_patch.rs | 57 +- src/runtime/test_runner/diff/printDiff.rs | 8 +- src/runtime/test_runner/expect.rs | 160 +++-- src/runtime/test_runner/expect/toBe.rs | 8 +- .../test_runner/expect/toBeArrayOfSize.rs | 5 +- src/runtime/test_runner/expect/toBeEmpty.rs | 3 +- .../test_runner/expect/toBeEmptyObject.rs | 3 +- .../test_runner/expect/toBeInstanceOf.rs | 12 +- src/runtime/test_runner/expect/toBeObject.rs | 7 +- src/runtime/test_runner/expect/toBeOneOf.rs | 10 +- src/runtime/test_runner/expect/toBeTypeOf.rs | 5 +- .../test_runner/expect/toBeValidDate.rs | 3 +- src/runtime/test_runner/expect/toBeWithin.rs | 5 +- src/runtime/test_runner/expect/toContain.rs | 5 +- .../test_runner/expect/toContainEqual.rs | 7 +- .../expect/toEqualIgnoringWhitespace.rs | 8 +- .../test_runner/expect/toHaveBeenCalled.rs | 2 - .../expect/toHaveBeenCalledOnce.rs | 1 - .../expect/toHaveBeenCalledTimes.rs | 37 +- .../expect/toHaveBeenCalledWith.rs | 7 +- .../expect/toHaveBeenLastCalledWith.rs | 1 - .../expect/toHaveBeenNthCalledWith.rs | 1 - .../expect/toHaveLastReturnedWith.rs | 4 +- .../test_runner/expect/toHaveLength.rs | 1 - .../expect/toHaveNthReturnedWith.rs | 2 - .../test_runner/expect/toHaveProperty.rs | 5 +- .../test_runner/expect/toHaveReturnedWith.rs | 13 +- .../test_runner/expect/toIncludeRepeated.rs | 10 +- src/runtime/test_runner/expect/toMatch.rs | 4 +- .../expect/toMatchInlineSnapshot.rs | 3 +- .../test_runner/expect/toMatchObject.rs | 1 - .../test_runner/expect/toMatchSnapshot.rs | 3 +- src/runtime/test_runner/expect/toSatisfy.rs | 5 +- src/runtime/test_runner/expect/toThrow.rs | 3 +- .../toThrowErrorMatchingInlineSnapshot.rs | 5 +- .../expect/toThrowErrorMatchingSnapshot.rs | 3 +- src/runtime/test_runner/harness/fixtures.rs | 2 - src/runtime/test_runner/harness/recover.rs | 47 +- src/runtime/test_runner/jest.rs | 35 +- src/runtime/test_runner/pretty_format.rs | 52 +- src/runtime/test_runner/snapshot.rs | 49 +- src/runtime/test_runner/timers/FakeTimers.rs | 12 +- src/runtime/timer/EventLoopDelayMonitor.rs | 2 +- src/runtime/timer/Timer.rs | 24 +- src/runtime/timer/WTFTimer.rs | 10 +- src/runtime/timer/mod.rs | 38 +- src/runtime/timer/timer_object_internals.rs | 59 +- src/runtime/valkey_jsc/ValkeyCommand.rs | 19 +- src/runtime/valkey_jsc/js_valkey.rs | 51 +- src/runtime/valkey_jsc/js_valkey_functions.rs | 14 +- src/runtime/valkey_jsc/valkey.rs | 93 ++- src/runtime/webcore.rs | 24 +- src/runtime/webcore/ArrayBufferSink.rs | 28 +- src/runtime/webcore/BakeResponse.rs | 4 +- src/runtime/webcore/Blob.rs | 90 ++- src/runtime/webcore/Body.rs | 63 +- src/runtime/webcore/ByteBlobLoader.rs | 8 +- src/runtime/webcore/ByteStream.rs | 20 +- src/runtime/webcore/CookieMap.rs | 1 - src/runtime/webcore/Crypto.rs | 12 +- src/runtime/webcore/FileReader.rs | 28 +- src/runtime/webcore/FileSink.rs | 37 +- src/runtime/webcore/FormData.rs | 82 ++- src/runtime/webcore/ObjectURLRegistry.rs | 6 +- src/runtime/webcore/ReadableStream.rs | 28 +- src/runtime/webcore/Request.rs | 24 +- src/runtime/webcore/Response.rs | 36 +- src/runtime/webcore/ResumableSink.rs | 12 +- src/runtime/webcore/S3Client.rs | 12 +- src/runtime/webcore/S3File.rs | 17 +- src/runtime/webcore/Sink.rs | 77 ++- src/runtime/webcore/TextDecoder.rs | 9 +- .../webcore/TextEncoderStreamEncoder.rs | 6 +- src/runtime/webcore/blob/Store.rs | 19 +- src/runtime/webcore/blob/copy_file.rs | 68 +- src/runtime/webcore/blob/read_file.rs | 24 +- src/runtime/webcore/blob/write_file.rs | 28 +- src/runtime/webcore/encoding.rs | 16 +- src/runtime/webcore/fetch.rs | 84 +-- src/runtime/webcore/fetch/FetchTasklet.rs | 65 +- src/runtime/webcore/s3/client.rs | 27 +- src/runtime/webcore/s3/credentials_jsc.rs | 4 +- src/runtime/webcore/s3/download_stream.rs | 37 +- src/runtime/webcore/s3/error_jsc.rs | 3 +- src/runtime/webcore/s3/list_objects.rs | 54 +- src/runtime/webcore/s3/multipart.rs | 37 +- src/runtime/webcore/s3/simple_request.rs | 53 +- src/runtime/webcore/streams.rs | 114 ++-- src/runtime/webcore/wasm_streaming.rs | 6 +- src/runtime/webview/ChromeProcess.rs | 12 +- src/s3_signing/credentials.rs | 38 +- src/s3_signing/error.rs | 17 +- src/safety/CriticalSection.rs | 2 +- src/safety/alloc.rs | 4 +- src/semver/SemverQuery.rs | 9 +- src/semver/SemverRange.rs | 11 +- src/semver/Version.rs | 6 +- src/semver/lib.rs | 18 +- src/sha_hmac/sha.rs | 4 +- src/shell_parser/braces.rs | 81 ++- src/shell_parser/parse.rs | 90 +-- src/sourcemap/Chunk.rs | 10 +- src/sourcemap/InternalSourceMap.rs | 25 +- src/sourcemap/LineOffsetTable.rs | 5 +- src/sourcemap/Mapping.rs | 11 +- src/sourcemap/ParsedSourceMap.rs | 4 +- src/sourcemap/lib.rs | 24 +- src/sourcemap_jsc/CodeCoverage.rs | 20 +- src/sourcemap_jsc/JSSourceMap.rs | 37 +- src/sourcemap_jsc/internal_jsc.rs | 3 +- src/sourcemap_jsc/source_provider.rs | 6 +- src/spawn/process.rs | 37 +- src/spawn/static_pipe_writer.rs | 18 +- src/spawn_sys/posix_spawn.rs | 65 +- src/spawn_sys/spawn_process.rs | 20 +- src/sql/mysql/AuthMethod.rs | 1 - src/sql/mysql/Capabilities.rs | 18 +- src/sql/mysql/MySQLRequest.rs | 2 - src/sql/mysql/MySQLTypes.rs | 15 +- src/sql/mysql/StatusFlags.rs | 2 +- src/sql/mysql/protocol/AnyMySQLError.rs | 1 + src/sql/mysql/protocol/Auth.rs | 17 +- src/sql/mysql/protocol/AuthSwitchRequest.rs | 3 +- src/sql/mysql/protocol/AuthSwitchResponse.rs | 1 - src/sql/mysql/protocol/CharacterSet.rs | 592 ++++++++++-------- src/sql/mysql/protocol/ColumnDefinition41.rs | 39 +- src/sql/mysql/protocol/EOFPacket.rs | 8 +- src/sql/mysql/protocol/ErrorPacket.rs | 15 +- src/sql/mysql/protocol/HandshakeResponse41.rs | 8 +- src/sql/mysql/protocol/HandshakeV10.rs | 10 +- src/sql/mysql/protocol/LocalInfileRequest.rs | 12 +- src/sql/mysql/protocol/NewReader.rs | 20 +- src/sql/mysql/protocol/OKPacket.rs | 6 +- src/sql/mysql/protocol/PacketType.rs | 7 +- src/sql/mysql/protocol/PreparedStatement.rs | 24 +- src/sql/mysql/protocol/Query.rs | 28 +- src/sql/mysql/protocol/ResultSetHeader.rs | 10 +- src/sql/mysql/protocol/SSLRequest.rs | 6 +- src/sql/mysql/protocol/StmtPrepareOKPacket.rs | 8 +- src/sql/postgres/PostgresProtocol.rs | 3 +- src/sql/postgres/PostgresTypes.rs | 7 +- src/sql/postgres/protocol/ArrayList.rs | 1 - src/sql/postgres/protocol/Authentication.rs | 34 +- src/sql/postgres/protocol/BackendKeyData.rs | 8 +- src/sql/postgres/protocol/Close.rs | 8 +- src/sql/postgres/protocol/CommandComplete.rs | 5 +- src/sql/postgres/protocol/CopyData.rs | 13 +- src/sql/postgres/protocol/CopyFail.rs | 9 +- src/sql/postgres/protocol/CopyOutResponse.rs | 7 +- src/sql/postgres/protocol/DecoderWrap.rs | 13 +- src/sql/postgres/protocol/Describe.rs | 6 +- src/sql/postgres/protocol/ErrorResponse.rs | 10 +- src/sql/postgres/protocol/Execute.rs | 6 +- src/sql/postgres/protocol/FieldDescription.rs | 1 - src/sql/postgres/protocol/FieldMessage.rs | 4 +- src/sql/postgres/protocol/FieldType.rs | 7 +- .../protocol/NegotiateProtocolVersion.rs | 6 +- src/sql/postgres/protocol/NewReader.rs | 17 +- src/sql/postgres/protocol/NewWriter.rs | 3 +- src/sql/postgres/protocol/NoticeResponse.rs | 6 +- .../postgres/protocol/NotificationResponse.rs | 16 +- .../postgres/protocol/ParameterDescription.rs | 11 +- src/sql/postgres/protocol/ParameterStatus.rs | 8 +- src/sql/postgres/protocol/Parse.rs | 10 +- src/sql/postgres/protocol/PasswordMessage.rs | 6 +- src/sql/postgres/protocol/ReadyForQuery.rs | 8 +- src/sql/postgres/protocol/RowDescription.rs | 8 +- .../postgres/protocol/SASLInitialResponse.rs | 4 +- src/sql/postgres/protocol/SASLResponse.rs | 6 +- src/sql/postgres/protocol/StackReader.rs | 8 +- src/sql/postgres/protocol/StartupMessage.rs | 6 +- .../protocol/TransactionStatusIndicator.rs | 7 +- src/sql/postgres/types/Tag.rs | 6 +- src/sql/shared/Data.rs | 11 +- src/sql/shared/SQLQueryResultMode.rs | 4 - src/sql_jsc/Cargo.toml | 1 + src/sql_jsc/jsc.rs | 6 - src/sql_jsc/mysql/JSMySQLConnection.rs | 26 +- src/sql_jsc/mysql/JSMySQLQuery.rs | 32 +- src/sql_jsc/mysql/MySQLConnection.rs | 85 +-- src/sql_jsc/mysql/MySQLContext.rs | 6 +- src/sql_jsc/mysql/MySQLQuery.rs | 30 +- src/sql_jsc/mysql/MySQLRequestQueue.rs | 12 +- src/sql_jsc/mysql/MySQLStatement.rs | 24 +- src/sql_jsc/mysql/MySQLValue.rs | 20 +- .../mysql/protocol/DecodeBinaryValue.rs | 5 +- src/sql_jsc/mysql/protocol/ResultSet.rs | 6 +- src/sql_jsc/mysql/protocol/Signature.rs | 23 +- src/sql_jsc/postgres/AuthenticationState.rs | 5 +- src/sql_jsc/postgres/DataCell.rs | 21 +- src/sql_jsc/postgres/PostgresSQLConnection.rs | 137 ++-- src/sql_jsc/postgres/PostgresSQLContext.rs | 8 +- src/sql_jsc/postgres/PostgresSQLQuery.rs | 17 +- src/sql_jsc/postgres/PostgresSQLStatement.rs | 12 +- src/sql_jsc/postgres/SASL.rs | 20 +- src/sql_jsc/postgres/Signature.rs | 24 +- src/sql_jsc/postgres/error_jsc.rs | 7 +- .../postgres/protocol/error_response_jsc.rs | 3 +- .../postgres/protocol/notice_response_jsc.rs | 7 +- src/sql_jsc/postgres/types/PostgresString.rs | 10 +- src/sql_jsc/postgres/types/bytea.rs | 3 +- src/sql_jsc/postgres/types/json.rs | 9 +- src/sql_jsc/postgres/types/tag_jsc.rs | 3 +- src/sql_jsc/shared/CachedStructure.rs | 14 +- src/sql_jsc/shared/ObjectIterator.rs | 11 +- src/sql_jsc/shared/SQLDataCell.rs | 33 +- src/standalone_graph/StandaloneModuleGraph.rs | 116 ++-- src/sys/Error.rs | 19 +- src/sys/SignalCode.rs | 5 +- src/sys/copy_file.rs | 23 +- src/sys/coreutils_error_map.rs | 14 +- src/sys/dir.rs | 4 +- src/sys/fd.rs | 2 +- src/sys/file.rs | 4 +- src/sys/lib.rs | 54 +- src/sys/libuv_error_map.rs | 9 +- src/sys/sys_uv.rs | 10 +- src/sys/tmp.rs | 3 +- src/sys/walker_skippable.rs | 12 +- src/sys/windows/mod.rs | 10 +- src/sys_jsc/error_jsc.rs | 6 +- src/sys_jsc/fd_jsc.rs | 2 - src/sys_jsc/signal_code_jsc.rs | 4 +- src/tcc_sys/tcc.rs | 20 +- src/threading/Condition.rs | 10 +- src/threading/Futex.rs | 8 +- src/threading/Mutex.rs | 4 +- src/threading/ThreadPool.rs | 29 +- src/threading/WaitGroup.rs | 4 +- src/threading/channel.rs | 18 +- src/threading/guarded.rs | 13 +- src/threading/unbounded_queue.rs | 2 - src/threading/work_pool.rs | 3 - src/url/lib.rs | 44 +- src/uws/lib.rs | 19 +- src/uws_sys/App.rs | 29 +- src/uws_sys/BodyReaderMixin.rs | 13 +- src/uws_sys/ListenSocket.rs | 1 - src/uws_sys/Loop.rs | 74 ++- src/uws_sys/Request.rs | 6 +- src/uws_sys/Response.rs | 1 - src/uws_sys/SocketGroup.rs | 9 +- src/uws_sys/Timer.rs | 6 +- src/uws_sys/WebSocket.rs | 32 +- src/uws_sys/lib.rs | 2 +- src/uws_sys/quic/Header.rs | 9 +- src/uws_sys/socket.rs | 9 +- src/uws_sys/us_socket_t.rs | 2 - src/watcher/INotifyWatcher.rs | 30 +- src/watcher/KEventWatcher.rs | 5 +- src/watcher/Watcher.rs | 57 +- src/watcher/WatcherTrace.rs | 32 +- src/watcher/WindowsWatcher.rs | 28 +- src/watcher/lib.rs | 17 +- src/which/lib.rs | 26 +- src/windows_sys/externs.rs | 8 +- src/zlib/lib.rs | 28 +- src/zstd/lib.rs | 21 +- 1025 files changed, 13998 insertions(+), 14705 deletions(-) delete mode 100644 src/ptr/meta.rs diff --git a/Cargo.lock b/Cargo.lock index bc3e11e9b2b8..49654906c7e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,6 +204,7 @@ dependencies = [ "bun_core", "bun_dispatch", "bun_paths", + "bun_perf", "bun_ptr", "bun_sys", "bun_wyhash", @@ -356,6 +357,7 @@ dependencies = [ "bstr", "bumpalo", "bun_alloc", + "bun_analytics", "bun_ast", "bun_base64", "bun_collections", @@ -2041,6 +2043,7 @@ version = "0.0.0" dependencies = [ "bitflags", "bstr", + "bun_analytics", "bun_base64", "bun_boringssl", "bun_boringssl_sys", diff --git a/src/analytics/lib.rs b/src/analytics/lib.rs index 4af975b761d4..aff4e35b9ca5 100644 --- a/src/analytics/lib.rs +++ b/src/analytics/lib.rs @@ -52,7 +52,7 @@ pub fn is_enabled() -> bool { TriState::No => false, TriState::Unknown => { let detected = 'detect: { - // PORT NOTE: `env_var::*.get()` returns `Option` in + // Note (port): `env_var::*.get()` returns `Option` in // the Rust port even when a default exists; `DO_NOT_TRACK` has // `default: false` so `.unwrap_or(false)` matches Zig semantics. if env_var::DO_NOT_TRACK.get().unwrap_or(false) { @@ -79,7 +79,7 @@ pub fn is_enabled() -> bool { /// This answers, "What parts of bun are people actually using?" /// -/// PORT NOTE: In Zig this is a `struct` used purely as a namespace of `pub var` +/// Note (port): In Zig this is a `struct` used purely as a namespace of `pub var` /// decls, iterated via `@typeInfo` reflection. Rust has no decl reflection, so /// the feature list is declared once via `define_features!` and that macro /// generates the statics, `PACKED_FEATURES_LIST`, `PackedFeatures`, @@ -87,7 +87,7 @@ pub fn is_enabled() -> bool { pub mod features { use super::*; - // PORT NOTE (cyclebreak): the Zig original is + // Note (port, cyclebreak): the Zig original is // `EnumSet(bun.jsc.ModuleLoader.HardcodedModule)`. That enum lives in // `bun_resolve_builtins` (T5) and pulling it here would create a forward // dep (analytics is T1). The only operations we need are `insert` and @@ -99,15 +99,35 @@ pub mod features { // insert — fine for ≤~80 entries written once each at module-load time. pub(crate) static BUILTIN_MODULES: bun_core::Mutex> = bun_core::Mutex::new(std::collections::BTreeSet::new()); - // PORT NOTE: Zig used a plain mutable global; wrapped in a Mutex here + + /// Record a builtin-module load (Zig: `Features.builtin_modules.insert(hardcoded)`). + pub fn insert_builtin_module(name: &'static str) { + BUILTIN_MODULES.lock().insert(name); + } + // Note (port): Zig used a plain mutable global; wrapped in a Mutex here // because the set is not a single atomic word. macro_rules! define_features { - ( $( $(#[$doc:meta])* $idx:literal => ($ident:ident, $name:literal) ),* $(,)? ) => { + // Storage for one feature counter. Entries tagged `core = IDENT` alias + // the tier-0 `bun_core::Global::features::IDENT` static (the MOVE_DOWN + // set written by low-tier crates: dotenv/install/css/todo_panic!/...) + // so the crash-report/analytics readers below observe those writes — + // a fresh static here would be a split brain that stays forever 0. + // Entries with an `export_name` attribute must NOT be `core`-tagged: + // the exported symbol has to be the single canonical definition here. + (@storage $(#[$doc:meta])* $ident:ident) => { + $(#[$doc])* + #[allow(non_upper_case_globals)] + pub static $ident: AtomicUsize = AtomicUsize::new(0); + }; + (@storage $(#[$doc:meta])* $ident:ident, $core:ident) => { + $(#[$doc])* + #[allow(non_upper_case_globals)] + pub use ::bun_core::Global::features::$core as $ident; + }; + ( $( $(#[$doc:meta])* $idx:literal => ($ident:ident, $name:literal $(, core = $core:ident)?) ),* $(,)? ) => { $( - $(#[$doc])* - #[allow(non_upper_case_globals)] - pub static $ident: AtomicUsize = AtomicUsize::new(0); + define_features! { @storage $(#[$doc])* $ident $(, $core)? } )* // Zig: `validateFeatureName(decl.name)` per entry at comptime. @@ -123,7 +143,7 @@ pub mod features { // Zig: `pub const PackedFeatures = @Type(.{ .@"struct" = .{ .layout = .@"packed", .backing_integer = u64, ... } })` // All fields are `bool` → bitflags over u64. - // PORT NOTE: nightly `${index()}` (macro_metavar_expr) is unavailable + // Note (port): nightly `${index()}` (macro_metavar_expr) is unavailable // on stable, so each feature carries an explicit `$idx` literal at the // call site. The dense-index assertion below catches gaps/duplicates. ::bitflags::bitflags! { @@ -214,73 +234,73 @@ pub mod features { }; } - // PORT NOTE: Zig identifiers `@"Bun.stderr"` etc. cannot be Rust idents; + // Note (port): Zig identifiers `@"Bun.stderr"` etc. cannot be Rust idents; // renamed to `bun_stderr` etc. The string literal preserves the original // name for output / `PACKED_FEATURES_LIST` (matches `@tagName` semantics). // The leading integer is the bit index in `PackedFeatures` (must be dense // 0..N — asserted at compile time inside the macro). define_features! { - 0 => (bun_stderr, "Bun.stderr"), - 1 => (bun_stdin, "Bun.stdin"), - 2 => (bun_stdout, "Bun.stdout"), - 3 => (web_socket, "WebSocket"), - 4 => (abort_signal, "abort_signal"), - 5 => (binlinks, "binlinks"), - 6 => (bunfig, "bunfig"), - 7 => (define, "define"), - 8 => (dotenv, "dotenv"), - 9 => (debugger, "debugger"), - 10 => (external, "external"), - 11 => (extracted_packages, "extracted_packages"), - 12 => (fetch, "fetch"), - 13 => (git_dependencies, "git_dependencies"), - 14 => (html_rewriter, "html_rewriter"), + 0 => (bun_stderr, "Bun.stderr", core = BUN_STDERR), + 1 => (bun_stdin, "Bun.stdin", core = BUN_STDIN), + 2 => (bun_stdout, "Bun.stdout", core = BUN_STDOUT), + 3 => (web_socket, "WebSocket", core = WEBSOCKET), + 4 => (abort_signal, "abort_signal", core = ABORT_SIGNAL), + 5 => (binlinks, "binlinks", core = BINLINKS), + 6 => (bunfig, "bunfig", core = BUNFIG), + 7 => (define, "define", core = DEFINE), + 8 => (dotenv, "dotenv", core = DOTENV), + 9 => (debugger, "debugger", core = DEBUGGER), + 10 => (external, "external", core = EXTERNAL), + 11 => (extracted_packages, "extracted_packages", core = EXTRACTED_PACKAGES), + 12 => (fetch, "fetch", core = FETCH), + 13 => (git_dependencies, "git_dependencies", core = GIT_DEPENDENCIES), + 14 => (html_rewriter, "html_rewriter", core = HTML_REWRITER), /// TCP server from `Bun.listen` - 15 => (tcp_server, "tcp_server"), + 15 => (tcp_server, "tcp_server", core = TCP_SERVER), /// TLS server from `Bun.listen` - 16 => (tls_server, "tls_server"), - 17 => (http_server, "http_server"), - 18 => (https_server, "https_server"), - 19 => (http_client_proxy, "http_client_proxy"), + 16 => (tls_server, "tls_server", core = TLS_SERVER), + 17 => (http_server, "http_server", core = HTTP_SERVER), + 18 => (https_server, "https_server", core = HTTPS_SERVER), + 19 => (http_client_proxy, "http_client_proxy", core = HTTP_CLIENT_PROXY), /// Set right before JSC::initialize is called - 20 => (jsc, "jsc"), + 20 => (jsc, "jsc", core = JSC), /// Set when bake.DevServer is initialized - 21 => (dev_server, "dev_server"), - 22 => (lifecycle_scripts, "lifecycle_scripts"), - 23 => (loaders, "loaders"), - 24 => (lockfile_migration_from_package_lock, "lockfile_migration_from_package_lock"), - 25 => (text_lockfile, "text_lockfile"), - 26 => (isolated_bun_install, "isolated_bun_install"), - 27 => (hoisted_bun_install, "hoisted_bun_install"), - 28 => (macros, "macros"), - 29 => (no_avx2, "no_avx2"), - 30 => (no_avx, "no_avx"), - 31 => (shell, "shell"), - 32 => (spawn, "spawn"), - 33 => (standalone_executable, "standalone_executable"), - 34 => (standalone_shell, "standalone_shell"), + 21 => (dev_server, "dev_server", core = DEV_SERVER), + 22 => (lifecycle_scripts, "lifecycle_scripts", core = LIFECYCLE_SCRIPTS), + 23 => (loaders, "loaders", core = LOADERS), + 24 => (lockfile_migration_from_package_lock, "lockfile_migration_from_package_lock", core = LOCKFILE_MIGRATION_FROM_PACKAGE_LOCK), + 25 => (text_lockfile, "text_lockfile", core = TEXT_LOCKFILE), + 26 => (isolated_bun_install, "isolated_bun_install", core = ISOLATED_BUN_INSTALL), + 27 => (hoisted_bun_install, "hoisted_bun_install", core = HOISTED_BUN_INSTALL), + 28 => (macros, "macros", core = MACROS), + 29 => (no_avx2, "no_avx2", core = NO_AVX2), + 30 => (no_avx, "no_avx", core = NO_AVX), + 31 => (shell, "shell", core = SHELL), + 32 => (spawn, "spawn", core = SPAWN), + 33 => (standalone_executable, "standalone_executable", core = STANDALONE_EXECUTABLE), + 34 => (standalone_shell, "standalone_shell", core = STANDALONE_SHELL), /// Set when invoking a todo panic - 35 => (todo_panic, "todo_panic"), - 36 => (transpiler_cache, "transpiler_cache"), - 37 => (tsconfig, "tsconfig"), - 38 => (tsconfig_paths, "tsconfig_paths"), - 39 => (virtual_modules, "virtual_modules"), - 40 => (workers_spawned, "workers_spawned"), - 41 => (workers_terminated, "workers_terminated"), + 35 => (todo_panic, "todo_panic", core = TODO_PANIC), + 36 => (transpiler_cache, "transpiler_cache", core = TRANSPILER_CACHE), + 37 => (tsconfig, "tsconfig", core = TSCONFIG), + 38 => (tsconfig_paths, "tsconfig_paths", core = TSCONFIG_PATHS), + 39 => (virtual_modules, "virtual_modules", core = VIRTUAL_MODULES), + 40 => (workers_spawned, "workers_spawned", core = WORKERS_SPAWNED), + 41 => (workers_terminated, "workers_terminated", core = WORKERS_TERMINATED), #[unsafe(export_name = "Bun__napi_module_register_count")] 42 => (napi_module_register, "napi_module_register"), #[unsafe(export_name = "Bun__process_dlopen_count")] 43 => (process_dlopen, "process_dlopen"), 44 => (postgres_connections, "postgres_connections"), 45 => (s3, "s3"), - 46 => (valkey, "valkey"), + 46 => (valkey, "valkey", core = VALKEY), 47 => (csrf_verify, "csrf_verify"), 48 => (csrf_generate, "csrf_generate"), 49 => (unsupported_uv_function, "unsupported_uv_function"), - 50 => (exited, "exited"), - 51 => (yarn_migration, "yarn_migration"), - 52 => (pnpm_migration, "pnpm_migration"), - 53 => (yaml_parse, "yaml_parse"), + 50 => (exited, "exited", core = EXITED), + 51 => (yarn_migration, "yarn_migration", core = YARN_MIGRATION), + 52 => (pnpm_migration, "pnpm_migration", core = PNPM_MIGRATION), + 53 => (yaml_parse, "yaml_parse", core = YAML_PARSE), 54 => (cpu_profile, "cpu_profile"), #[unsafe(export_name = "Bun__Feature__heap_snapshot")] 55 => (heap_snapshot, "heap_snapshot"), @@ -291,7 +311,7 @@ pub mod features { } // Zig: `comptime { @export(&napi_module_register, .{ .name = "Bun__napi_module_register_count" }); ... }` - // PORT NOTE: C++ declares these as `extern "C" size_t Bun__...;` and + // Note (port): C++ declares these as `extern "C" size_t Bun__...;` and // reads/increments the value directly, so the exported symbol must BE the // `usize` storage (not a pointer to it). `AtomicUsize` is `#[repr(C)] // usize`-layout-compatible. Handled via `#[unsafe(export_name = "...")]` @@ -337,7 +357,7 @@ pub enum EventName { } // Zig: `var random: std.rand.DefaultPrng = undefined;` -// PORT NOTE: declared but never read in analytics.zig — dead code. Dropped +// Note (port): declared but never read in analytics.zig — dead code. Dropped // rather than gated; if a future schema-encode path needs a PRNG, seed one // locally (PORTING.md §Concurrency: OnceLock<...>, no `static mut`). @@ -410,7 +430,7 @@ pub mod generate_header { // ────────────────────────────────────────────────────────────────── // Zig: `pub var linux_os_name: std.c.utsname = undefined;` - // PORT NOTE: Zig's `Environment.isLinux` is true on Android (it checks + // Note (port): Zig's `Environment.isLinux` is true on Android (it checks // the kernel, not the libc target), so all Linux-gated items below are // `any(linux, android)` — `for_linux()` itself branches on Android. // The cached `utsname` itself now lives in T1 at @@ -579,7 +599,7 @@ pub mod generate_header { // ────────────────────────────────────────────────────────────────── // Zig std's `std.c.utsname` has no FreeBSD branch; use translate-c's. - // PORT NOTE: Rust's `libc` crate does provide `utsname`/`uname` on + // Note (port): Rust's `libc` crate does provide `utsname`/`uname` on // FreeBSD, so the translate-c indirection is unnecessary here. #[cfg(target_os = "freebsd")] fn for_freebsd() -> analytics::Platform { diff --git a/src/analytics/schema.rs b/src/analytics/schema.rs index aa521e7e7d3e..c6050db9b1ef 100644 --- a/src/analytics/schema.rs +++ b/src/analytics/schema.rs @@ -1,6 +1,7 @@ // GENERATED: re-run the analytics schema generator (peechy) with .rs output // source: src/analytics/schema.zig -// TODO(port): regenerate remaining analytics::* types for Rust +// Hand-ported subset — the remaining analytics::* types are unused at runtime +// and come back with the next peechy regen (see the `analytics` mod below). use bun_core::Error; @@ -23,10 +24,14 @@ use bun_core::Error; // already do). /// Zig: `Reader.ReadError = error{EOF}`. -// PORT NOTE: peechy's two error cases (`EOF`, `InvalidValue`) are folded into +// peechy's two error cases (`EOF`, `InvalidValue`) are folded into // the crate-wide `bun_core::Error` so downstream `decode` signatures stay // `Result<_, bun_core::Error>` without an extra `From` hop. -pub(crate) const EOF: Error = Error::TODO; // TODO(port): Error::from_name("EOF") once name→code table lands +// (`Error::from_name` interns at runtime, so this is a fn, not a const.) +#[inline] +pub(crate) fn eof() -> Error { + bun_core::err!("EOF") +} /// Primitive integers encodable in the peechy wire format (native-endian raw /// bytes). Zig handled this via `comptime T` + `std.mem.readIntSliceNative` / @@ -87,7 +92,7 @@ pub trait Reader { /// Concrete buffer-backed reader — direct port of Zig's `pub const Reader = struct`. /// -/// PORT NOTE: the Zig struct also carries `std.mem.Allocator param` for +/// The Zig struct also carries `std.mem.Allocator param` for /// `readArray`'s nested-slice case; per PORTING.md §Allocators (non-AST crate) /// the allocator param is dropped — callers that need owned sub-arrays /// allocate at the call site. @@ -107,7 +112,7 @@ impl<'a> Reader for BufReader<'a> { fn read(&mut self, count: usize) -> Result<&[u8], Error> { let read_count = core::cmp::min(count, self.remain.len()); if read_count < count { - return Err(EOF); + return Err(eof()); } let (slice, rest) = self.remain.split_at(read_count); self.remain = rest; @@ -123,7 +128,7 @@ impl<'a> Reader for BufReader<'a> { // will be filled in by the peechy regen. pub mod analytics { /// Zig: `pub const OperatingSystem = enum(u8) { _none, linux, macos, windows, wsl, android, freebsd, _ }` - // PORT NOTE: Zig's open enum (`_`) is dropped — Rust enums are closed; the + // Zig's open enum (`_`) is dropped — Rust enums are closed; the // schema decoder is the only producer of unknown discriminants and it is // not yet ported. #[repr(u8)] diff --git a/src/api/lib.rs b/src/api/lib.rs index de79a80e96c2..52ea746e1344 100644 --- a/src/api/lib.rs +++ b/src/api/lib.rs @@ -42,12 +42,11 @@ pub mod npm_registry { pub use super::NpmRegistry; - // PORT NOTE: `Parser` stays generic over `L` (Log) / `S` (Source) so this - // leaf schema crate doesn't need to name `bun_logger`. The lone live body - // (`parse_registry_url_string_impl`) doesn't touch log/source — only the - // not-yet-ported `parse_registry_object` / `parse_registry` paths do, and - // those need `js_ast::Expr` so they belong upstream in the bunfig parser - // anyway. + // `Parser` stays generic over `L` (Log) / `S` (Source) so this leaf + // schema crate doesn't need to name `bun_logger`. The lone live body + // (`parse_registry_url_string_impl`) doesn't touch log/source — only + // `parse_registry_object` / `parse_registry` would, and those need + // `js_ast::Expr` so they belong upstream in the bunfig parser anyway. pub struct Parser<'a, L, S> { pub log: &'a mut L, pub source: &'a S, diff --git a/src/ast/Cargo.toml b/src/ast/Cargo.toml index 8ef63ef52594..279f490bb4f1 100644 --- a/src/ast/Cargo.toml +++ b/src/ast/Cargo.toml @@ -29,4 +29,5 @@ bun_ptr.workspace = true bun_paths.workspace = true bun_collections.workspace = true bun_sys.workspace = true +bun_perf.workspace = true bun_wyhash.workspace = true diff --git a/src/ast/ast_result.rs b/src/ast/ast_result.rs index 644b5fd49d63..c5f47ba372e6 100644 --- a/src/ast/ast_result.rs +++ b/src/ast/ast_result.rs @@ -96,7 +96,7 @@ pub struct Ast<'a> { pub import_meta_ref: Ref, } -// PORT NOTE: Zig field defaults reference named constants (`Ref.None`, `logger.Range.None`, +// Zig field defaults reference named constants (`Ref.None`, `logger.Range.None`, // `ExportsKind.none`, `Target.browser`) whose equivalence to the Rust types' `Default::default()` // is unverified across crates, so spell them out here instead of `#[derive(Default)]`. // @@ -188,9 +188,9 @@ impl<'a> Ast<'a> { // Zig `deinit` only freed `parts`, `symbols`, `import_records` via `bun.default_allocator`, // and was guarded by "Do not call this if it wasn't globally allocated!". - // In Rust those fields own their storage and free on Drop; no explicit body needed. - // TODO(port): Vec Drop semantics must distinguish arena-backed vs heap-backed to - // preserve the Zig conditional-free behavior. Revisit. + // In Rust those fields are `ArenaVec`s (`BabyVec`) whose `Drop` deallocates + // through the allocator each instance was constructed with, so the Zig + // arena-vs-heap conditional-free is encoded in the type — no explicit body needed. } pub use crate::g::Class; diff --git a/src/ast/b.rs b/src/ast/b.rs index f2d42ba501e8..f6a56367595e 100644 --- a/src/ast/b.rs +++ b/src/ast/b.rs @@ -28,7 +28,7 @@ pub use crate::ArrayBinding; /// B.Object /// ``` // Zig: `union(Binding.Tag)` — tag enum lives on `Binding::Tag`. -// PORT NOTE: arena values are referenced via `StoreRef` (LIFETIMES.tsv: ARENA) +// Arena values are referenced via `StoreRef` (LIFETIMES.tsv: ARENA) // rather than a threaded `&'bump mut T`. #[derive(Copy, Clone, bun_core::EnumTag)] #[enum_tag(existing = super::binding::Tag)] @@ -74,24 +74,27 @@ pub struct Property { pub value: Binding, pub default_value: Option, } -// TODO(port): partial defaults — Zig only defaults `flags`/`default_value`; `key`/`value` have none, so no `impl Default`. +// No `impl Default` on purpose: Zig only defaults `flags`/`default_value`; +// `key`/`value` have no defaults, so every constructor must supply them. pub struct Object { pub properties: crate::StoreSlice, pub is_single_line: bool, } -// Zig: `pub const Property = B.Property;` — inherent associated type alias. -// TODO(port): inherent associated types are unstable; callers use `B::Property` directly. -// TODO(port): partial defaults — Zig only defaults `is_single_line`; `properties` has none, so no `impl Default`. +// Zig: `pub const Property = B.Property;` — inherent associated types are +// unstable in Rust, so callers spell `b::Property` directly. +// No `impl Default` on purpose: Zig only defaults `is_single_line`; +// `properties` has no default, so every constructor must supply it. pub struct Array { pub items: crate::StoreSlice, pub has_spread: bool, pub is_single_line: bool, } -// Zig: `pub const Item = ArrayBinding;` — inherent associated type alias. -// TODO(port): inherent associated types are unstable; callers use `ArrayBinding` directly. -// TODO(port): partial defaults — Zig only defaults `has_spread`/`is_single_line`; `items` has none, so no `impl Default`. +// Zig: `pub const Item = ArrayBinding;` — inherent associated types are +// unstable in Rust, so callers spell `ArrayBinding` directly. +// No `impl Default` on purpose: Zig only defaults `has_spread`/`is_single_line`; +// `items` has no default, so every constructor must supply it. #[derive(Default, Copy, Clone)] pub struct Missing {} @@ -127,7 +130,7 @@ impl B { where H: bun_core::Hasher + ?Sized, S: crate::base::SymbolTable + ?Sized, - // PORT NOTE: `symbol_table: anytype` — forwarded to `Ref::get_symbol` and + // `symbol_table: anytype` — forwarded to `Ref::get_symbol` and // `Expr::Data::write_to_hasher`; bound mirrors `Expr::Data::write_to_hasher`. { // Local mirror of `bun.writeAnyToHasher`. Zig fed anonymous tuples diff --git a/src/ast/char_freq.rs b/src/ast/char_freq.rs index e329705ea8e7..18c5b0d9a67d 100644 --- a/src/ast/char_freq.rs +++ b/src/ast/char_freq.rs @@ -7,8 +7,8 @@ pub(crate) struct CharAndCount { pub index: usize, } -// PORT NOTE: Zig `CharAndCount.Array` was an associated type alias; inherent -// associated types are unstable in Rust, so it's a free alias here. +// Zig's `CharAndCount.Array` associated alias is a free alias here; inherent +// associated types are unstable in Rust. pub(crate) type CharAndCountArray = [CharAndCount; CHAR_FREQ_COUNT]; // PERF(port): Zig used `@Vector(CHAR_FREQ_COUNT, i32)` for SIMD adds — profile @@ -16,9 +16,6 @@ type Buffer = [i32; CHAR_FREQ_COUNT]; #[derive(Copy, Clone)] pub struct CharFreq { - // PORT NOTE: Zig field was `align(1)` (unaligned i32 array). Rust gives natural - // alignment; if the packed layout was load-bearing for an FFI/serialized struct, - // revisit. pub freqs: Buffer, } @@ -75,7 +72,7 @@ impl CharFreq { } // std.sort.pdq → Rust's sort_unstable_by (pattern-defeating quicksort). - // PORT NOTE: do NOT route through `CharAndCount::less_than` and map + // Do NOT route through `CharAndCount::less_than` and map // false→Greater — that comparator never returns `Equal`, which // violates `sort_unstable_by`'s total-order contract (Rust 1.81+ // is permitted to panic on inconsistent comparators). `index` is @@ -120,7 +117,7 @@ impl CharFreq { fn scan_big(out: &mut Buffer, text: &[u8], delta: i32) { // https://zig.godbolt.org/z/P5dPojWGK - // PORT NOTE: Zig copied `out.*` into a stack local and wrote back via `defer` to + // Zig copied `out.*` into a stack local and wrote back via `defer` to // avoid unaligned (`align(1)`) loads in the hot loop. We operate on `out` directly; // the field is naturally aligned in Rust. let mut deltas: [i32; 256] = [0; 256]; @@ -141,7 +138,7 @@ fn scan_big(out: &mut Buffer, text: &[u8], delta: i32) { deltas[c as usize] += delta; } - // PORT NOTE — INTENTIONAL SPEC DIVERGENCE: CharFreq.zig:64 writes + // INTENTIONAL DIVERGENCE from the Zig original: CharFreq.zig:64 writes // `freqs[0..26].* = deltas[...]`, which *overwrites* the accumulator // (`var freqs = out.*` is dead). That is an upstream bug: every ≥32-byte // scan discards all prior counts, so the result is last-big-scan-wins @@ -169,7 +166,7 @@ fn scan_big(out: &mut Buffer, text: &[u8], delta: i32) { } fn scan_small(out: &mut Buffer, text: &[u8], delta: i32) { - // PORT NOTE: Zig copied `out.*` into a stack local to avoid unaligned (`align(1)`) + // Zig copied `out.*` into a stack local to avoid unaligned (`align(1)`) // RMWs in the loop. The Rust field is naturally aligned, so operate on `out` directly // (same treatment as `scan_big`). for &c in text { diff --git a/src/ast/e.rs b/src/ast/e.rs index d1d101d4feff..a761879f5ff6 100644 --- a/src/ast/e.rs +++ b/src/ast/e.rs @@ -63,9 +63,6 @@ impl Default for Array { } } } -// TODO(port): Array methods call `Vec::init_capacity(bump, n)` -// (signature mismatch: Vec takes only `n`; AST-crate variant with bump -// arena pending) and `Expr::Data::*` deep matches. Un-gate with parser round. // Live subset of `Array` accessors needed by downstream crates. impl Array { pub const EMPTY: Array = Array { @@ -105,7 +102,7 @@ impl Array { // same allocation profile (one upfront `with_capacity`), no uninit. let mut out: ExprNodeList = ExprNodeList::init_capacity(estimated_count + self.items.len_u32() as usize); - // PORT NOTE: reshaped for borrowck — iterate items via index so the &mut + // Reshaped for borrowck — iterate items via index so the &mut // borrow of `out` does not overlap a shared borrow of `self`. let items_len = self.items.len_u32() as usize; for idx in 0..items_len { @@ -327,7 +324,8 @@ pub enum CallUnwrap { pub struct Dot { // target is Node pub target: ExprNodeIndex, - // TODO(port): arena-owned slice + // Arena-owned slice (`StoreStr`: lifetime-erased arena ownership, bulk-freed + // at `Store::reset()` — see `nodes.rs` StoreStr docs). pub name: Str, pub name_loc: crate::Loc, pub optional_chain: Option, @@ -355,9 +353,9 @@ impl Default for Dot { } impl Dot { pub fn has_same_flags_as(&self, b: &Dot) -> bool { - // TODO(port): Zig refers to `a.is_direct_eval` which does not exist on Dot; - // mirroring the (likely buggy) Zig literally would not compile. Preserving - // the three fields that DO exist; revisit. + // Zig refers to `a.is_direct_eval`, which does not exist on Dot (likely a + // copy/paste bug in the Zig source); mirroring it literally would not + // compile. Compare the three flag fields that DO exist on Dot. self.optional_chain == b.optional_chain && self.can_be_removed_if_unused == b.can_be_removed_if_unused && self.call_can_be_unwrapped_if_unused == b.call_can_be_unwrapped_if_unused @@ -843,7 +841,8 @@ macro_rules! impl_number_cast { impl_number_cast!(u16, u32, u64, usize); pub struct BigInt { - // TODO(port): arena-owned slice + // Arena-owned slice (`StoreStr`: lifetime-erased arena ownership, bulk-freed + // at `Store::reset()` — see `nodes.rs` StoreStr docs). pub value: Str, } impl BigInt { @@ -1303,7 +1302,7 @@ fn package_json_sort_is_less_than(lhs: &G::Property, rhs: &G::Property) -> Order match lhs_key_size.cmp(&rhs_key_size) { Ordering::Equal => { - // PORT NOTE: Zig `cmpStringsAsc` is `std.mem.order(u8, a, b) == .lt`; lifted to + // Zig `cmpStringsAsc` is `std.mem.order(u8, a, b) == .lt`; lifted to // a full `Ordering` so this is usable with `sort_by`. let a = lhs .key @@ -1356,7 +1355,8 @@ pub struct EString { // A version of this where `utf8` and `value` are stored in a packed union, with len as a single u32 was attempted. // It did not improve benchmarks. Neither did converting this from a heap-allocated type to a stack-allocated type. // TODO: change this to *const anyopaque and change all uses to either .slice8() or .slice16() - // TODO(port): arena-owned slice + // Arena-owned slice (`StoreStr`: lifetime-erased arena ownership, bulk-freed + // at `Store::reset()` — see `nodes.rs` StoreStr docs). pub data: Str, pub prefer_template: bool, @@ -2086,7 +2086,8 @@ impl Template { } pub struct RegExp { - // TODO(port): arena-owned slice + // Arena-owned slice (`StoreStr`: lifetime-erased arena ownership, bulk-freed + // at `Store::reset()` — see `nodes.rs` StoreStr docs). pub value: Str, /// This exists for JavaScript bindings @@ -2172,7 +2173,8 @@ pub struct RequireResolveString { pub struct InlinedEnum { pub value: ExprNodeIndex, - // TODO(port): arena-owned slice + // Arena-owned slice (`StoreStr`: lifetime-erased arena ownership, bulk-freed + // at `Store::reset()` — see `nodes.rs` StoreStr docs). pub comment: Str, } diff --git a/src/ast/expr.rs b/src/ast/expr.rs index f0980358545c..ea3d7e841d47 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -107,16 +107,17 @@ impl Expr { data::Store::create(); } - /// Zig: `Expr.Data.Store.assert()` — debug-only re-entrancy guard. + /// Zig: `Expr.Data.Store.assert()` — debug-only "Store must be init'd" + /// guard (expr.zig:3170-3175). The re-entrancy `Disabler` check lives in + /// `Store::append`, mirroring expr.zig:3194. #[inline] pub fn data_store_assert() { - crate::DebugOnlyDisabler::::assert(); + data::Store::assert(); } } impl Expr { pub fn clone_in(&self, bump: &Bump) -> Result { - // TODO(port): narrow error set Ok(Expr { loc: self.loc, data: Data::clone_in(self.data, bump)?, @@ -136,7 +137,6 @@ impl Expr { } pub fn wrap_in_arrow(this: Expr, bump: &Bump) -> Result { - // TODO(port): narrow error set let stmts: &mut [Stmt] = bump.alloc_slice_fill_with(1, |_| { Stmt::alloc(S::Return { value: Some(this) }, this.loc) }); @@ -369,7 +369,7 @@ impl Expr { /// - `foo[123].bar[456].baz.qux` // etc. /// /// This is not intended for use by the transpiler, instead by pretty printing JSON. - // PORT NOTE: Zig passed `bun.default_allocator` to getByIndex; Rust threads the arena + // Zig passed `bun.default_allocator` to getByIndex; Rust threads the arena // explicitly because get_by_index allocates an E.String slice into &Bump. pub fn get_path_may_be_index(&self, bump: &Bump, name: &[u8]) -> Option { if name.is_empty() { @@ -568,7 +568,7 @@ impl Expr { } } - // PORT NOTE: `Query` holds `expr` by value (Copy). The iterator stores the + // `Query` holds `expr` by value (Copy). The iterator stores the // `StoreRef` directly (Copy, arena-backed) so no lifetime is tied // to a local temporary — `StoreRef::Deref` re-borrows the arena slot on use. pub fn get_array(&self, name: &[u8]) -> Option { @@ -698,7 +698,7 @@ impl ArrayIterator { } } -// PORT NOTE: earlier drafts of `as_array`/`is_string`/`as_utf8_string_literal`/ +// Earlier drafts of `as_array`/`is_string`/`as_utf8_string_literal`/ // `as_string`/`as_string_cloned`/`as_bool`/`as_number` duplicated the live `&self` // implementations above (lines ~231-315) with worse signatures (`expr: &Expr`, // raw-ptr returns). Those drafts were dropped; only the methods without a live @@ -827,7 +827,7 @@ impl Expr { ) } - // PORT NOTE: Zig threaded `_: std.mem.Allocator` (unused) so the caller's + // Zig threaded `_: std.mem.Allocator` (unused) so the caller's // arena reached `Expr.init`. The Rust port uses the thread-local // `data::Store` and drops the parameter. pub fn join_with_comma(self, b: Expr) -> Expr { @@ -862,7 +862,7 @@ impl Expr { } } - // PORT NOTE: Zig threaded `ctx: anytype` and called `callback(ctx, ...)` on + // Zig threaded `ctx: anytype` and called `callback(ctx, ...)` on // each element. Rust passes `ctx` by `&mut` so a single `&mut P` (the parser // state) can be reborrowed for each callback invocation without `Copy`. pub fn join_all_with_comma_callback( @@ -977,7 +977,7 @@ impl Expr { #[cfg(debug_assertions)] pub(crate) static ICOUNT: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0); -// PORT NOTE: Zig `expr.zig` declares `true_bool`/`false_bool`/`bool_values` +// Zig `expr.zig` declares `true_bool`/`false_bool`/`bool_values` // statics but never references them — `E.Boolean` is stored by value in // `Data.e_boolean` (both `allocate` and `init` arms), not as a pointer to a // pooled singleton. Dropped here; the comment "We don't need to dynamically @@ -2302,13 +2302,12 @@ impl Data { /// Shallow clone: re-allocate the boxed payload (so the caller owns a fresh /// arena slot) but don't recurse into children. Zig: `Data.clone`. /// - /// PORT NOTE: the `E::*` payloads do not derive `Clone` (they hold raw arena + /// The `E::*` payloads do not derive `Clone` (they hold raw arena /// pointers / `Vec`). Zig copied struct bytes (`el.*`); we mirror that /// with a `core::ptr::read` of the payload, which is sound because every /// payload is `Copy`-shaped (no `Drop`, no owned heap state — `Vec` /// stores a raw pointer + len/cap into the arena). pub fn clone_in(this: Data, bump: &Bump) -> Result { - // TODO(port): narrow error set macro_rules! shallow { ($variant:ident, $el:expr) => {{ // SAFETY: `$el` is a `StoreRef` deref to a live arena `T`; `T` is @@ -2689,7 +2688,7 @@ impl Data { e.value.data.write_to_hasher(hasher, symbol_table); } Data::EYield(e) => { - // TODO(port): Zig hashed the raw bytes of `.{ e.is_star, e.value }` (the full + // Zig hashed the raw bytes of `.{ e.is_star, e.value }` (the full // `?Expr` optional, including loc/data pointer). Rust `Option` layout is // not byte-compatible, so we hash the discriminant here and recurse below. raw(hasher, e.is_star); @@ -2724,7 +2723,7 @@ impl Data { hasher.update(&e.value); } Data::EString(e) => { - // PORT NOTE: Zig declared `var next: ?*E.String = e;` and tested `if (next)` + // Zig declared `var next: ?*E.String = e;` and tested `if (next)` // — i.e. it only ever hashes the *first* rope segment (the `next = current.next` // store is dead). Preserved here. let current: &E::String = e; @@ -3403,7 +3402,6 @@ pub use data::Store; // Zig: `pub fn StoredData(tag: Tag) type` — comptime type-level function. // Rust cannot return types from runtime tags. Callers should match on `Data` // directly. -// TODO(port): if needed, expose as a macro mapping Tag → payload type. fn string_to_equivalent_number_value(str: &[u8]) -> f64 { // +"" -> 0 @@ -3413,7 +3411,6 @@ fn string_to_equivalent_number_value(str: &[u8]) -> f64 { if !bun_core::is_all_ascii(str) { return f64::NAN; } - // TODO(port): move to *_sys unsafe extern "C" { // NOT `safe fn`: callee dereferences `ptr` for `len` bytes — caller must // guarantee the (ptr,len) pair is a valid readable range. diff --git a/src/ast/g.rs b/src/ast/g.rs index 9b557a9405ea..4fd9b14cf7c3 100644 --- a/src/ast/g.rs +++ b/src/ast/g.rs @@ -12,7 +12,7 @@ use crate::{ExprData, ExprNodeList, LocRef, StmtNodeList, StoreSlice, StoreStr, pub use crate::flags::Function as FnFlags; pub use crate::flags::FunctionSet as FnFlagsSet; -// PORT NOTE: all `&'ast mut [T]` arena slices are `StoreSlice` (lifetime- +// All `&'ast mut [T]` arena slices are `StoreSlice` (lifetime- // erased arena-slice newtype). 'ast/'bump can be threaded crate-wide later by // adding a `PhantomData<&'arena ()>` to `StoreSlice` in one pass. diff --git a/src/ast/import_record.rs b/src/ast/import_record.rs index 859236a64fe2..e1b0cf6634a7 100644 --- a/src/ast/import_record.rs +++ b/src/ast/import_record.rs @@ -24,11 +24,10 @@ pub struct ImportRecord { pub source_index: Index, - /// `js_printer::printBundledImport` reads this. The Zig field was removed - /// from `ImportRecord` but the printer body referencing it is dead (never - /// analysed by Zig's lazy compilation). Kept here so the eagerly-compiled - /// Rust port of that body type-checks; always 0 in practice. - // TODO(port): delete once `printBundledImport` is confirmed dead and removed. + /// Dead field: the Zig `ImportRecord` removed it, and the only reader + /// (`js_printer`'s `print_bundled_import`, dead code in Zig too) has been + /// deleted. Always 0. Remove together with the `module_id: 0` initializers + /// in the parser/bundler/css constructors when those files are next touched. pub module_id: u32, /// The original import specifier as written in source code (e.g., "./foo.js"). diff --git a/src/ast/known_global.rs b/src/ast/known_global.rs index d50ac3bdd052..1ff20cf28d0b 100644 --- a/src/ast/known_global.rs +++ b/src/ast/known_global.rs @@ -112,7 +112,7 @@ impl KnownGlobal { js_ast::Expr::init(call, loc) } - // PORT NOTE: `_bump` is kept for call-site shape parity with the Zig + // `_bump` is kept for call-site shape parity with the Zig // `std.mem.Allocator` arg. The `Vec` uses the global arena. #[inline(never)] pub fn minify_global_constructor( diff --git a/src/ast/lib.rs b/src/ast/lib.rs index b2104c4a98bd..1d83b8bdcbd6 100644 --- a/src/ast/lib.rs +++ b/src/ast/lib.rs @@ -412,8 +412,8 @@ impl fmt::Debug for Ref { } } -// TODO(port): bun_paths must define `PathContentsPair` (TYPE_ONLY from bun_resolver::fs). -// Local mirror so init_file / init_recycled_file resolve until paths' move-in lands. +// Local mirror of `bun_resolver::fs`'s path+contents pair (canonical home would +// be `bun_paths`); defined here so init_file / init_recycled_file resolve. // `pub` so `bun_bundler::Transpiler::parse_maybe` can construct it for // `Source::init_recycled_file` (transpiler.zig:852). /// A [`Source`]'s path paired with its raw bytes (used by virtual-module @@ -423,12 +423,11 @@ pub struct PathContentsPair { pub path: bun_paths::fs::Path<'static>, pub contents: &'static [u8], } -// TODO(port): bun_schema::api — `to_api` methods gated behind . // In Zig: `const string = []const u8;` type Str = &'static [u8]; -// TODO(port): lifetime — see module-level note. `Str` is a stand-in for the Zig -// `[]const u8` struct-field pattern; TODO(port): replace with the real type. +// `Str` is a lifetime-erased stand-in for the Zig `[]const u8` struct-field +// pattern; see the module-level OWNERSHIP note for the real ownership story. // ─────────────────────────────────────────────────────────────────────────── // api — hand-ported slice of `bun.schema.api` (src/options_types/schema.zig @@ -496,7 +495,8 @@ pub mod api { /// `[]const u8` parameter shim — accepts `&str` / `&[u8]` (any lifetime) /// and erases to the crate-wide `Str` (`&'static [u8]`) lie so callers in either /// string flavour compile against the same Zig-shaped signatures. -/// TODO(port): lifetime — remove with `Str` once `'source` is threaded through. +/// Removable together with `Str` once a `'source` lifetime is threaded through +/// (see the module-level OWNERSHIP note). pub trait IntoStr { fn into_str(self) -> Str; } @@ -712,7 +712,7 @@ pub struct Location { // - 4-byte fields last: i32 // This eliminates padding between differently-sized fields. // - // PORT NOTE: `file` / `line_text` are `Cow` (not `Str`) because + // `file` / `line_text` are `Cow` (not `Str`) because // `Location::clone()` must deep-dupe them (Zig: `allocator.dupe(u8, ..)`, // logger.zig:113) so a `BuildMessage`/`ResolveMessage` that outlives the // `Source.contents` it borrowed from doesn't read poisoned memory. The @@ -738,7 +738,7 @@ pub struct Location { pub column: i32, } -// PORT NOTE: NOT `#[derive(Clone)]`. `file` / `line_text` are +// NOT `#[derive(Clone)]`. `file` / `line_text` are // `Cow<'static, [u8]>` whose `Borrowed` arm may carry a lifetime-erased view // into `Source.contents` (see `init_or_null`, `css_parser.rs`, `error.rs`, // `JSBundler.rs`). The derived `Cow::clone` would re-borrow that pointer, so a @@ -802,7 +802,7 @@ impl Location { } pub fn clone_with_builder(&self, _string_builder: &mut StringBuilder) -> Location { - // PORT NOTE: Zig's `string_builder.append` copies into a buffer owned + // Zig's `string_builder.append` copies into a buffer owned // by the destination `Log`'s allocator (StringBuilder.zig). The local // `StringBuilder` stub above is a no-op that returns its input, so a // `Cow::Borrowed(append(s))` would alias `self`'s storage and dangle @@ -906,7 +906,7 @@ impl Location { } else { 1 }, - // PORT NOTE: Zig borrows `source.contents` here and relies on the + // Zig borrows `source.contents` here and relies on the // arena outliving the `Log` (transpiler.zig:853 — `entry.contents` // is arena-allocated and never explicitly freed on `return null`). // Rust's `source_backing` in `Transpiler::parse_*` is RAII and @@ -1151,19 +1151,11 @@ impl Data { pretty_write!(":{}", location.line)?; } - if cfg!(debug_assertions) { - // TODO(port): the Zig gates this on - // `std.mem.indexOf(u8, @typeName(@TypeOf(to)), "fs.file") != null` — - // i.e. comptime reflection on the writer's type name to detect - // a real file writer (vs Bun.inspect). No Rust equivalent; - // TODO(port): plumb an explicit flag. - if false - && Output::ENABLE_ANSI_COLORS_STDERR - .load(core::sync::atomic::Ordering::Relaxed) - { - pretty_write!(" byte={}", location.offset)?; - } - } + // Zig appends a debug-only ` byte={offset}` suffix here, gated on + // comptime reflection over the writer's type name (`@typeName` + // contains "fs.file" — i.e. a real file writer, not Bun.inspect). + // Rust has no writer-type-name reflection and no caller plumbs an + // equivalent flag, so the suffix is intentionally not emitted. } } @@ -1206,8 +1198,15 @@ impl BabyString { } pub fn r#in(parent: &[u8], text: &[u8]) -> BabyString { - // TODO(port): bun_core::index_of missing — inline bstr fallback. - let off = bstr::ByteSlice::find(parent, text).expect("unreachable"); + // Zig's BabyString.in uses std.mem.indexOf, which returns 0 for an empty + // needle; bun_core::strings::index_of (a port of bun.strings.indexOf) + // deliberately returns None for an empty needle. An empty `text` reaches + // this path via resolve errors for `import ""`, so short-circuit it here + // to preserve std.mem.indexOf semantics. + if text.is_empty() { + return BabyString::new(0, 0); + } + let off = bun_core::strings::index_of(parent, text).expect("unreachable"); BabyString::new(off as u16, text.len() as u16) // @truncate } @@ -1285,8 +1284,9 @@ impl Msg { for (i, note) in self.notes.iter().enumerate() { notes[i] = note.clone_with_builder(builder); } - // TODO(port): lifetime — Zig returns a sub-slice of the - // caller-provided `notes` buffer; with `Box<[Data]>` we copy. + // Zig returns a sub-slice of the caller-provided `notes` + // buffer; with `Box<[Data]>` ownership we copy (perf-only + // deviation). break 'brk notes[0..self.notes.len()].to_vec().into_boxed_slice(); } } else { @@ -1321,7 +1321,7 @@ impl Msg { } pub fn to_api_from_list(list: &[Msg]) -> Box<[api::Message]> { - // PORT NOTE: Zig took `comptime ListType: type, list: ListType` and read + // Zig took `comptime ListType: type, list: ListType` and read // `list.items`; collapsed to `&[Msg]`. let mut out_list = Vec::with_capacity(list.len()); for item in list { @@ -1359,7 +1359,7 @@ impl Msg { } pub fn format_writer(&self, writer: &mut impl fmt::Write) -> fmt::Result { - // PORT NOTE: Zig had an unused `comptime _: bool` param; dropped. + // Zig had an unused `comptime _: bool` param; dropped. if let Some(location) = &self.data.location { write!( writer, @@ -1442,31 +1442,63 @@ impl Default for Range { } /// Was `bun_js_parser::lexer::rangeOfIdentifier`. -/// Moved into logger to break logger→js_parser. Mirrors lexer.zig:3113-3148. -/// TODO(port): full Unicode `isIdentifierStart/Continue` tables — currently -/// ASCII + `#`/`\` only; non-ASCII identifiers get a Range with len up to the -/// first non-ASCII byte (only affects error-highlight width, not correctness). +/// Moved into logger to break logger→js_parser. Mirrors lexer.zig:3113-3160, +/// including the full Unicode `isIdentifierStart/Continue` tables (via +/// `bun_core::identifier`) and `\u{...}` escape skipping. pub fn range_of_identifier(contents: &[u8], loc: Loc) -> Range { if loc.start < 0 || (loc.start as usize) >= contents.len() { return Range::NONE; } let text = &contents[loc.start as usize..]; - let mut i = 0usize; - if text.first() == Some(&b'#') { - i = 1; - } - let is_start = |c: u8| c.is_ascii_alphabetic() || c == b'_' || c == b'$' || c == b'\\'; - let is_cont = |c: u8| c.is_ascii_alphanumeric() || c == b'_' || c == b'$' || c == b'\\'; - if i < text.len() && is_start(text[i]) { - i += 1; - while i < text.len() && is_cont(text[i]) { - i += 1; + let mut r = Range { loc, len: 0 }; + if text.is_empty() { + return r; + } + let end = text.len() as u32; + + let iter = bun_core::strings::CodepointIterator::init(text); + let mut cursor = bun_core::strings::Cursor::default(); + if !iter.next(&mut cursor) { + return r; + } + + // Handle private names + if cursor.c == '#' as i32 { + if !iter.next(&mut cursor) { + r.len = 1; + return r; + } + } + + if bun_core::identifier::is_identifier_start(cursor.c) || cursor.c == '\\' as i32 { + while iter.next(&mut cursor) { + if cursor.c == '\\' as i32 { + // Skip over bracketed unicode escapes such as "\u{10000}" + if cursor.i + 2 < end + && text[cursor.i as usize + 1] == b'u' + && text[cursor.i as usize + 2] == b'{' + { + cursor.i += 2; + while cursor.i < end { + if text[cursor.i as usize] == b'}' { + cursor.i += 1; + break; + } + cursor.i += 1; + } + } + } else if !lexer_tables::is_identifier_continue(cursor.c) { + r.len = i32::try_from(cursor.i).expect("int cast"); + return r; + } } + + // EOF inside the identifier: Zig also reports `cursor.i` (the byte + // offset of the last codepoint read), matching lexer.zig:3160. + r.len = i32::try_from(cursor.i).expect("int cast"); } - Range { - loc, - len: i32::try_from(i).expect("int cast"), - } + + r } impl Range { @@ -1769,16 +1801,16 @@ impl Log { } if notes_count > 0 { - // TODO(port): lifetime — Zig allocates one shared `[Data; notes_count]` - // buffer in `other`'s allocator and re-slices each `msg.notes` into it. - // With `Box<[Data]>` per-Msg we instead deep-copy each notes slice. + // Zig allocates one shared `[Data; notes_count]` buffer in `other`'s + // allocator and re-slices each `msg.notes` into it. With `Box<[Data]>` + // per-Msg we instead deep-copy each notes slice (perf-only deviation). for msg in &mut self.msgs { msg.notes = msg.notes.to_vec().into_boxed_slice(); } } other.msgs.extend(self.msgs.iter().map(Msg::clone)); - // PORT NOTE: reshaped for borrowck — Zig appendSlice moves the (now + // Reshaped for borrowck — Zig appendSlice moves the (now // re-sliced) Msgs; here we clone since `self` retains them. other.warnings += self.warnings; other.errors += self.errors; @@ -1814,7 +1846,7 @@ impl Log { let mut notes_buf = vec![Data::default(); notes_count]; let mut note_i: usize = 0; - // PORT NOTE: reshaped for borrowck — Zig zips `self.msgs` with the + // Reshaped for borrowck — Zig zips `self.msgs` with the // tail of `other.msgs`; index instead. for (k, msg) in self.msgs.iter().enumerate() { let j = dest_start + k; @@ -1850,7 +1882,7 @@ impl Log { } } -// PORT NOTE: Zig `Log.deinit` only does `msgs.clearAndFree()` — field-free-only, +// Zig `Log.deinit` only does `msgs.clearAndFree()` — field-free-only, // so per PORTING.md no `impl Drop` is emitted (Vec drops automatically). // The mid-life semantic operation is exposed as `clear_and_free` above. @@ -1930,7 +1962,7 @@ impl Log { ) { let text = alloc_print(args); // TODO: fix this. this is stupid, it should be returned in allocPrint. - // PORT NOTE: Zig reads `args.@"0"` (first tuple element) for the + // Zig reads `args.@"0"` (first tuple element) for the // specifier; with `fmt::Arguments` that's opaque, so callers must pass // `specifier_arg` explicitly. let specifier = BabyString::r#in(&text, specifier_arg); @@ -2175,8 +2207,8 @@ impl Log { let data = Data { text: alloc_print(args), location: Some(Location { - // TODO(port): lifetime — `Location.file` borrows `Str`; thread - // real ownership through (see module doc). + // `Location.file` borrows the lifetime-erased `Str`; see the + // module-level OWNERSHIP note. file: Cow::Borrowed(filepath), line: i32::try_from(line).expect("int cast"), column: i32::try_from(col).expect("int cast"), @@ -2391,7 +2423,7 @@ impl Log { self.warnings += 1; let data = self.tracked_range_data(source, r, text); self.add_msg(Msg { - // PORT NOTE: Zig has `.kind = .warning` here which doesn't exist in + // Zig has `.kind = .warning` here which doesn't exist in // `Kind`; presumed dead code / typo for `.warn`. kind: Kind::Warn, data, @@ -2657,7 +2689,7 @@ pub fn usize2loc(loc: usize) -> Loc { pub struct Source { pub path: bun_paths::fs::Path<'static>, - /// PORT NOTE: `Cow` so `source_from_file` / `File::to_source_at` can hand + /// `Cow` so `source_from_file` / `File::to_source_at` can hand /// back a heap buffer without leaking (PORTING.md §Forbidden). Borrowed /// arm covers the Zig `[]const u8`-field default (parser/transpiler feed /// arena slices via `IntoStr`). Prefer the `.contents()` accessor at @@ -2668,7 +2700,7 @@ pub struct Source { /// Lazily-generated human-readable identifier name that is non-unique /// Avoid accessing this directly most of the time /// - /// PORT NOTE: `Cow` because the cached value is produced by + /// `Cow` because the cached value is produced by /// `MutableString::ensure_valid_identifier` (owned `Box<[u8]>`); the Zig /// freed it in `deinit`, so per PORTING.md §Forbidden this cannot be /// `&'static [u8]` + leak. @@ -2910,7 +2942,6 @@ impl Source { } pub fn identifier_name(&mut self) -> Result<&[u8], bun_core::Error> { - // TODO(port): narrow error set if !self.identifier_name.is_empty() { return Ok(&self.identifier_name); } @@ -3357,18 +3388,56 @@ pub enum IndentationCharacter { // ─────────────────────────────────────────────────────────────────────────── /// `bun.DebugOnlyDisabler(T)` — debug-build re-entrancy guard around Store -/// access. No-op in release; in debug, asserts `!disabled`. +/// access. No-op in release; in debug, `assert()` panics while the per-type +/// disable count is non-zero (bun.zig:1473 `disable_create_in_debug`). +/// +/// Zig monomorphises a `threadlocal var disable_create_in_debug: usize` per +/// `T`; Rust cannot declare a generic `#[thread_local]` static, so the debug +/// build keeps one thread-local `TypeId` multiset instead (each `disable()` +/// pushes `TypeId::of::()`, each `enable()` pops one occurrence; membership +/// count == Zig's counter). pub struct DebugOnlyDisabler(core::marker::PhantomData); -impl DebugOnlyDisabler { + +#[cfg(debug_assertions)] +mod debug_disabler_state { + std::thread_local! { + /// Multiset of currently-disabled store types (one entry per + /// outstanding `disable()`); debug builds only. + pub(super) static DISABLED: core::cell::RefCell> = + const { core::cell::RefCell::new(Vec::new()) }; + } +} + +impl DebugOnlyDisabler { #[inline] pub fn assert() { - // TODO(port): wire to a thread-local `disabled: bool` if any caller - // actually toggles it; Zig sites only call `assert()`. + #[cfg(debug_assertions)] + debug_disabler_state::DISABLED.with(|d| { + assert!( + !d.borrow().contains(&core::any::TypeId::of::()), + "[{}] called while disabled (did you forget to call enable?)", + core::any::type_name::(), + ); + }); } #[inline] - pub fn disable() {} + pub fn disable() { + #[cfg(debug_assertions)] + debug_disabler_state::DISABLED + .with(|d| d.borrow_mut().push(core::any::TypeId::of::())); + } #[inline] - pub fn enable() {} + pub fn enable() { + #[cfg(debug_assertions)] + debug_disabler_state::DISABLED.with(|d| { + let mut v = d.borrow_mut(); + let pos = v + .iter() + .rposition(|t| *t == core::any::TypeId::of::()) + .expect("DebugOnlyDisabler::enable without matching disable"); + v.remove(pos); + }); + } /// RAII scope: `disable()` now, `enable()` on drop. Replaces the Zig idiom /// `Disabler.disable(); defer Disabler.enable();`. #[inline] @@ -3380,8 +3449,8 @@ impl DebugOnlyDisabler { /// Guard returned by [`DebugOnlyDisabler::scope`]; re-enables on drop. #[must_use = "disabler is re-enabled on drop; bind to a named local"] -pub struct DebugOnlyDisablerScope(core::marker::PhantomData); -impl Drop for DebugOnlyDisablerScope { +pub struct DebugOnlyDisablerScope(core::marker::PhantomData); +impl Drop for DebugOnlyDisablerScope { #[inline] fn drop(&mut self) { DebugOnlyDisabler::::enable(); diff --git a/src/ast/loader.rs b/src/ast/loader.rs index 3759ed20baf4..12f335ddca71 100644 --- a/src/ast/loader.rs +++ b/src/ast/loader.rs @@ -14,7 +14,7 @@ use phf; /// - bun-native-bundler-plugin-api/bundler_plugin.h /// - src/jsc/bindings/headers-handwritten.h #[repr(u8)] -#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Enum, strum::IntoStaticStr)] +#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Enum, strum::IntoStaticStr, strum::VariantNames)] // Zig field names are lower_snake — `@tagName` is exposed to JS (HTMLImportManifest // `"loader":`, BuildArtifact.loader) so the strum serialization must match exactly. #[strum(serialize_all = "snake_case")] @@ -246,7 +246,7 @@ impl Loader { matches!(self, Loader::Jsx | Loader::Js | Loader::Ts | Loader::Tsx) } - // PORT NOTE: spelling-aliases for the canonical `is_typescript` / + // Spelling-aliases for the canonical `is_typescript` / // `is_javascript_like*` (acronym-collapsing rule). Hoisted from // `bun_bundler::options::LoaderExt` so cross-crate callers (bun_jsc, // bun_runtime) resolve them as inherent methods without a trait import. diff --git a/src/ast/new_store.rs b/src/ast/new_store.rs index a28ec01048a5..7e90ca28b881 100644 --- a/src/ast/new_store.rs +++ b/src/ast/new_store.rs @@ -10,6 +10,22 @@ // Scope name distinct from the macro-generated `struct Store`. ::bun_core::declare_scope!(STORE_LOG, hidden); +/// Zig: `fn supportsType(T: type) bool` — compile-time membership check for +/// the type list of a `new_store!`-generated store. +/// +/// `new_store!` implements this for every `$T` in its list (with `S` = the +/// generated `Store` type), and bounds `Store::allocate`/`Store::append` (and +/// the `thread_local_ast_store!` front-end `append`) on it. Allocating a type +/// the store was not declared with is therefore a compile error, mirroring +/// Zig's `comptime if (!supportsType(T)) @compileError(...)`. +pub trait StoredIn: sealed::Sealed {} + +#[doc(hidden)] +pub mod sealed { + /// Sealing supertrait — only `new_store!` expansions implement it. + pub trait Sealed {} +} + /// Zig: `pub fn NewStore(comptime types: []const type, comptime count: usize) type` /// /// Rust cannot take a slice of types as a generic parameter, and the body @@ -82,7 +98,7 @@ macro_rules! new_store { } /// Zig: `pub const Block = struct { ... }` - // PORT NOTE: `buffer` needs `align(LARGEST_ALIGN)` but `#[repr(align(N))]` + // `buffer` needs `align(LARGEST_ALIGN)` but `#[repr(align(N))]` // requires a literal. Over-approximate with align(16) — every AST payload // type is `<= 16` aligned (asserted below). Switch to a // `#[repr(C)] union AlignUnion { $($T),+ }` element type if a >16-aligned @@ -97,6 +113,17 @@ macro_rules! new_store { next: Option>, } + // Zig: `fn supportsType(T: type) bool` + the + // `comptime if (!supportsType(T)) @compileError(...)` guard in + // `allocate` — only the types listed in this `new_store!` + // invocation may be allocated from this store. `allocate`/`append` + // are bounded on `StoredIn`, so an unsupported type is a + // compile error. + $( + impl $crate::new_store::sealed::Sealed for $T {} + impl $crate::new_store::StoredIn for $T {} + )+ + impl Block { pub const SIZE: usize = BLOCK_SIZE; @@ -139,7 +166,8 @@ macro_rules! new_store { // SAFETY: `start` is in-bounds (checked above) and aligned for T // (align_forward above). Buffer base alignment must be >= align_of::() - // — see TODO(port) on Block re: LARGEST_ALIGN. + // — guaranteed by `repr(align(16))` on Block plus the + // `LARGEST_ALIGN <= 16` const assert above. Some(unsafe { NonNull::new_unchecked( block.buffer.as_mut_ptr().add(start).cast::(), @@ -257,10 +285,8 @@ macro_rules! new_store { store.current = head_ptr; } - fn allocate(store: &mut Store) -> NonNull { + fn allocate>(store: &mut Store) -> NonNull { debug_assert!(size_of::() > 0); // don't allocate! - // TODO(port): `comptime if (!supportsType(T)) @compileError(...)` — - // enforce via a sealed trait generated over `$($T),+`. // Lazily materialise the first `Block` on first use — this is // the only `~BLOCK_SIZE` allocation a never-written store @@ -304,7 +330,10 @@ macro_rules! new_store { } #[inline] - pub fn append(store: &mut Store, data: T) -> NonNull { + pub fn append>( + store: &mut Store, + data: T, + ) -> NonNull { let ptr = Store::allocate::(store); /* scoped_log elided — debug_logs feature only */ // SAFETY: `allocate` returned aligned, in-bounds, exclusive storage for T. @@ -332,9 +361,8 @@ macro_rules! new_store { let _ = store; } - // Zig: `fn supportsType(T: type) bool` - // TODO(port): comptime type-list membership check; replace with sealed - // trait `Stored` impl'd for each `$($T),+` and bound `allocate`. + // Zig: `fn supportsType(T: type) bool` — see the + // `StoredIn` impls generated above `impl Block`. } } }; @@ -481,7 +509,7 @@ macro_rules! thread_local_ast_store { } #[inline] - pub fn append(value: T) -> $crate::StoreRef { + pub fn append>(value: T) -> $crate::StoreRef { if let Some(ma) = MEMORY_ALLOCATOR.get() { // `BackRef: Deref` — owning scope outlives this call. return ma.append(value); diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index c6f67e8f68b6..fcf1feec3f13 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -1114,7 +1114,9 @@ pub enum PartTag { } // Zig: std.ArrayHashMapUnmanaged(Ref, Symbol.Use, RefHashCtx, false) -// TODO(port): bun_collections::ArrayHashMap must accept a custom hasher ctx (RefHashCtx). +// `AutoContext` hashes the whole `Ref` instead of Zig's `RefHashCtx` +// (which hashed the packed bits directly) — semantics are identical, only +// the hash function differs. pub type PartSymbolUseMap = ArrayHashMap; pub type PartSymbolPropertyUseMap = ArrayHashMap< Ref, @@ -1158,7 +1160,7 @@ impl StmtOrExpr { StmtOrExpr::Expr(expr) => expr, StmtOrExpr::Stmt(stmt) => match stmt.data { crate::stmt::Data::SFunction(mut s) => { - // PORT NOTE: Zig moved `func.func` out by value; StoreRef arena + // Zig moved `func.func` out by value; the StoreRef arena // slot is never individually dropped, so `take` (replace with // Default) is the safe Rust equivalent. let func = core::mem::take(&mut s.func); @@ -1275,8 +1277,8 @@ impl Batcher { where T: Default, { - // TODO(port): bumpalo alloc_slice for uninit T — Zig `arena.alloc(Type, count)`. - // PERF(port): Zig left the slice uninitialized; bumpalo requires Default fill. + // PERF(port): Zig `arena.alloc(Type, count)` left the slice + // uninitialized; bumpalo requires Default fill. let all = bump.alloc_slice_fill_default(count); Ok(Self { head: StoreSlice::new_mut(all), @@ -1288,8 +1290,8 @@ impl Batcher { } pub fn eat(&mut self, value: T) -> *mut T { - // PORT NOTE: Zig source `@ptrCast(&this.head.eat1(value).ptr)` appears to - // intend `this.eat1(value).ptr` cast to *T. Porting the apparent intent. + // Zig source `@ptrCast(&this.head.eat1(value).ptr)` appears to + // intend `this.eat1(value).ptr` cast to *T; this ports the apparent intent. self.eat1(value).as_ptr().cast_mut() } diff --git a/src/ast/op.rs b/src/ast/op.rs index 62f4f3b4c3ef..662f804b97fe 100644 --- a/src/ast/op.rs +++ b/src/ast/op.rs @@ -263,8 +263,6 @@ impl Default for Op { } impl Op { - // PORT NOTE: Zig `init(triple: anytype)` took an anonymous tuple .{text, level, is_keyword} - // and accessed .@"0"/.@"1"/.@"2". Flattened to positional params. pub const fn init(text: &'static [u8], level: Level, is_keyword: bool) -> Op { Op { text, diff --git a/src/ast/runtime.rs b/src/ast/runtime.rs index b234450197fd..07ef0ddbfbc6 100644 --- a/src/ast/runtime.rs +++ b/src/ast/runtime.rs @@ -33,8 +33,10 @@ impl Runtime { pub enum ReplaceableExport { Delete, Replace(Expr), + /// Zig's `name: string` borrowed a transpiler-owned buffer; the Rust + /// variant owns the bytes (constructed from an owned slice in + /// `JSTranspiler`; the parser copies into its bump arena when consuming). Inject { name: Box<[u8]>, value: Expr }, - // TODO(port): `name` was `string` (= []const u8). Ownership unclear; using Box<[u8]>. } impl ReplaceableExport { @@ -339,9 +341,9 @@ impl Imports { } } - /// Zig: `contains(imports, comptime key: string)`. - // TODO(port): comptime-string key — Rust callers should access the field directly - // (`imports.__foo.is_some()`). Runtime fallback provided for parity. + /// Zig: `contains(imports, comptime key: string)`. The Zig key was + /// comptime; callers that know the key statically can read the field + /// directly (`imports.__foo.is_some()`) — this is the runtime equivalent. pub fn contains(&self, key: &[u8]) -> bool { Self::ALL .iter() @@ -359,8 +361,9 @@ impl Imports { false } - /// Zig: `put(imports, comptime key: string, ref: Ref)`. - // TODO(port): comptime-string key — Rust callers should assign the field directly. + /// Zig: `put(imports, comptime key: string, ref: Ref)`. The Zig key was + /// comptime; callers that know the key statically can assign the field + /// directly — this is the runtime equivalent. pub fn put(&mut self, key: &[u8], ref_: Ref) { if let Some(i) = Self::ALL.iter().position(|&k| k == key) { if let Some(slot) = self.field_mut(i) { @@ -369,8 +372,9 @@ impl Imports { } } - /// Zig: `at(imports, comptime key: string) ?Ref`. - // TODO(port): comptime-string key — Rust callers should read the field directly. + /// Zig: `at(imports, comptime key: string) ?Ref`. The Zig key was + /// comptime; callers that know the key statically can read the field + /// directly — this is the runtime equivalent. pub fn at(&self, key: &[u8]) -> Option { Self::ALL .iter() diff --git a/src/ast/scope.rs b/src/ast/scope.rs index 74672d9f992a..d28d3b96cce1 100644 --- a/src/ast/scope.rs +++ b/src/ast/scope.rs @@ -16,7 +16,7 @@ use crate::ts::TSNamespaceScope; /// never runs, every member map leaked. pub(crate) type MemberHashMap = StringHashMap; -// PORT NOTE: Zig `Scope` is a value type — `Ast.module_scope` / `BundledAst.module_scope` +// `Scope` is a value type — `Ast.module_scope` / `BundledAst.module_scope` // hold it by value and `toAST` / `init` bitwise-copy it (`this.module_scope`). Vec no // longer derives `Clone` (private `origin` field); callers that need a shallow copy must // `core::mem::take` or `core::ptr::read` instead. diff --git a/src/ast/server_component_boundary.rs b/src/ast/server_component_boundary.rs index 9528108ff2ec..b905fceb8b8b 100644 --- a/src/ast/server_component_boundary.rs +++ b/src/ast/server_component_boundary.rs @@ -75,9 +75,9 @@ impl List { reference_source_index, ssr_source_index, })?; - // PORT NOTE: reshaped for borrowck — Zig built `Adapter` from - // `m.list.slice()` while also borrowing `m.map` mutably. Here we hand - // the adapter just the `source_index` column it needs. + // The Zig original built `Adapter` from `m.list.slice()` while also + // borrowing `m.map` mutably; for borrowck we hand the adapter just + // the `source_index` column it needs. let gop = self.map.get_or_put_adapted( &source_index, &Adapter { @@ -148,7 +148,7 @@ impl<'a> Slice<'a> { } } -// PORT NOTE: Zig stored the full `MultiArrayList.Slice` and called +// The Zig original stored the full `MultiArrayList.Slice` and called // `.items(.source_index)` on each compare. The Rust `Slice` is not `Copy`, // so we cache just the `source_index` column the adapter actually needs. pub(crate) struct Adapter<'a> { diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index defe492bb319..1dcdfb2d3cff 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -28,10 +28,12 @@ impl Stmt { data::Store::create(); } - /// Zig: `Stmt.Data.Store.assert()` — debug-only re-entrancy guard. + /// Zig: `Stmt.Data.Store.assert()` — debug-only "Store must be init'd" + /// guard (stmt.zig:120). The re-entrancy `Disabler` check lives in + /// `Store::append`, mirroring stmt.zig:166. #[inline] pub fn data_store_assert() { - crate::DebugOnlyDisabler::::assert(); + data::Store::assert(); } pub fn assign(a: Expr, b: Expr) -> Stmt { @@ -95,7 +97,7 @@ impl Default for Stmt { const NONE: S::Empty = S::Empty {}; -// PORT NOTE: Zig `pub var icount: usize = 0;` is a plain mutable global (not +// Zig `pub var icount: usize = 0;` is a plain mutable global (not // threadlocal), never read. Debug-only here so release doesn't pay a contended // `lock xadd` per Stmt across the bundler worker pool. #[cfg(debug_assertions)] @@ -210,7 +212,10 @@ macro_rules! impl_statement_data { } #[inline] fn arena_alloc(self, bump: &bun_alloc::Arena) -> Data { - // TODO(port): StoreRef vs &'bump — unify the arena ref type. + // `StoreRef::from_bump` is the settled crate-wide arena-ref + // convention (see its docs in nodes.rs); expr.rs + // `arena_alloc` does the same. No separate `&'bump` ref + // type is planned. Data::$variant(StoreRef::from_bump(bump.alloc(self))) } } @@ -991,10 +996,10 @@ pub mod data { crate::thread_local_ast_store!(stmt_store::Store, "Stmt"); } -// Zig `pub fn StoredData(tag: Tag) type` — returns the payload type for a tag, -// dereferencing pointer variants. Rust has no type-returning fns. -// TODO(port): callers should use the `StatementData` trait or a per-variant -// associated type; revisit once call sites are known. +// Zig `pub fn StoredData(tag: Tag) type` — comptime type-level function +// returning the payload type for a tag (dereferencing pointer variants). +// Rust has no type-returning fns and no caller needs it; the +// `StatementData` trait covers the per-payload dispatch. impl Stmt { pub fn cares_about_scope(&self) -> bool { diff --git a/src/ast/symbol.rs b/src/ast/symbol.rs index aef56f39de74..5721f55c2243 100644 --- a/src/ast/symbol.rs +++ b/src/ast/symbol.rs @@ -166,11 +166,9 @@ pub struct Symbol { pub has_been_assigned_to: bool, } -// TODO(port): Zig asserts @sizeOf(Symbol) == 88 and @alignOf(Symbol) == @alignOf([]const u8). -// Rust default repr reorders fields and Option niche may differ -// (likely needs #[repr(C)] or manual packing if the size is load-bearing). -// const _: () = assert!(core::mem::size_of::() == 88); -// const _: () = assert!(core::mem::align_of::() == core::mem::align_of::()); +// Zig asserted @sizeOf(Symbol) == 88 as a layout-regression tripwire; the size +// was never load-bearing (no FFI, no serialization). Rust's default repr packs +// the struct differently, so the assert is intentionally not ported. const INVALID_CHUNK_INDEX: u32 = u32::MAX; pub const INVALID_NESTED_SCOPE_SLOT: u32 = u32::MAX; @@ -583,7 +581,7 @@ impl Map { } } - // PORT NOTE: Zig aliased the caller's stack `[1]List` slot directly; that's + // Zig aliased the caller's stack `[1]List` slot directly; that's // unsound in Rust (would dangle on return). Take ownership of `list` and // box it into a one-element NestedList instead. // PERF(port): one extra allocation vs Zig — profile (single @@ -633,7 +631,9 @@ impl Map { } pub fn follow_all(&mut self) { - // TODO(port): bun_perf::trace("Symbols.followAll") — RAII guard + // Zig: `const trace = bun.perf.trace("Symbols.followAll"); defer trace.end();` + // — the returned `Ctx` is RAII and ends the span on drop. + let _trace = bun_perf::trace(bun_perf::PerfEvent::SymbolsFollowAll); // `link` is `Cell`, so we can iterate the table by shared ref and // mutate `link` in place; `follow()` only takes `&self` and only touches // `link`, so the nested shared borrows coexist. @@ -650,7 +650,7 @@ impl Map { /// Equivalent to followSymbols in esbuild. /// - /// PORT NOTE: Zig's body is naturally recursive (`follow(symbol.link)`). + /// Zig's body is naturally recursive (`follow(symbol.link)`). /// Reshaped to an iterative two-phase walk so the per-hop work is just two /// raw pointer adds and a load — no call frame, no `Option` unwrap, no /// repeated tag/null guards. Semantics are identical to Zig's: every node diff --git a/src/ast/target.rs b/src/ast/target.rs index 6554d2ffc435..6124540a489b 100644 --- a/src/ast/target.rs +++ b/src/ast/target.rs @@ -96,9 +96,8 @@ impl Target { } pub fn default_conditions(self) -> &'static [&'static [u8]] { - // PORT NOTE: Zig `default_conditions` is `std.EnumArray(Target, []const string)` - // — `string` is `[]const u8`. Callers (`ESMConditions::init`) take byte - // slices, so surface bytes directly rather than `&str`. + // Callers (`ESMConditions::init`) take byte slices, so surface + // bytes directly rather than `&str`. match self { Target::Node => &[b"node"], Target::Browser => &[b"browser", b"module"], diff --git a/src/ast/ts.rs b/src/ast/ts.rs index b528bf9f4818..1acd6d47f20a 100644 --- a/src/ast/ts.rs +++ b/src/ast/ts.rs @@ -37,8 +37,9 @@ use crate::e::String as EString; /// hierarchical scope-based identifier lookup in JavaScript. Lookup now needs /// to search sibling scopes in addition to parent scopes. This is accomplished /// by sharing the map of exported members between all matching sibling scopes. -// PORT NOTE: 'arena lifetime dropped — `EnumString` payload is a `StoreRef` -// rather than an arena-borrowed reference (see the TODO on that variant). +// The Zig 'arena lifetime is dropped here — the `EnumString` payload is a +// `StoreRef` (AST-store back-pointer) rather than an arena-borrowed +// reference, matching the rest of the AST crate. pub struct TSNamespaceScope { /// This is specific to this namespace block. It's the argument of the /// immediately-invoked function expression that the namespace block is @@ -131,7 +132,6 @@ pub enum Data { EnumNumber(f64), /// "enum ns { it = 'it' }" // LIFETIMES.tsv: ARENA — assigned from Expr.Data.e_string payload (AST Expr store). - // TODO(port): &'bump EString once 'bump threaded crate-wide. EnumString(crate::nodes::StoreRef), /// "enum ns { it = something() }" EnumProperty, @@ -139,8 +139,8 @@ pub enum Data { impl Data { pub fn is_enum(&self) -> bool { - // PORT NOTE: Zig used `inline else` + comptime `@tagName` prefix check ("enum_"). - // Expanded to an explicit match over the enum_* variants. + // Zig used `inline else` + comptime `@tagName` prefix check ("enum_"); + // expanded to an explicit match over the enum_* variants. matches!( self, Data::EnumNumber(_) | Data::EnumString(_) | Data::EnumProperty @@ -174,9 +174,9 @@ pub enum Metadata { MSymbol, MPromise, MIdentifier(Ref), - // TODO(port): Zig used `std.ArrayListUnmanaged(Ref)`. This is an AST crate; - // if this list is arena-backed in practice, switch to - // `bun_alloc::ArenaVec<'bump, Ref>`. + // Zig used `std.ArrayListUnmanaged(Ref)` (parser-arena-backed); a heap + // `Vec` is used here because `Metadata` is lifetime-free. Decorator + // metadata is rare and the lists are tiny. MDot(Vec), } @@ -223,7 +223,7 @@ impl Metadata { _ => Metadata::MObject, }; } else { - // PORT NOTE: reshaped for borrowck — copy Ref out before reassigning *result + // Reshaped for borrowck — copy Ref out before reassigning *result if let Metadata::MIdentifier(r) = result { let r = *r; if let Metadata::MIdentifier(l) = left { @@ -282,7 +282,7 @@ impl Metadata { _ => Metadata::MObject, }; } else { - // PORT NOTE: reshaped for borrowck — copy Ref out before reassigning *result + // Reshaped for borrowck — copy Ref out before reassigning *result if let Metadata::MIdentifier(r) = result { let r = *r; if let Metadata::MIdentifier(l) = left { diff --git a/src/base64/lib.rs b/src/base64/lib.rs index 2775f4b6a5bd..871cd5928f0f 100644 --- a/src/base64/lib.rs +++ b/src/base64/lib.rs @@ -6,7 +6,7 @@ pub use zig_base64::STANDARD_ALPHABET_CHARS; const VT: u8 = 0x0B; // std.ascii.control_code.vt const FF: u8 = 0x0C; // std.ascii.control_code.ff -// PORT NOTE: Zig evaluates this at comptime; const-initialized static lands in `.rodata` +// Const-initialized static lands in `.rodata` // (no `Once` atomic on the `Integrity::parse` hot path). static MIXED_DECODER: zig_base64::Base64DecoderWithIgnore = { let mut decoder = @@ -124,7 +124,6 @@ pub fn encode_alloc(source: &[u8]) -> Vec { let len = encode_len(source); let mut destination = vec![0u8; len]; let encoded_len = encode(&mut destination, source); - // PORT NOTE: Zig built Vec from ptr/len/cap; here Vec already carries cap == len. destination.truncate(encoded_len); destination } @@ -236,8 +235,8 @@ pub mod vlq { &self.bytes[0..self.len as usize] } - // PORT NOTE: Zig took `writer: anytype`. `std::io::Write` is used as the - // byte-sink trait; base64 stays a tier-0 leaf with no bun_io dep. + // `std::io::Write` is used as the byte-sink trait so base64 stays a + // tier-0 leaf with no bun_io dep. pub fn write_to(self, writer: &mut impl std::io::Write) -> Result<(), bun_core::Error> { writer.write_all(&self.bytes[0..self.len as usize])?; Ok(()) @@ -439,7 +438,7 @@ pub mod zig_base64 { } /// Standard Base64 codecs, with padding - // PORT NOTE: Zig comptime → const-initialized `static` (lives in `.rodata`, no `Once`). + // Const-initialized `static` (lives in `.rodata`, no `Once`). pub static STANDARD: Codecs = Codecs { alphabet_chars: STANDARD_ALPHABET_CHARS, pad_char: Some(b'='), @@ -460,9 +459,6 @@ pub mod zig_base64 { pub(crate) const URL_SAFE_ALPHABET_CHARS: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; - // PORT NOTE: dropped `standard_pad_char`/`standard_encoder`/`standard_decoder` - // @compileError deprecation stubs — no Rust equivalent for use-site compile errors. - #[derive(Copy, Clone)] pub struct Base64Encoder { pub alphabet_chars: [u8; 64], @@ -516,7 +512,6 @@ pub mod zig_base64 { } pub fn encode_without_size_check(&self, dest: &mut [u8], source: &[u8]) -> usize { - // PORT NOTE: Zig used u12/u4; Rust uses u16/u8 with explicit masking. let mut acc: u16 = 0; let mut acc_len: u8 = 0; let mut out_idx: usize = 0; @@ -613,7 +608,6 @@ pub mod zig_base64 { if self.pad_char.is_some() && !source.len().is_multiple_of(4) { return Err(Error::InvalidPadding); } - // PORT NOTE: Zig used u12/u4; Rust uses u16/u8 with explicit masking. let mut acc: u16 = 0; let mut acc_len: u8 = 0; let mut dest_idx: usize = 0; @@ -712,11 +706,10 @@ pub mod zig_base64 { wrote: &mut usize, ) -> Result<(), Error> { let decoder = &self.decoder; - // PORT NOTE: Zig used u12/u4; Rust uses u16/u8 with explicit masking. let mut acc: u16 = 0; let mut acc_len: u8 = 0; - // PORT NOTE: reshaped `defer { wrote.* = dest_idx; }` into direct mutation - // of `*wrote` so it is always current on every return path. + // `*wrote` is mutated directly (rather than once before return) + // so it is always current on every return path. *wrote = 0; let mut leftover_idx: Option = None; @@ -991,8 +984,7 @@ pub fn wyhash_url_safe<'a>( // PERF(port): was stack-fallback alloc (StackFallbackAllocator 128B) — profile if hot. let mut hasher = bun_wyhash::Wyhash11::init(0); - // PORT NOTE: std.fmt.count + allocPrint collapsed; write into a scratch - // Vec then hash. Freed immediately (Zig used stack-fallback for this). + // Write into a scratch Vec then hash; freed immediately. let mut fmt_str: Vec = Vec::with_capacity(128); write!(&mut fmt_str, "{}", args).expect("unreachable"); hasher.update(&fmt_str); @@ -1002,8 +994,8 @@ pub fn wyhash_url_safe<'a>( let encode_len = simdutf_encode_len_url_safe(h_bytes.len()); - // PORT NOTE: Zig reused fmt_str buffer when encode_len > 128 - at_start; arena makes the - // distinction moot (both arms allocate from bump). Always alloc fresh slice here. + // The Zig original reused the fmt_str buffer when encode_len > 128 - at_start; the arena + // makes the distinction moot (both arms allocate from bump). Always alloc a fresh slice. // PERF(port): was buffer reuse for large encode_len — profile if hot. let slice_to_write: &mut [u8] = bump.alloc_slice_fill_default(encode_len + usize::from(at_start)); diff --git a/src/brotli/lib.rs b/src/brotli/lib.rs index debf1039bde9..a0601544819c 100644 --- a/src/brotli/lib.rs +++ b/src/brotli/lib.rs @@ -25,7 +25,6 @@ pub struct DecoderOptions { /// Zig: `std.enums.EnumFieldStruct(c.BrotliDecoderParameter, bool, false)` — /// one `bool` per `BrotliDecoderParameter` variant, default `false`. -// TODO(port): if BrotliDecoderParameter grows more variants, mirror them here. #[derive(Default)] pub struct DecoderParams { pub large_window: bool, @@ -49,7 +48,7 @@ impl Default for DecoderOptions { pub struct BrotliReaderArrayList<'a> { pub input: &'a [u8], - // PORT NOTE: reshaped for borrowck — Zig kept a by-value copy of the + // Reshaped for borrowck — Zig kept a by-value copy of the // ArrayListUnmanaged in `list` and wrote it back to `*list_ptr` on every // `readAll` (defer). `Vec` is not `Copy`, so we operate on `list_ptr` // directly and drop the redundant `list` + `list_allocator` fields. @@ -98,7 +97,6 @@ impl<'a> BrotliReaderArrayList<'a> { list: &'a mut Vec, options: &DecoderOptions, ) -> Result, Error> { - // TODO(port): narrow error set Ok(Self::new(Self::init_with_options( input, list, @@ -117,7 +115,6 @@ impl<'a> BrotliReaderArrayList<'a> { finish_flush_op: c::BrotliEncoderOperation, full_flush_op: c::BrotliEncoderOperation, ) -> Result { - // TODO(port): narrow error set if !BrotliDecoder::initialize_brotli() { return Err(err!("BrotliFailedToLoad")); } @@ -166,8 +163,7 @@ impl<'a> BrotliReaderArrayList<'a> { } pub fn read_all(&mut self, is_done: bool) -> Result<(), Error> { - // TODO(port): narrow error set - // PORT NOTE: Zig's `defer this.list_ptr.* = this.list;` is gone — we + // Zig's `defer this.list_ptr.* = this.list;` is gone — we // mutate through `list_ptr` directly (see field note above). if self.state == ReaderState::End || self.state == ReaderState::Error { @@ -266,7 +262,7 @@ impl<'a> Drop for BrotliReaderArrayList<'a> { // Created by BrotliDecoder::create_instance; destroyed exactly once here. BrotliDecoder::destroy_instance(self.brotli_mut()); } - // PORT NOTE: Zig's `bun.destroy(this)` is implicit — callers hold a + // Zig's `bun.destroy(this)` is implicit — callers hold a // `Box` and dropping it frees the allocation. } } @@ -298,7 +294,6 @@ impl BrotliCompressionStream { finish_flush_op: c::BrotliEncoderOperation, full_flush_op: c::BrotliEncoderOperation, ) -> Result { - // TODO(port): narrow error set // SAFETY: brotli FFI constructor; alloc/free are valid extern "C" // fns and opaque is null (unused by our allocator). let instance = unsafe { @@ -336,7 +331,6 @@ impl BrotliCompressionStream { // next compress_stream/destroy call. Tying it to `&mut self` prevents // overlapping calls that would invalidate it. pub fn write_chunk(&mut self, input: &[u8], last: bool) -> Result<&[u8], Error> { - // TODO(port): narrow error set self.total_in += input.len(); let op = if last { self.finish_flush_op @@ -360,7 +354,6 @@ impl BrotliCompressionStream { } pub fn write(&mut self, input: &[u8], last: bool) -> Result<&[u8], Error> { - // TODO(port): narrow error set if self.state == CompressionState::End || self.state == CompressionState::Error { return Ok(b""); } @@ -369,9 +362,8 @@ impl BrotliCompressionStream { } pub fn end(&mut self) -> Result<&[u8], Error> { - // TODO(port): narrow error set // Zig: `defer this.state = .End` — runs on BOTH ok and error paths. - // PORT NOTE: reshaped for borrowck — `compress_stream`'s output borrows + // Reshaped for borrowck — `compress_stream`'s output borrows // `&mut *self.brotli`, so we set `self.state` first and inline // write/write_chunk("", true). Net state matches Zig (defer overrides // any intermediate `Error` back to `End`). @@ -395,8 +387,9 @@ impl BrotliCompressionStream { BrotliWriter::init(self, writable) } - // TODO(port): Zig's `writer()` returned a `std.Io.GenericWriter` adapter. - // Rust callers should use `writer_context()` directly (it impls Write). + // Zig's `writer()` returned a `std.Io.GenericWriter` adapter; in Rust the + // returned `BrotliWriter` implements `bun_io::Write` itself, so this is + // just an alias for `writer_context()`. pub fn writer(&mut self, writable: W) -> BrotliWriter<'_, W> { self.writer_context(writable) } @@ -419,7 +412,8 @@ pub struct BrotliWriter<'a, W> { impl<'a, W: bun_io::Write> BrotliWriter<'a, W> { // Zig: `WriteError = error{BrotliCompressionError} || InputWriter.Error` - // PORT NOTE: error-set union collapsed to `bun_core::Error`. + // — the error-set union is collapsed to `bun_core::Error` (error names + // are preserved through `err!`), like the rest of this file. pub fn init(compressor: &'a mut BrotliCompressionStream, input_writer: W) -> Self { Self { @@ -435,15 +429,30 @@ impl<'a, W: bun_io::Write> BrotliWriter<'a, W> { } pub fn end(&mut self) -> Result<(), Error> { - // PORT NOTE: Zig declared `!usize` but the body has no return — the + // Zig declared `!usize` but the body has no return — the // Zig fn would fail to compile if ever instantiated. Port as `()`. let decompressed = self.compressor.end()?; self.input_writer.write_all(decompressed)?; Ok(()) } +} + +// Zig: `std.Io.GenericWriter` adapter returned by `writer()` — in Rust the +// `BrotliWriter` is the writer, so it implements `bun_io::Write` directly. +impl bun_io::Write for BrotliWriter<'_, W> { + fn write_all(&mut self, buf: &[u8]) -> Result<(), Error> { + self.write(buf).map(|_| ()) + } - // TODO(port): `std.Io.GenericWriter` adapter — provide `impl bun_io::Write` - // if any caller needs the trait object. + fn flush(&mut self) -> Result<(), Error> { + // Drain the encoder first so compressed-so-far bytes reach the sink: + // an empty write runs `compress_stream` with the stream's configured + // `flush_op` (emits pending output for FLUSH-configured streams; a + // no-op for PROCESS). `end()` is still required to finalize. + let out = self.compressor.write(b"", false)?; + self.input_writer.write_all(out)?; + self.input_writer.flush() + } } // ported from: src/brotli/brotli.zig diff --git a/src/brotli_sys/brotli_c.rs b/src/brotli_sys/brotli_c.rs index a8c783203344..1852d5a445f2 100644 --- a/src/brotli_sys/brotli_c.rs +++ b/src/brotli_sys/brotli_c.rs @@ -109,7 +109,6 @@ impl BrotliDecoder { decoded_ptr, ) }; - // PORT NOTE: reshaped for borrowck — Zig mutated `decoded.len` in place via `*[]u8` // SAFETY: decoded_ptr points to the same allocation; decoded_size <= original len per brotli contract *decoded = unsafe { core::slice::from_raw_parts_mut(decoded_ptr, decoded_size) }; result @@ -389,8 +388,8 @@ impl<'a> Default for CompressionResult<'a> { } } -// PORT NOTE: Zig's `BrotliEncoder.Operation` flattened to module-level alias -// (inherent associated types are unstable in Rust). +// `BrotliEncoder.Operation` is a module-level alias because inherent +// associated types are unstable in Rust. pub type Operation = BrotliEncoderOperation; impl BrotliEncoder { diff --git a/src/bun.js.rs b/src/bun.js.rs index 971b9d5d46db..3371ab0dba26 100644 --- a/src/bun.js.rs +++ b/src/bun.js.rs @@ -14,7 +14,6 @@ use bun_standalone_graph::StandaloneModuleGraph::{Flags as GraphFlags, Standalon // Thin re-exports (mirrors `pub const X = @import(...)` at file top). pub use crate::api; pub use crate::webcore; -pub use bun_jsc as jsc_mod; // TODO(port): naming — Zig exposed this as `bun.js.jsc` /// Canonical `Run` lives in `cli::run_command`; re-export so callers that /// expect `bun.js.Run` resolve to the single definition. diff --git a/src/bun_alloc/MaxHeapAllocator.rs b/src/bun_alloc/MaxHeapAllocator.rs index 71546a6b600c..daf7682df39e 100644 --- a/src/bun_alloc/MaxHeapAllocator.rs +++ b/src/bun_alloc/MaxHeapAllocator.rs @@ -78,9 +78,8 @@ impl MaxHeapAllocator { MaxHeapScope { inner: self } } - // PORT NOTE: reshaped out-param constructor. Zig's `init(self: *Self, allocator) -> std.mem.Allocator` - // both initialized `self` and returned a vtable+ptr pair. In Rust the caller constructs - // `MaxHeapAllocator::init()` and obtains `&dyn Allocator` by borrowing the result. + // The caller constructs `MaxHeapAllocator::init()` and obtains + // `&dyn Allocator` by borrowing the result. pub fn init() -> Self { Self { ptr: None, diff --git a/src/bun_alloc/NullableAllocator.rs b/src/bun_alloc/NullableAllocator.rs index f3ee890f10c4..2bfe73b8184d 100644 --- a/src/bun_alloc/NullableAllocator.rs +++ b/src/bun_alloc/NullableAllocator.rs @@ -4,10 +4,10 @@ use core::ffi::c_void; use crate::{Alignment, AllocatorVTable, StdAllocator}; -/// PORT NOTE: Zig stored `{ ptr: *anyopaque, vtable: ?*const VTable }` and -/// recovered the `Allocator` by null-checking the vtable. Rust models the same -/// thing directly — `vtable: Option<&'static AllocatorVTable>` carries the -/// niche, so the struct is identical in size to `StdAllocator`. +/// Zig stored `{ ptr: *anyopaque, vtable: ?*const VTable }` and recovered the +/// `Allocator` by null-checking the vtable. Rust models the same thing +/// directly — `vtable: Option<&'static AllocatorVTable>` carries the niche, +/// so the struct is identical in size to `StdAllocator`. #[derive(Clone, Copy)] pub struct NullableAllocator { ptr: *mut c_void, diff --git a/src/bun_alloc/fallback/z.rs b/src/bun_alloc/fallback/z.rs index 5fae0a219819..4bf2cb7788a6 100644 --- a/src/bun_alloc/fallback/z.rs +++ b/src/bun_alloc/fallback/z.rs @@ -44,8 +44,6 @@ impl Z { if !c_allocator.raw_resize(buf, alignment, new_len, return_address) { return false; } - // PORT NOTE: reshaped for borrowck — capture len before re-deriving the - // tail pointer (Zig: `buf.ptr[buf.len..new_len]`). let old_len = buf.len(); // Only zero on grow. On shrink (`new_len < old_len`), `new_len - old_len` // would underflow to ~usize::MAX and `write_bytes` would corrupt the heap. diff --git a/src/bun_alloc/heap_breakdown.rs b/src/bun_alloc/heap_breakdown.rs index d04b2709245d..45711f92fb43 100644 --- a/src/bun_alloc/heap_breakdown.rs +++ b/src/bun_alloc/heap_breakdown.rs @@ -7,7 +7,8 @@ use core::ffi::{c_int, c_uint}; type vm_size_t = usize; // Environment.allow_assert and Environment.isMac and !Environment.enable_asan -// TODO(port): `enable_asan` mapped to a cargo feature; verify the build wires this the same way. +// (`bun_asan` is set via RUSTFLAGS `--cfg=bun_asan` in scripts/build/rust.ts, +// matching Zig's `enable_asan` build option.) pub const ENABLED: bool = cfg!(debug_assertions) && cfg!(target_os = "macos") && !cfg!(bun_asan); /// Zig: `pub fn getZone(comptime name: [:0]const u8) *Zone` @@ -20,8 +21,6 @@ pub const ENABLED: bool = cfg!(debug_assertions) && cfg!(target_os = "macos") && /// Runtime `getZone(name)` — looks up (or creates) the per-name zone. The /// `get_zone!` macro is the zero-cost form. This runtime path keys a /// process-global map for callers that pass a non-literal name. -// TODO(port): could be replaced with a `#[heap_label]` derive that expands -// `get_zone!` directly. #[allow(clippy::assertions_on_constants)] pub fn get_zone(name: &[u8]) -> &'static Zone { debug_assert!( @@ -154,9 +153,6 @@ impl Zone { // `impl crate::Allocator for Zone` (see below); the raw vtable struct is a // Zig-ism and is not materialized here, so the `resize`/`free` vtable thunks // (and the `malloc_size` helper they used) are not ported. - // TODO(port): if `bun_alloc::Allocator` ever becomes a literal vtable struct - // (to match `std.mem.Allocator` ABI), reintroduce a `pub static VTABLE` - // along with the `resize`/`raw_free` thunks. /// Zig: `pub fn allocator(zone: *Zone) std.mem.Allocator` pub fn allocator(&'static self) -> &'static dyn crate::Allocator { @@ -174,7 +170,7 @@ impl Zone { #[inline] pub fn try_create(&self, data: T) -> Result<*mut T, crate::AllocError> { let alignment = core::mem::align_of::(); - // TODO(port): Zig passed `@returnAddress()` as the ret_addr hint; Rust has no + // Zig passed `@returnAddress()` as the ret_addr hint; Rust has no // stable equivalent. Passing 0 — the macOS zone API ignores it anyway. let raw = Zone::raw_alloc( // SAFETY: vtable context pointer — `as_mut_ptr()` yields the @@ -216,8 +212,8 @@ impl Zone { // for `is_instance` identity checks. impl crate::Allocator for Zone {} -// TODO(port): move to bun_alloc_sys (or keep here gated `#[cfg(target_os = "macos")]` -// since these are macOS-only libc symbols). +// macOS-only libmalloc symbols, kept here (gated on `target_os = "macos"`) +// since heap_breakdown is their only consumer. #[cfg(target_os = "macos")] unsafe extern "C" { /// No preconditions; returns the process default malloc zone. diff --git a/src/bun_alloc/lib.rs b/src/bun_alloc/lib.rs index 4227c90a4b3b..c30f2765dc31 100644 --- a/src/bun_alloc/lib.rs +++ b/src/bun_alloc/lib.rs @@ -592,7 +592,7 @@ pub fn trim<'a>(s: &'a [u8], chars: &[u8]) -> &'a [u8] { /// to a byte-at-a-time `to_ascii_lowercase` zip. pub fn copy_lowercase<'a>(in_: &[u8], out: &'a mut [u8]) -> &'a [u8] { let mut in_slice = in_; - // PORT NOTE: reshaped for borrowck — track output offset instead of reslicing &mut. + // Reshaped for borrowck — track output offset instead of reslicing &mut. let mut out_off: usize = 0; 'begin: loop { @@ -2275,9 +2275,8 @@ impl OverflowGroup { // OverflowList // ────────────────────────────────────────────────────────────────────────── -// TODO(port): const-generic arithmetic (`[ValueType; COUNT]` inside a generic struct) requires -// `feature(generic_const_exprs)` on stable Rust. Pin COUNT per instantiation site -// or use a heap `Box<[ValueType]>` with debug_assert on len. +// Const-generic arithmetic (deriving COUNT from another const param) requires +// `feature(generic_const_exprs)` on stable Rust, so COUNT is pinned per instantiation site. pub struct OverflowListBlock { // Zig: `SizeType = std.math.IntFittingRange(0, count)`; use u32 here. @@ -2429,9 +2428,9 @@ impl OverflowList { /// We do keep a pointer to it globally, but because the data is not zero-initialized, it ends up /// taking space in the object file. We don't want to spend 1-2 MB on these structs. /// -/// TODO(port): const-generic arithmetic (`COUNT = _COUNT * 2`) and per-monomorphization -/// a raw mutable INSTANCE static are not expressible on stable Rust. Instantiate per use-site -/// via `macro_rules!` or pin concrete `COUNT` constants. +/// Const-generic arithmetic (`COUNT = _COUNT * 2`) and a per-monomorphization +/// raw mutable INSTANCE static are not expressible on stable Rust; callers +/// pin concrete `COUNT` constants per use-site. /// /// `#[repr(C)]` with the small mutated scalars (`mutex`, `head`, `used`, /// `tail`'s header) laid out *before* the giant `backing_buf` array. Storage @@ -2445,7 +2444,8 @@ impl OverflowList { pub struct BSSList { pub mutex: Mutex, // LIFETIMES.tsv: dual semantics — points at sibling `tail` OR a heap alloc. - // TODO(port): lifetime — keep raw NonNull; self-referential when `head == &self.tail`. + // Kept as a raw NonNull: self-referential when `head == &self.tail`, so a safe + // borrow cannot express it. pub head: Option>>, pub used: u32, pub tail: BSSListOverflowBlock, @@ -2733,7 +2733,7 @@ pub struct BSSListPair { /// Stores an initial count in .bss section of the object file. /// Overflows to heap when count is exceeded. /// -/// TODO(port): same const-generic-arithmetic and per-type-static caveats as `BSSList`. +/// Same const-generic-arithmetic and per-type-static caveats as `BSSList`. pub struct BSSStringList< const COUNT: usize, /* = _COUNT * 2 */ const ITEM_LENGTH: usize, /* = _ITEM_LENGTH + 1 */ @@ -3141,7 +3141,7 @@ impl BSSStringList` (the `store_keys = false` shape) // - `BSSMap` (the `store_keys = true` wrapper) -// TODO(port): callers that passed `store_keys=false` should name `BSSMapInner` directly. +// Callers that passed `store_keys=false` should name `BSSMapInner` directly. pub struct BSSMapInner { pub index: IndexMap, @@ -3343,9 +3343,9 @@ pub struct BSSMap< pub key_list_buffer: NonNull<[MaybeUninit]>, // len == COUNT * ESTIMATED_KEY_LENGTH pub key_list_buffer_used: usize, pub key_list_slices: NonNull<[MaybeUninit<&'static [u8]>]>, // len == COUNT - // TODO(port): Zig declares this as `OverflowList([]u8, count / 4)` but then calls + // Zig declares this as `OverflowList([]u8, count / 4)` but then calls // `.items[...]` and `.append(allocator, slice)` on it — those are `std.ArrayListUnmanaged` - // methods, NOT `OverflowList` methods. Likely dead code or a latent bug upstream. + // methods, NOT `OverflowList` methods (likely dead code or a latent bug upstream). // Ported as `Vec<&'static [u8]>` to match the *called* API. pub key_list_overflow: Vec<&'static [u8]>, } @@ -3429,7 +3429,7 @@ impl< // a process-lifetime mapping of `COUNT` slots. Some(unsafe { *self.key_list_slices.cast::<&'static [u8]>().as_ptr().add(i) }) } else { - // TODO(port): see key_list_overflow note — Zig indexes `.items` here. + // See the `key_list_overflow` field note — Zig indexes `.items` here. Some(self.key_list_overflow[index.index() as usize]) } } @@ -3442,7 +3442,7 @@ impl< result: &mut Result, value: ValueType, ) -> core::result::Result<&mut ValueType, AllocError> { - // PORT NOTE: reshaped for borrowck — Zig returns `ptr` from map.put then calls put_key; + // Reshaped for borrowck — Zig returns `ptr` from map.put then calls put_key; // Rust can't hold &mut ValueType across &mut self.put_key. Stash as raw, re-borrow after. let ptr: *mut ValueType = self.map_mut().put(result, value)?; if STORE_KEY { @@ -3530,7 +3530,7 @@ impl< .write(MaybeUninit::new(slice)); } } else { - // TODO(port): see key_list_overflow note above re: `.items` / `.append(alloc, _)`. + // See the `key_list_overflow` field note re: `.items` / `.append(alloc, _)`. let idx = result.index.index() as usize; if self.key_list_overflow.len() > idx { let existing_slice = self.key_list_overflow[idx]; diff --git a/src/bun_alloc/maybe_owned.rs b/src/bun_alloc/maybe_owned.rs index 65411d46868a..df628ec67d44 100644 --- a/src/bun_alloc/maybe_owned.rs +++ b/src/bun_alloc/maybe_owned.rs @@ -13,7 +13,7 @@ //! //! This type is a `GenericAllocator`; see `src/allocators.zig`. //! -//! PORT NOTE: Zig modelled this over `Nullable` / `Borrowed` allocator +//! The Zig original modelled this over `Nullable` / `Borrowed` allocator //! adaptors. With `#[global_allocator]`, "owned" reduces to "drop the box, //! borrowed = leak"; the generic allocator threading is dropped. The struct //! keeps the `Option` shape so callers that pattern-match on diff --git a/src/bun_alloc/memory.rs b/src/bun_alloc/memory.rs index bc97888d15d0..03671ec3db05 100644 --- a/src/bun_alloc/memory.rs +++ b/src/bun_alloc/memory.rs @@ -1,7 +1,7 @@ //! Basic utilities for working with memory and objects. // ────────────────────────────────────────────────────────────────────────────── -// PORT NOTE: `exemptedFromDeinit`, `deinitIsVoid`, and `deinit` are intentionally +// `exemptedFromDeinit`, `deinitIsVoid`, and `deinit` are intentionally // NOT ported as functions. // // Zig's `bun.memory.deinit(ptr_or_slice)` walked `@typeInfo` to: @@ -24,7 +24,7 @@ // port is not possible — and per §Idiom map, `deinit` definitions become `impl Drop` // on the target type, not a free function here. // -// TODO(port): if any caller relied on the `*x = undefined` poisoning to catch UAF in +// If any caller relied on the `*x = undefined` poisoning to catch UAF in // debug, add `#[cfg(debug_assertions)] unsafe { ptr::write_bytes(p, 0xAA, 1) }` at // that call site. // ────────────────────────────────────────────────────────────────────────────── diff --git a/src/bun_bin/lib.rs b/src/bun_bin/lib.rs index 9ec80f18f1d2..664b835a8352 100644 --- a/src/bun_bin/lib.rs +++ b/src/bun_bin/lib.rs @@ -76,7 +76,7 @@ pub extern "C" fn __asan_default_options() -> *const core::ffi::c_char { // detect_leaks=0: off by default (Linux defaults it on); CI opts in via // ASAN_OPTIONS with a suppressions file. // - // PORT NOTE: matches `src/safety/asan.zig` exactly. Do NOT add `symbolize=0` + // Matches `src/safety/asan.zig` exactly. Do NOT add `symbolize=0` // here — LSAN's function-name suppression matching (`test/leaksan.supp`) // requires symbolized stacks; with symbolization disabled every entry like // `leak:uws_create_app` silently stops matching and CI reports the diff --git a/src/bun_core/Global.rs b/src/bun_core/Global.rs index 1352feac513d..d41bf43e9ef4 100644 --- a/src/bun_core/Global.rs +++ b/src/bun_core/Global.rs @@ -458,9 +458,6 @@ pub const package_json_version_with_canary: &str = if cfg!(debug_assertions) { version_string }; -// PORT NOTE: Zig sliced `git_sha[0..@min(len, 8)]` inline; we use the -// pre-computed `GIT_SHA_SHORT` (same value) since const slicing of a const -// `&str` by a runtime-ish min() is awkward in stable Rust. /// The version and a short hash in parenthesis. pub const package_json_version_with_sha: &str = if env::GIT_SHA.is_empty() { package_json_version @@ -581,8 +578,8 @@ pub fn set_thread_name(name: &ZStr) { // no memory-safety preconditions, so the call site needs no `unsafe` block. pub type ExitFn = extern "C" fn(); -// PORT NOTE: Zig used an unsynchronized global `ArrayListUnmanaged`. Registration -// can happen from any thread (FFI `Bun__atexit`), so guard with a Mutex. +// Registration can happen from any thread (FFI `Bun__atexit`), so this is +// guarded with a Mutex (the Zig original used an unsynchronized global list). static ON_EXIT_CALLBACKS: crate::Mutex> = crate::Mutex::new(Vec::new()); #[unsafe(no_mangle)] diff --git a/src/bun_core/Progress.rs b/src/bun_core/Progress.rs index 51dd8d714683..e2192307a05a 100644 --- a/src/bun_core/Progress.rs +++ b/src/bun_core/Progress.rs @@ -201,8 +201,10 @@ pub enum Unit { pub struct Node { pub context: *mut Progress, pub parent: *mut Node, - // TODO(port): lifetime — caller-borrowed slice, Zig is non-allocating; using - // 'static here as a placeholder (callers in install/ pass string literals). + // Zig accepts a caller-borrowed `[]const u8`; the non-allocating design means + // `Node` cannot own the bytes. `'static` is the chosen Rust simplification + // because all current callers (install/, cli/) pass string literals — a + // faithful port would thread a lifetime through `Node`/`Progress` instead. pub name: &'static [u8], pub unit: Unit, /// Must be handled atomically to be thread-safe. @@ -312,7 +314,7 @@ impl Node { } parent.complete_one(); } else { - // PORT NOTE: reshaped for borrowck — guard borrows context.update_mutex; + // Reshaped for borrowck — guard borrows context.update_mutex; // we capture a raw ptr first so the &mut access goes through *mut. let ctx_ptr = std::ptr::from_mut::(context); let _g = context.update_mutex.lock(); @@ -368,9 +370,9 @@ impl Node { /// Thread-safe. pub fn set_unit(&mut self, unit: Unit) { - // TODO(port): Zig signature was `unit: []const u8` assigned to an enum field — - // dead code in Zig (lazy compilation never type-checked it). Ported with the - // enum type to keep it well-typed; revisit if any caller appears. + // Zig signature was `unit: []const u8` assigned to an enum field — + // dead code in Zig (lazy compilation never type-checked it). Ported with + // the enum type to keep it well-typed. let ctx_ptr = self.context_ptr(); // SAFETY: see `context_ptr` — `&mut Progress` would alias the node tree. let progress = unsafe { &mut *ctx_ptr }; @@ -416,8 +418,6 @@ impl Progress { /// API to return Progress rather than accept it as a parameter. /// `estimated_total_items` value of 0 means unknown. pub fn start(&mut self, name: &'static [u8], estimated_total_items: usize) -> &mut Node { - // TODO(port): std.fs.File.stderr() / supportsAnsiEscapeCodes() / isTty() — - // map to bun_sys::File equivalents. let stderr = File::stderr(); self.terminal = None; if stderr.supports_ansi_escape_codes() { @@ -454,9 +454,9 @@ impl Progress { /// Updates the terminal if enough time has passed since last update. Thread-safe. pub fn maybe_refresh(&mut self) { - // PORT NOTE: reshaped for borrowck — Instant is Copy, captured by value. + // Reshaped for borrowck — Instant is Copy, captured by value. if let Some(timer) = self.timer { - // PORT NOTE: reshaped for borrowck — capture *mut self before the + // Reshaped for borrowck — capture *mut self before the // guard borrows update_mutex. let ctx_ptr = std::ptr::from_mut::(self); let Some(_g) = self.update_mutex.try_lock() else { @@ -519,7 +519,6 @@ impl Progress { 'winapi: { debug_assert!(self.is_windows_terminal); - // TODO(port): verify bun_sys::windows::CONSOLE_SCREEN_BUFFER_INFO layout & kernel32 bindings. let mut info: windows::CONSOLE_SCREEN_BUFFER_INFO = crate::ffi::zeroed(); if GetConsoleScreenBufferInfo(file.console_handle(), &mut info) != windows::TRUE { @@ -647,8 +646,8 @@ impl Progress { &mut end, format_args!("[{}/{} files] ", current_item, eti), ), - // TODO(port): Zig `{Bi:.2}` is std.fmt binary-bytes formatter (e.g. "1.50KiB"). - // Need a bun_core::fmt::BytesBi helper. + // Zig `{Bi:.2}` is std.fmt's binary-bytes formatter (e.g. "1.50KiB"); + // raw counts are printed until an IEC-units helper lands. Unit::Bytes => self .buf_write(&mut end, format_args!("[{}/{}] ", current_item, eti)), } @@ -664,7 +663,7 @@ impl Progress { Unit::Files => { self.buf_write(&mut end, format_args!("[{} files] ", current_item)) } - // TODO(port): Zig `{Bi:.2}` binary-bytes formatter. + // Zig `{Bi:.2}` binary-bytes formatter; see the note above. Unit::Bytes => { self.buf_write(&mut end, format_args!("[{}] ", current_item)) } @@ -693,7 +692,8 @@ impl Progress { let _ = File::stderr().write_fmt(args); return; }; - // TODO(port): Zig `file.writerStreaming(&.{})` — map to bun_sys::File writer. + // Zig wraps the file in `writerStreaming(&.{})` (an unbuffered streaming + // writer); `File::write_fmt` below is the direct equivalent. self.refresh(); if file.write_fmt(args).is_err() { self.terminal = None; @@ -706,13 +706,14 @@ impl Progress { /// called. During the lock, the progress information is cleared from the /// terminal. /// - /// PORT NOTE: Zig splits the lock/unlock across fn boundaries. - /// `crate::Mutex` (std::sync wrapper) has no raw `unlock()`, and storing a - /// guard on `self` is self-referential. There are currently **no callers** - /// of `lock_stderr`/`unlock_stderr` in either the Zig or Rust trees, so - /// this clears the terminal under a scoped lock and `unlock_stderr` is a - /// no-op. If a caller materializes, refactor to return the guard (or move - /// `update_mutex` to a raw `bun_threading::Mutex` once layering allows). + /// Zig splits the lock/unlock across fn boundaries (and also takes + /// `std.debug.getStderrMutex()`). `crate::Mutex` (std::sync wrapper) has no + /// raw `unlock()`, and storing a guard on `self` is self-referential. There + /// are currently **no callers** of `lock_stderr`/`unlock_stderr` in either + /// the Zig or Rust trees, so this clears the terminal under a scoped lock + /// and `unlock_stderr` is a no-op. If a caller materializes, refactor to + /// return the guard (or move `update_mutex` to a raw `bun_threading::Mutex` + /// once layering allows) and route stderr through a shared global mutex. pub fn lock_stderr(&mut self) { let ctx_ptr = std::ptr::from_mut::(self); let _g = self.update_mutex.lock(); @@ -727,13 +728,11 @@ impl Progress { this.terminal = None; } } - // `_g` drops here; lock is NOT held past return — see PORT NOTE above. - // TODO(port): std.debug.getStderrMutex().lock() — need a global stderr mutex in bun_core. + // `_g` drops here; lock is NOT held past return — see the doc comment above. } pub fn unlock_stderr(&mut self) { - // TODO(port): std.debug.getStderrMutex().unlock() — see lock_stderr. - // No-op; see PORT NOTE on `lock_stderr`. + // No-op; see the doc comment on `lock_stderr`. let _ = self; } @@ -807,7 +806,7 @@ mod tests { ); node.activate(); thread::sleep(Duration::from_nanos(10 * speed_factor)); - // PORT NOTE: reshaped for borrowck — cannot borrow `progress` while `root_node` + // Reshaped for borrowck — cannot borrow `progress` while `root_node` // (a &mut into progress.root) is live; refresh via the node's context backref. // SAFETY: see `context_ptr` — `&mut Progress` would alias the node tree. unsafe { (*node.context_ptr()).refresh() }; diff --git a/src/bun_core/bounded_array.rs b/src/bun_core/bounded_array.rs index 6bc4a06e03cd..77ec41c42602 100644 --- a/src/bun_core/bounded_array.rs +++ b/src/bun_core/bounded_array.rs @@ -33,7 +33,7 @@ crate::named_error_set!(OverflowError); /// runtime, but whose maximum size is known at comptime, without requiring /// an `Allocator`. pub type BoundedArray = BoundedArrayAligned; -// PORT NOTE: Zig's `BoundedArray` delegates to `BoundedArrayAligned` with `@alignOf(T)`. +// Zig's `BoundedArray` delegates to `BoundedArrayAligned` with `@alignOf(T)`. // In Rust the natural alignment of `[T; N]` is already `align_of::()`, so the alias is // transparent. The explicit `alignment` const-param is dropped (see below). @@ -43,23 +43,23 @@ pub type BoundedArray = BoundedArrayAligned { buffer: [MaybeUninit; BUFFER_CAPACITY], - // TODO(port): Zig uses `Length = std.math.ByteAlignedInt(std.math.IntFittingRange(0, buffer_capacity))` + // Zig uses `Length = std.math.ByteAlignedInt(std.math.IntFittingRange(0, buffer_capacity))` // (smallest byte-aligned uint that fits `0..=BUFFER_CAPACITY`) to shrink this field. // Stable Rust const generics cannot pick an integer type from a const value without - // `generic_const_exprs`. Using `usize` for now. + // `generic_const_exprs`, so `usize` is used. // PERF(port): was size-optimized integer field — profile if it shows up on a hot path len: usize, } // `const Length = std.math.ByteAlignedInt(std.math.IntFittingRange(0, buffer_capacity));` -// — see TODO above; collapsed to `usize`. +// — see the `len` field note above; collapsed to `usize`. type Length = usize; impl Default for BoundedArrayAligned { @@ -94,7 +94,7 @@ impl BoundedArrayAligned { } /// View the internal array as a slice whose size was previously set. - // PORT NOTE: Zig's `slice(self: anytype)` is mut/const-polymorphic via `@TypeOf`. + // Zig's `slice(self: anytype)` is mut/const-polymorphic via `@TypeOf`. // Rust splits this into `slice(&mut self)` and `const_slice(&self)`. pub fn slice(&mut self) -> &mut [T] { let len = self.len; @@ -138,7 +138,7 @@ impl BoundedArrayAligned { list.slice().copy_from_slice(m); Ok(list) } - // TODO(port): Zig `@memcpy` works for non-Copy `T` (bitwise). If a non-`Copy` caller + // Zig `@memcpy` works for non-Copy `T` (bitwise). If a non-`Copy` caller // appears, add a `from_slice_clone` or use `ptr::copy_nonoverlapping`. /// Return the element at index `i` of the slice. @@ -224,7 +224,7 @@ impl BoundedArrayAligned { pub fn unused_capacity_slice(&mut self) -> &mut [MaybeUninit] { &mut self.buffer[self.len..] } - // PORT NOTE: returns `&mut [MaybeUninit]` instead of `&mut [T]` because the region is + // Returns `&mut [MaybeUninit]` instead of `&mut [T]` because the region is // uninitialized by definition; Zig's `[]T` over undefined memory has no safe Rust equivalent. /// Insert `item` at index `i` by moving `slice[n .. slice.len]` to make room. @@ -234,7 +234,7 @@ impl BoundedArrayAligned { return Err(OverflowError::Overflow); } let _ = self.add_one()?; - // PORT NOTE: reshaped for borrowck — Zig aliases `s[i+1..]` and `s[i..len-1]` from one slice. + // Reshaped for borrowck — Zig aliases `s[i+1..]` and `s[i..len-1]` from one slice. let s_len = self.len; // mem.copyBackwards(T, s[i + 1 .. s.len], s[i .. s.len - 1]); // SAFETY: ranges are within `[0..len)`; src and dst overlap, hence `ptr::copy` (memmove). @@ -282,7 +282,7 @@ impl BoundedArrayAligned { T: Copy, { let after_range = start + len; - // PORT NOTE: reshaped for borrowck — Zig holds `range` borrow across `insertSlice`. + // Reshaped for borrowck — Zig holds `range` borrow across `insertSlice`. let range_len = after_range - start; if range_len == new_items.len() { @@ -295,7 +295,7 @@ impl BoundedArrayAligned { } else { self.slice()[start..after_range][..new_items.len()].copy_from_slice(new_items); let after_subrange = start + new_items.len(); - // PORT NOTE: reshaped for borrowck — Zig reads `constSlice()[after_range..]` while + // Reshaped for borrowck — Zig reads `constSlice()[after_range..]` while // writing `slice()[after_subrange..]` in the same loop body. let tail_len = self.len - after_range; for i in 0..tail_len { @@ -303,7 +303,7 @@ impl BoundedArrayAligned { self.slice()[after_subrange..][i] = item; } self.len = Length::try_from(self.len - len + new_items.len()).expect("int cast"); - // PORT NOTE: Zig source had `self.len - len - new_items.len`, which over-shrinks + // Zig source had `self.len - len - new_items.len`, which over-shrinks // (and underflows when the replacement is non-empty). Removing `len` items and // inserting `new_items.len()` items yields `self.len - len + new_items.len()`. } @@ -312,7 +312,7 @@ impl BoundedArrayAligned { /// Extend the slice by 1 element. pub fn append(&mut self, item: T) -> Result<(), OverflowError> { - // PORT NOTE: Zig's `new_item_ptr.* = item` is a raw bitwise store. The naive Rust + // Zig's `new_item_ptr.* = item` is a raw bitwise store. The naive Rust // transliteration `*new_item_ptr = item` would drop the (uninitialized) prior occupant // of the slot first — UB that manifests as a bad free when `T` owns heap memory. self.ensure_unused_capacity(1)?; @@ -344,7 +344,7 @@ impl BoundedArrayAligned { return self.pop().unwrap(); } let old_item = self.get(i); - // PORT NOTE: reshaped for borrowck — Zig writes through `*b` while calling `self.get()`. + // Reshaped for borrowck — Zig writes through `*b` while calling `self.get()`. for j in 0..(newlen - i) { let v = self.get(i + 1 + j); self.slice()[i + j] = v; diff --git a/src/bun_core/deprecated.rs b/src/bun_core/deprecated.rs index ebb085c7b077..3cbd515afd0d 100644 --- a/src/bun_core/deprecated.rs +++ b/src/bun_core/deprecated.rs @@ -4,9 +4,11 @@ use core::ptr; // BufferedReader // ────────────────────────────────────────────────────────────────────────── -// TODO(port): Zig's `ReaderType` only needs `.read(&mut [u8]) -> Result` -// and an associated `Error` type. There is no `bun_io::Read` trait yet; introduce one -// (or reuse whatever the `std.Io.GenericReader` port lands as) and bound `R` on it. +// Plain storage for Zig's `bun.deprecated.BufferedReader(buffer_size, ReaderType)`. +// Zig's `ReaderType` duck-typing has no Rust trait equivalent here, and the only +// in-tree consumer (`pack_command::BufferedFileReader`) supplies its own read shim +// over `bun_sys::read`, so this stays a bare struct: no reader trait, no methods. +// (The dedicated stdin instance lives at `output::BufferedStdin`.) pub struct BufferedReader { pub unbuffered_reader: R, pub buf: [u8; BUFFER_SIZE], @@ -14,66 +16,6 @@ pub struct BufferedReader { pub end: usize, } -impl BufferedReader -where - // TODO(port): replace with the real reader trait once it exists. - R: DeprecatedRead, -{ - // Zig: `pub const Error = R.Error;` — inherent assoc types are nightly-only - // (E0658). Callers name `R::Error` directly; this alias was sugar. - // TODO(port): `pub const Reader = std.Io.GenericReader(*Self, Error, read);` — - // depends on the Rust port of `std.Io.GenericReader`. Left unported; `reader()` - // below is stubbed accordingly. - - pub fn read(&mut self, dest: &mut [u8]) -> Result { - // First try reading from the already buffered data onto the destination. - let current = &self.buf[self.start..self.end]; - if !current.is_empty() { - let to_transfer = current.len().min(dest.len()); - dest[0..to_transfer].copy_from_slice(¤t[0..to_transfer]); - self.start += to_transfer; - return Ok(to_transfer); - } - - // If dest is large, read from the unbuffered reader directly into the destination. - if dest.len() >= BUFFER_SIZE { - return self.unbuffered_reader.read(dest); - } - - // If dest is small, read from the unbuffered reader into our own internal buffer, - // and then transfer to destination. - self.end = self.unbuffered_reader.read(&mut self.buf)?; - let to_transfer = self.end.min(dest.len()); - dest[0..to_transfer].copy_from_slice(&self.buf[0..to_transfer]); - self.start = to_transfer; - Ok(to_transfer) - } - - pub fn reader(&mut self) -> &mut Self { - // TODO(port): Zig returned a `std.Io.GenericReader` adapter wrapping `self`. - // Until the generic-reader port exists, hand back `&mut Self` (which already - // exposes `read`). Wire to the real adapter type once it exists. - self - } -} - -// TODO(port): placeholder trait standing in for `ReaderType` duck-typing. Remove once -// the shared reader trait exists and bound `R` on that instead. -pub trait DeprecatedRead { - type Error; - fn read(&mut self, dest: &mut [u8]) -> Result; -} - -pub fn buffered_reader(reader: R) -> BufferedReader<4096, R> { - BufferedReader { - unbuffered_reader: reader, - // PERF(port): Zig left `buf` undefined; zero-init here is an extra 4 KiB memset. - buf: [0u8; 4096], - start: 0, - end: 0, - } -} - // ────────────────────────────────────────────────────────────────────────── // SinglyLinkedList // ────────────────────────────────────────────────────────────────────────── diff --git a/src/bun_core/env.rs b/src/bun_core/env.rs index 85fe5aadd21e..46c56753a113 100644 --- a/src/bun_core/env.rs +++ b/src/bun_core/env.rs @@ -1,8 +1,6 @@ use phf::phf_map; -// PORT NOTE: `build_options` was Zig's build-system-injected module. In Rust it -// is a generated module (build.rs consts). -// Zig: `pub const build_options = @import("build_options");` — public re-export. +// `build_options` is a generated module (build.rs consts), re-exported publicly. pub use crate::build_options; #[repr(u8)] diff --git a/src/bun_core/env_var.rs b/src/bun_core/env_var.rs index 84129898328a..4b6564ef50e0 100644 --- a/src/bun_core/env_var.rs +++ b/src/bun_core/env_var.rs @@ -26,14 +26,12 @@ //! everything. This means that we potentially scan through envp a lot of //! times, even though we could only do it once. -// TODO(port): The Zig original uses comptime type-returning functions (`New`, `PlatformSpecificNew`) +// The Zig original uses comptime type-returning functions (`New`, `PlatformSpecificNew`) // that take comptime string keys + option structs and return a unique type per env var with an // embedded `static` cache. Rust cannot parameterize a generic type on `&'static str` + a struct // value in stable, so this port models `New`/`PlatformSpecificNew` as `macro_rules!` that emit a // module per env var. In Zig the declarations come first and the type-generator fns come last; -// here the macros must be defined (or `#[macro_use]`d) before the declarations. The macro -// definitions could move into a sibling `env_var_impl.rs` and be `#[macro_use]`d to restore Zig -// declaration order in this file. +// here the macros must be defined (or `#[macro_use]`d) before the declarations. use core::sync::atomic::{AtomicPtr, AtomicU8, AtomicU64, AtomicUsize, Ordering}; @@ -508,9 +506,9 @@ pub(crate) mod kind { // `const fn new()` time. pub(crate) struct Cache { value: AtomicU64, - // TODO(port): in Zig `ip` is a comptime param baked into the type; here it lives as - // runtime data on the static. The `default_fallback` arm in handle_error was a - // `@compileError` when no default was set — that compile-time check is lost. + // In Zig `ip` is a comptime param baked into the type; here it lives as runtime + // data on the static. The `default_fallback` arm in handle_error was a + // `@compileError` when no default was set — that check is runtime-only here. ip: Input, } @@ -645,10 +643,10 @@ pub(crate) use new; #[macro_export] #[doc(hidden)] macro_rules! platform_specific_new { - // TODO(port): this macro is a draft of the Zig comptime type-generator. It expands to a + // Port of the Zig comptime type-generator. It expands to a // `pub mod $name { pub fn get() / key() / platform_get() / ... }` so call sites read // `env_var::HOME::get()` like Zig's `env_var.HOME.get()`. The opts-parsing arms below cover - // exactly the option shapes used in this file; harden / generalize if new shapes appear. + // exactly the option shapes used in this file; new shapes need new arms. ( $vis:vis $name:ident : $kind:ident, posix = $posix:tt, windows = $windows:tt, @@ -670,9 +668,9 @@ macro_rules! platform_specific_new { ); // Zig computed `DefaultType`/`ReturnType` at comptime from whether `opts.default` is - // set. We expose the default + a const HAS_DEFAULT and always return Option; - // a thin `pub fn get() -> ValueType` wrapper that `.unwrap()`s is added when a default - // exists. TODO(port): restore the non-nullable `get()` return type for defaulted vars. + // set, making `get()` non-optional for defaulted vars. A `macro_rules!` expansion + // can't vary the return type on an optional opt, so `get()` always returns + // `Option` (always `Some` when `DEFAULT` is `Some`). pub(crate) const DEFAULT: Option = $crate::env_var::__default_opt!($kind, { $($opts)* }); @@ -726,9 +724,9 @@ macro_rules! platform_specific_new { None } - // TODO(port): `getNotEmpty` only makes sense for string-kind vars (it calls `.len`). - // In Zig, lazy compilation means it simply isn't instantiated for non-string vars. - // Could gate this fn on `$kind == string` via a separate macro arm. + // `getNotEmpty` only makes sense for string-kind vars (it calls `.len`). In Zig, + // lazy compilation means it simply isn't instantiated for non-string vars; here the + // `HasLen` bound does the same gating — calls on non-string kinds fail to compile. pub fn get_not_empty() -> Option where K::ValueType: $crate::env_var::HasLen, @@ -802,18 +800,15 @@ macro_rules! platform_specific_new { } fn assert_platform_supported() { - // Zig: `@compileError` when the current platform's key is null. - // TODO(port): Rust cannot `compile_error!` from inside a const-evaluated `if cfg!` - // without separate macro arms per (posix=None / windows=None) combination. Could - // split the macro so e.g. `posix = None` emits `#[cfg(unix)] compile_error!`. + // Zig: `@compileError` when the current platform's key is null. Zig only + // instantiates this lazily (per call site); a Rust `compile_error!` here would + // fire unconditionally on the unsupported platform even when nothing calls + // `get()`, so the closest faithful port is a debug assertion. debug_assert!( platform_key().is_some(), - concat!( - "Cannot retrieve the value of ", - // TODO(port): COMPTIME_KEY is &[u8]; concat! wants literals - "", - " since no key is associated with it on this platform." - ) + "Cannot retrieve the value of {} since no key is associated with it on this platform.", + ::core::str::from_utf8($crate::env_var::__first_key!($posix, $windows)) + .unwrap_or("") ); } } @@ -822,8 +817,6 @@ macro_rules! platform_specific_new { pub(crate) use platform_specific_new; // ─── helper macros for platform_specific_new! ─── -// TODO(port): these are scaffolding for the draft macro; could be replaced with a cleaner -// trait-based design once the call-site shape is settled. #[doc(hidden)] #[macro_export] diff --git a/src/bun_core/external_shared.rs b/src/bun_core/external_shared.rs index a7650784f2e3..bbb7cee6ccd1 100644 --- a/src/bun_core/external_shared.rs +++ b/src/bun_core/external_shared.rs @@ -8,8 +8,8 @@ use core::ptr::NonNull; /// # Safety /// Implementors guarantee that `ext_ref`/`ext_deref` operate on a valid externally-owned /// reference count, and that the pointee remains alive while the count is > 0. -// PORT NOTE: Zig names are `ref`/`deref`; renamed to avoid the `ref` keyword and -// `core::ops::Deref::deref` confusion. +// Named `ext_ref`/`ext_deref` (Zig uses `ref`/`deref`) to avoid the `ref` +// keyword and `core::ops::Deref::deref` confusion. pub unsafe trait ExternalSharedDescriptor { unsafe fn ext_ref(this: *mut Self); unsafe fn ext_deref(this: *mut Self); diff --git a/src/bun_core/fmt.rs b/src/bun_core/fmt.rs index 930c51973cfe..1a13941f5450 100644 --- a/src/bun_core/fmt.rs +++ b/src/bun_core/fmt.rs @@ -52,18 +52,19 @@ pub mod js_printer { input: &[u8], f: &mut impl fmt::Write, quote: u8, - _allow_backtick: bool, + ascii_only: bool, enc: Encoding, ) -> fmt::Result { - // TODO(port): full impl in bun_js_printer; this tier only needs the - // "already quoted" passthrough for fmt.rs JS-string display. // Zig writePreQuotedString writes the escaped body WITHOUT surrounding - // quotes — delegate to the canonical chars-only escaper. - let _ = quote; - match enc { - Encoding::Latin1 => super::encode_json_string_chars_latin1(f, input), - _ => super::encode_json_string_chars(f, input), - } + // quotes. Delegate to the canonical full port in `string::printer` + // (a byte-sink writer) and bridge the result into the `fmt::Write`. + // `json = true` matches the Zig fmt.zig JSONFormatterUTF8 call site; + // in JSON mode every non-printable scalar (including lone surrogates) + // is emitted as an ASCII escape. + let mut buf: Vec = Vec::with_capacity(input.len() + 8); + crate::string::printer::write_pre_quoted_string(input, &mut buf, quote, ascii_only, true, enc) + .map_err(|_| fmt::Error)?; + f.write_str(&String::from_utf8_lossy(&buf)) } } use strum::IntoStaticStr; @@ -988,10 +989,6 @@ where // `bun_js_parser::lexer`, and `bun_install` can call it without taking a // `bun_jsc` edge. `bun_core::wtf`, `bun_jsc::wtf`, and `bun::` re-export it // to preserve the Zig namespace shape. -// -// TODO(port): Zig `bun.parseDouble` falls back to `std.fmt.parseFloat` under -// `comptime Environment.isWasm` (no WebKit link). Restore when wasm target is -// brought up. // ────────────────────────────────────────────────────────────────────────── /// Error from [`parse_double`] — Zig `error{InvalidCharacter}`. @@ -1733,7 +1730,8 @@ pub enum RedactedKeyword { pub struct RedactedKeywords; impl RedactedKeywords { - // TODO(port): replace with phf::Map. + // 5 entries — a `matches!` chain beats a hash map at this size (the big + // keyword table in `Keywords::get` is where `phf` pays off). pub fn has(s: &[u8]) -> bool { matches!( s, @@ -1780,12 +1778,9 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { let code = keyword.color_code(); write!( writer, - // TODO(port): Output.prettyFmt("{s}{s}", true) - "{}{}{}{}", - Output::RESET, + crate::pretty_fmt!("{s}{s}", true), code.color(), bstr::BStr::new(&text[..i]), - Output::RESET, )?; } else { should_redact_value = @@ -1797,14 +1792,10 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { prev_keyword = None; if i < text.len() && text[i] == b'(' { - // TODO(port): Output.prettyFmt("{s}", true) write!( writer, - "{}{}{}{}", - Output::RESET, - Output::BOLD, + crate::pretty_fmt!("{s}", true), bstr::BStr::new(&text[..i]), - Output::RESET, )?; break 'write; } @@ -1814,15 +1805,10 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { | Keyword::Declare | Keyword::Type | Keyword::Interface => { - // TODO(port): Output.prettyFmt("{s}", true) write!( writer, - "{}{}{}{}{}", - Output::RESET, - Output::BOLD, - ColorCode::Blue.color(), + crate::pretty_fmt!("{s}", true), bstr::BStr::new(&text[..i]), - Output::RESET, )?; prev_keyword = None; break 'write; @@ -1887,8 +1873,7 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { let end = crate::strings_impl::index_of_char(text, b'\n') .unwrap_or(text.len()); text = &text[end..]; - // TODO(port): Output.prettyFmt("***", true) - write!(writer, "{}\x1b[33m***{}", Output::RESET, Output::RESET)?; + write!(writer, crate::pretty_fmt!("***", true))?; continue; } @@ -1926,13 +1911,10 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { } } - // TODO(port): Output.prettyFmt("{s}", true) write!( writer, - "{}\x1b[33m{}{}", - Output::RESET, + crate::pretty_fmt!("{s}", true), bstr::BStr::new(&text[..i]), - Output::RESET, )?; text = &text[i..]; } @@ -1954,13 +1936,10 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { i += 1; } - // TODO(port): Output.prettyFmt("{s}", true) write!( writer, - "{}\x1b[32m{}{}", - Output::RESET, + crate::pretty_fmt!("{s}", true), bstr::BStr::new(&text[..curly_start]), - Output::RESET, )?; writer.write_str("${")?; let mut opts = self.opts; @@ -1981,12 +1960,9 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { text = &text[i..]; i = 0; if !text.is_empty() && text[0] == char_ { - // TODO(port): Output.prettyFmt("`", true) write!( writer, - "{}\x1b[32m`{}", - Output::RESET, - Output::RESET + crate::pretty_fmt!("`", true) )?; text = &text[1..]; continue 'outer; @@ -2009,8 +1985,7 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { should_redact_value = false; if i > 2 && text[i - 1] == char_ { let len = i - 2; - // TODO(port): Output.prettyFmt("{c}", true) - write!(writer, "{}\x1b[32m{}", Output::RESET, char_ as char)?; + write!(writer, crate::pretty_fmt!("{s}", true), char_ as char)?; splat_byte_all(writer, b'*', len)?; write!(writer, "{}{}", char_ as char, Output::RESET)?; } else { @@ -2125,13 +2100,10 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { } if self.opts.redact_sensitive_information { - // TODO(port): Output.prettyFmt("{f}", true) write!( writer, - "{}\x1b[2m{}{}", - Output::RESET, + crate::pretty_fmt!("{f}", true), redacted_source(remain_to_print), - Output::RESET, )?; } else { write!( @@ -2227,8 +2199,7 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { text = &text[len..]; continue; } - // TODO(port): Output.prettyFmt(";", true) - write!(writer, "{}\x1b[2m;{}", Output::RESET, Output::RESET)?; + write!(writer, crate::pretty_fmt!(";", true))?; text = &text[1..]; } b'.' => { @@ -2255,14 +2226,10 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { } if i < text.len() && text[i] == b'(' { - // TODO(port): Output.prettyFmt("{s}", true) write!( writer, - "{}\x1b[3m{}{}{}", - Output::RESET, - Output::BOLD, + crate::pretty_fmt!("{s}", true), bstr::BStr::new(&text[..i]), - Output::RESET, )?; text = &text[i..]; continue; @@ -2295,8 +2262,9 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { // Zig `while (cond) { i += 1 } else { i = 1; break :jsx; }` — Zig's // while-else runs the else branch whenever the condition becomes false // (i.e. on normal loop exit, since the body has no `break`). So the - // else ALWAYS fires here and the code below is dead in Zig too. - // TODO(port): Zig while-else always fires here — likely upstream bug, worth verifying. + // else ALWAYS fires (verified against fmt.zig's `jsx:` block): the + // `>`-scan + cyan-print code that follows it in Zig is dead there, + // and is intentionally not ported here. while i < text.len() && js_lexer::is_identifier_continue(text[i] as i32) { i += 1; @@ -2389,8 +2357,8 @@ pub fn enum_tag_list() -> EnumTagListF // formatIp // ─────────────────────────────────────────────────────────────────────────── -// TODO(port): `std.net.Address` — bun_core stays I/O-free; this should accept a -// bun_sys/bun_net Address type. Logic preserved against a placeholder Display. +// Zig took a `std.net.Address`; bun_core stays I/O-free, so this accepts any +// `Display` (callers pass their own address type). The strip logic is identical. pub fn format_ip<'a>( address: &impl Display, into: &'a mut [u8], @@ -2403,8 +2371,8 @@ pub fn format_ip<'a>( write!(cursor, "{}", address).map_err(|_| crate::err!("NoSpaceLeft"))?; let written = cursor.position() as usize; - // PORT NOTE: reshaped for borrowck — compute (start, end) offsets against - // `into` instead of iteratively reborrowing a `result` slice, so the final + // Reshaped for borrowck — compute (start, end) offsets against `into` + // instead of iteratively reborrowing a `result` slice, so the final // returned `&mut into[start..end]` carries the caller's `'a` lifetime // cleanly. Semantics match Zig's `result = result[a..b]` chain exactly. let mut start = 0usize; @@ -2698,8 +2666,8 @@ impl Display for SizeFormatter { } } -// TODO(port): Zig `size(bytes: anytype, ...)` switched on @TypeOf(bytes) for -// f64/f32/f128 (intFromFloat) and i64/isize (intCast). Expose typed helpers. +// Zig `size(bytes: anytype, ...)` switched on @TypeOf(bytes); the Rust port +// splits that into `size` (usize), `size_f64`, and `size_i64` below. pub fn size(bytes: usize, opts: SizeFormatterOptions) -> SizeFormatter { SizeFormatter { value: bytes, opts } } @@ -2738,7 +2706,7 @@ pub fn size_f64(bytes: f64, opts: SizeFormatterOptions) -> SizeFormatter { } } pub fn size_i64(bytes: i64, opts: SizeFormatterOptions) -> SizeFormatter { - // PORT NOTE: Zig's `@intCast(bytes)` is unchecked in release (UB-wraps negative); + // Zig's `@intCast(bytes)` is unchecked in release (UB-wraps negative); // clamp to 0 instead of panicking so release builds never crash on a transiently // negative size, while keeping the safe-build trap via debug_assert. debug_assert!(bytes >= 0); @@ -3024,10 +2992,10 @@ pub const fn hex_u16(v: u16) -> [u8; 4] { ] } -// TODO(port): Zig parameterizes on `comptime Int: type` and computes +// Zig parameterizes on `comptime Int: type` and computes // `BufType = [@bitSizeOf(Int) / 4]u8`. Rust const generics can't derive an array -// length from a type's bit-width. Represent as a generic over u64 with explicit -// nibble count; add per-width helpers if this shows up on a hot path. +// length from a type's bit-width, so this is generic over u64 with an explicit +// nibble count. pub struct HexIntFormatter { pub value: u64, } @@ -3148,7 +3116,7 @@ impl Display for TrimmedPrecisionFormatter { let rem = self.num - whole; if rem != 0.0 { // buf size = "0." + PRECISION digits - // PORT NOTE: Zig used `[2 + precision]u8` stack array; Rust const-generic array + // Zig used a `[2 + precision]u8` stack array; Rust const-generic array // length arithmetic is unstable, so use a small fixed upper bound and // const-assert it suffices (matches Zig's compile-time sizing guarantee). const { @@ -3360,7 +3328,6 @@ pub struct FormatDouble { pub number: f64, } -// TODO(port): move to _sys unsafe extern "C" { // `&mut [u8; 124]` is ABI-identical to the C `char *` argument (thin // non-null pointer to 124 writable bytes); the type encodes WTF__dtoa's diff --git a/src/bun_core/lib.rs b/src/bun_core/lib.rs index 3f554c8f5472..9a2214018e32 100644 --- a/src/bun_core/lib.rs +++ b/src/bun_core/lib.rs @@ -628,6 +628,9 @@ bun_dispatch::link_interface! { pub ErrnoNames[Sys] { fn name(errno: i32) -> Option<&'static str>; fn max_dense() -> u32; + // Raw Win32 `GetLastError()` code → `SystemErrno` tag name. + // Always `None` on non-Windows. + fn win32_name(code: u32) -> Option<&'static str>; } } @@ -951,7 +954,7 @@ bun_alloc::oom_from_alloc!(JsError); impl From for JsError { fn from(_: crate::Error) -> Self { - // PORT NOTE: Zig coerces arbitrary `anyerror` into the JS error union by + // Zig coerces arbitrary `anyerror` into the JS error union by // throwing a generic Error; the throw happens at the call site. Mapping // to `Thrown` here lets `?` propagate while the actual throw is handled // by the host-fn wrapper. @@ -1096,7 +1099,7 @@ pub fn handle_oom(r: core::result::Result) -> T { /// Zig-faithful narrowing version (`bun.handleOom` with comptime error-set /// reflection) see `bun_crash_handler::HandleOom`. /// -/// PORT NOTE: this is intentionally a blanket `impl` — it matches the +/// This is intentionally a blanket `impl` — it matches the /// existing `bun_core::handle_oom` free fn and the two pre-existing local /// blanket impls in `run_command.rs` / `valkey.rs`. Callers that want a strict /// `error{OutOfMemory}`-only whitelist should use `bun_crash_handler::HandleOom` @@ -1129,11 +1132,16 @@ pub fn handle_error_return_trace(_err: E) {} /// yet implemented" path that the Zig source ships with — distinct from a /// porting placeholder. Captures file/line via `file!()`/`line!()` (the /// `@src()` equivalent) and routes through `Output::panic`. -// TODO(port): wire `bun_analytics::Features::todo_panic` once the analytics -// crate is reachable from bun_core without a dep cycle. #[macro_export] macro_rules! todo_panic { ($($arg:tt)*) => {{ + // Zig: `analytics.Features.todo_panic = 1;` (bun.zig todoPanic). + // Recorded in the tier-0 `Global::features` counter (same as + // css_parser's todo store). `bun_analytics::features::todo_panic` — + // the set the crash report serializes via `packed_features()` — is a + // re-export of this same static (see `define_features!`'s `core =` + // entries in src/analytics/lib.rs), so the bit reaches crash reports. + $crate::Global::features::TODO_PANIC.store(1, ::core::sync::atomic::Ordering::Relaxed); $crate::output::panic(::core::format_args!( "TODO: {} ({}:{})", ::core::format_args!($($arg)*), @@ -2468,7 +2476,7 @@ pub(crate) mod strings_impl { /// caller is responsible for sizing `out` for the worst case (≤ 3× input /// code units). /// - /// PORT NOTE: Zig passes `out` straight to simdutf with no bounds check + /// Zig passes `out` straight to simdutf with no bounds check /// (UB if undersized). We assert in release too — one extra SIMD length /// scan is cheap, and a panic beats heap corruption if a future caller /// gets the sizing wrong. All current callers (~10, Windows wide-path diff --git a/src/bun_core/output.rs b/src/bun_core/output.rs index dbf3b7141534..39126c635b1c 100644 --- a/src/bun_core/output.rs +++ b/src/bun_core/output.rs @@ -45,8 +45,10 @@ pub(crate) fn output_sink() -> OutputSink { } /// Opaque handle to a `bun_sys::file::QuietWriter`. bun_core treats it as a -/// POD blob; bun_sys casts back to the concrete type. -/// TODO(port): bun_sys::file::QuietWriter — size/align must match. +/// POD blob; bun_sys casts back to the concrete type. Contract: bun_sys only +/// stashes the raw fd in slot 0 (see `qw_fd`/`qw_set_fd` in bun_sys), so the +/// blob just needs pointer size/alignment — keep `[*mut (); 4]` in sync with +/// that consumer if either side changes. #[derive(Clone, Copy)] #[repr(C)] pub struct QuietWriter { @@ -87,7 +89,9 @@ impl core::fmt::Write for QuietWriter { // `qw.write_fmt(args)` resolves through `fmt::Write`. /// Opaque adapter wrapping a QuietWriter and exposing `crate::io::Writer`. -/// TODO(port): bun_sys::QuietWrite::Adapter — size/align must match. +/// Layout contract: bun_sys's concrete `SysQuietWriterAdapter` must fit in +/// these 64 bytes with `io::Writer` as its first field; const-asserted on the +/// bun_sys side next to that struct. #[repr(C, align(8))] pub struct QuietWriterAdapter { _opaque: [u8; 64], @@ -314,7 +318,7 @@ pub static TERMINAL_SIZE: crate::AtomicCell = #[cfg(not(target_arch = "wasm32"))] pub type StreamType = File; #[cfg(target_arch = "wasm32")] -pub type StreamType = io::FixedBufferStream; // TODO(port): FixedBufferStream arrives via bun_io→core move-in. +pub type StreamType = io::FixedBufferStream; // wasm32 is not built yet; FixedBufferStream is unported. pub struct Source { pub stdout_buffer: [u8; 4096], @@ -342,14 +346,39 @@ pub struct Source { } impl Source { - // TODO(port): proper zero-init for QuietWriterAdapter / StreamType — currently relies on - // `mem::zeroed()` which is only sound if those are `#[repr(C)]` POD with no - // `NonNull`/`NonZero`/niche-enum fields. Hand-write `ZEROED`/`Default` once their - // layouts are fixed in bun_sys. - // SAFETY: byte arrays and raw `*mut` pointers are valid all-zero (null fat ptr = - // `(null, 0)`); only read after `init()` overwrites every field. See TODO above for - // the adapter/stream caveat. - pub const ZEROED: Self = unsafe { crate::ffi::zeroed_unchecked() }; + // Field-wise placeholder value for the pre-`init()` thread_local slot. + // Every field is overwritten by `init()` before use. + // + // The pre-init stream placeholder is cfg-split because the two + // `StreamType` aliases have different const surfaces: native + // `File::ZEROED` is `Fd::INVALID`, so a pre-init read fails loudly + // instead of aliasing fd 0; `FixedBufferStream` has no `ZEROED` const, + // so the wasm32 arm spells out the empty stream field-wise. + #[cfg(not(target_arch = "wasm32"))] + const ZEROED_STREAM: StreamType = StreamType::ZEROED; + #[cfg(target_arch = "wasm32")] + const ZEROED_STREAM: StreamType = StreamType { + buf: core::ptr::null_mut(), + len: 0, + pos: 0, + }; + + pub const ZEROED: Self = Self { + stdout_buffer: [0u8; 4096], + stderr_buffer: [0u8; 4096], + buffered_stream_backing: QuietWriterAdapter::uninit(), + buffered_error_stream_backing: QuietWriterAdapter::uninit(), + buffered_stream: core::ptr::null_mut(), + buffered_error_stream: core::ptr::null_mut(), + stream_backing: QuietWriterAdapter::uninit(), + error_stream_backing: QuietWriterAdapter::uninit(), + stream: core::ptr::null_mut(), + error_stream: core::ptr::null_mut(), + raw_stream: Self::ZEROED_STREAM, + raw_error_stream: Self::ZEROED_STREAM, + out_buffer: core::ptr::slice_from_raw_parts_mut(core::ptr::null_mut(), 0), + err_buffer: core::ptr::slice_from_raw_parts_mut(core::ptr::null_mut(), 0), + }; /// Accessors replacing the self-referential `*std.Io.Writer` fields. #[inline] @@ -369,12 +398,11 @@ impl Source { self.error_stream_backing.new_interface() } - // TODO(port): in-place init — `out` is the pre-allocated thread_local slot; PORTING.md - // says keep `&mut MaybeUninit` (or reshape to `-> Self`) for out-param ctors. + // The out-param shape is load-bearing: `out` is the pre-allocated + // thread_local slot, and the adapters built below capture raw pointers + // into `out.stdout_buffer`/`out.stderr_buffer`. Returning `Self` by value + // would move the struct after those pointers were captured and dangle them. pub fn init(out: &mut Source, stream: StreamType, err_stream: StreamType) { - // TODO(port): bun_alloc::USE_MIMALLOC + mimalloc::Option::ShowErrors - // are gated in bun_alloc; re-enable once bun_alloc/basic.rs is un-gated. - if cfg!(debug_assertions) && bun_alloc::USE_MIMALLOC && !SOURCE_SET.get() { bun_alloc::mimalloc::mi_option_set(bun_alloc::mimalloc::Option::show_errors, 1); } @@ -384,9 +412,9 @@ impl Source { out.raw_error_stream = err_stream; // stdout_buffer / stderr_buffer left uninitialized (overwritten by adapter writes) - // PORT NOTE: reshaped for borrowck — the Zig wrote `out.* = .{ ...undefined... }` - // then patched the self-referential pointers. In Rust we construct the backings - // directly and expose accessors instead of storing interior pointers. + // The Zig wrote `out.* = .{ ...undefined... }` then patched the + // self-referential pointers. Here we construct the backings directly + // and expose accessors instead of storing interior pointers. out.buffered_stream_backing = out .raw_stream .quiet_writer() @@ -694,7 +722,6 @@ pub mod windows_stdio { pub mod stdio { use super::*; - // TODO(port): move to bun_core_sys unsafe extern "C" { // Written once by C at process startup before threads; Rust only reads. // `[AtomicI32; 3]` has identical layout to C's `int32_t[3]` (`AtomicI32` @@ -943,14 +970,9 @@ pub fn is_ai_agent() -> bool { if env_var::REPL_ID.get().unwrap_or(false) { return true; } - // TODO: add environment variable for Gemini - // Gemini does not appear to add any environment variables to identify it. - - // TODO: add environment variable for Codex - // codex does not appear to add any environment variables to identify it. - - // TODO: add environment variable for Cursor Background Agents - // cursor does not appear to add any environment variables to identify it. + // Other agents we'd like to detect, but which do not appear to set + // any identifying environment variables: Gemini, Codex, Cursor + // Background Agents. Add checks here if they ever grow one. false } @@ -1031,12 +1053,11 @@ pub fn disable_buffering_scope() -> DisableBufferingScope { #[cold] pub fn panic(args: fmt::Arguments<'_>) -> ! { - // PORT NOTE: Zig branched on enable_ansi_colors_stderr to pick the comptime-colored - // vs stripped format string. In Rust callers use `panic!(pretty_fmt!(...))` directly; - // this fn is kept for non-macro callers and writes the (already-formatted) args. - // TODO(port): branch on ENABLE_ANSI_COLORS_STDERR once the colored vs - // stripped paths actually differ; callers should wrap fmt in - // `pretty_fmt!(.., enable_ansi_colors_stderr)`. + // Zig branched on enable_ansi_colors_stderr to pick the comptime-colored + // vs stripped format string. In Rust callers use `panic!(pretty_fmt!(...))` + // directly; this fn is kept for non-macro callers and writes the + // (already-formatted) args. Callers that want coloring wrap the fmt in + // `pretty_fmt!(.., enable_ansi_colors_stderr)` themselves. core::panic!("{args}"); } @@ -1045,7 +1066,8 @@ pub fn raw_error_writer() -> StreamType { SOURCE.with_borrow(|s| s.raw_error_stream) } -// TODO: investigate migrating this to the buffered one. +// Possible perf follow-up: migrate callers of these unbuffered accessors to +// the buffered writer. // // TODO(port): these accessors hand out a `&'static mut` to a thread-local // `Source` field. The Zig original returned `*Writer` and callers used it @@ -1214,7 +1236,7 @@ impl fmt::Display for ElapsedFormatter { } } -// PORT NOTE: Zig's `printElapsedToWithCtx` passed the raw `[...]` +// Zig's `printElapsedToWithCtx` passed the raw `[...]` // template to a `(comptime fmt, args)` printer (`prettyError`/`pretty`), which // then routed through `prettyTo` to branch on `enable_ansi_colors_{stdout,stderr}`. // In Rust the `pretty_fmt!` rewrite must happen at the macro call site, so the @@ -1224,7 +1246,7 @@ impl fmt::Display for ElapsedFormatter { // intermediate helper had no way to defer the color/no-color choice without // leaking raw `\x1b[` escapes when colors are disabled, so it was removed. -// `print_elapsed_to` intentionally removed — see PORT NOTE above. The colored +// `print_elapsed_to` intentionally removed — see the note above. The colored // template can't be deferred through an `fmt::Arguments`-taking printer without // either leaking raw escapes or stripping color, so callers must use // `print_elapsed` / `print_elapsed_stdout` (or `ElapsedFormatter` directly). @@ -1373,7 +1395,8 @@ fn write_bytes(dest: Destination, bytes: &[u8]) { pub fn print_to(dest: Destination, args: fmt::Arguments<'_>) { #[cfg(target_arch = "wasm32")] { - // TODO(port): wasm console_log/console_error path via root.Uint8Array + // wasm32 is not built yet; the Zig console_log/console_error path + // (via root.Uint8Array) is unported and output is dropped. let _ = (dest, args); return; } @@ -1485,13 +1508,14 @@ impl Visibility { /// Runtime state for one scoped logger. One static instance per `declare_scope!`. pub struct ScopedLogger { - pub tagname: &'static str, // already lowercased + // As-declared (`stringify!`) casing; lowered for display via `_LowerTag`, + // and env/flag matching is case-insensitive. + pub tagname: &'static str, really_disable: AtomicBool, is_visible_once: std::sync::Once, lock: Mutex<()>, - out_set: AtomicBool, - // TODO(port): per-scope buffered writer (`buffer: [4096]u8` + adapter). For now route - // through `scoped_writer()` directly; could reintroduce per-scope buffering. + // Unlike Zig, there is no per-scope `[4096]u8` buffered writer; logs route + // through `scoped_writer()` directly (debug-logging perf only). } impl ScopedLogger { @@ -1501,7 +1525,6 @@ impl ScopedLogger { really_disable: AtomicBool::new(matches!(visibility, Visibility::Hidden)), is_visible_once: std::sync::Once::new(), lock: Mutex::new(()), - out_set: AtomicBool::new(false), } } @@ -1580,9 +1603,6 @@ impl ScopedLogger { return; } - // TODO(port): per-scope `[4096]u8` buffered adapter (`out`/`out_set`/`buffered_writer`) - let _ = self.out_set.load(Ordering::Relaxed); - let _lock = self.lock.lock(); let mut out = scoped_writer(); @@ -1595,9 +1615,9 @@ impl ScopedLogger { self.really_disable.store(true, Ordering::Relaxed); return; } - // TODO(port): Zig also disables on flush failure; QuietWriter::flush() - // currently returns () via the SysHooks vtable so flush errors are not - // observable here. Surface a Result once bun_sys exposes it. + // Zig also disables the scope on flush failure; `QuietWriter::flush()` + // returns `()` through the OutputSink vtable, so flush errors are not + // observable here (debug logging only). out.flush(); } } @@ -1615,7 +1635,8 @@ macro_rules! declare_scope { // in downstream crates; allow it at every expansion site. #[allow(non_upper_case_globals, unreachable_pub)] pub static $name: $crate::output::ScopedLogger = $crate::output::ScopedLogger::new( - // TODO(port): lowercase tagname at compile time (Zig did std.ascii.toLower) + // Zig lowercased the tag at comptime; here matching is + // case-insensitive and display lowering happens via `_LowerTag`. stringify!($name), $crate::output::Visibility::Hidden, ); @@ -2111,8 +2132,9 @@ pub fn pretty_fmt_runtime(fmt: &[u8], is_enabled: bool) -> Vec { is_reset = true; break 'picker ""; } else { - // Zig: @compileError — runtime version returns empty - // TODO(port): proc-macro emits compile_error! here + // Unknown tag: the comptime paths reject this (Zig + // @compileError; the `pretty_fmt!` proc-macro at its + // call sites); this runtime path drops the tag. break 'picker ""; } }; @@ -2508,8 +2530,8 @@ macro_rules! debug_warn { /// The Zig original switched on `@typeInfo` of `error_name`. The Rust port accepts anything /// implementing `ErrName` (impl'd for `&[u8]`, `&str`, `bun_core::Error`, `bun_sys::Error`, /// and any `#[derive(strum::IntoStaticStr)]` enum). -// TODO(port): the comptime-literal fast path (is_comptime_name) is dropped — -// could be recovered with a proc-macro overload that detects string literals. +// Zig's comptime-literal fast path (is_comptime_name) is dropped; literal tags +// go through the same runtime rendering as everything else. // // By-value `error_name` is intentional: callers pass `"EACCES"` / `b"tag"` / // `bun_core::Error` (Copy) and `bun_sys::Error` (non-Copy, consumed). Taking @@ -2604,9 +2626,10 @@ impl ErrName for crate::Error { (*self).name().as_bytes() } } -// TODO(port): `impl ErrName for bun_sys::Error` and `bun_sys::SystemErrno` arrive -// via move-in pass in bun_sys (orphan rule allows higher tier to impl this trait). -// TODO(port): blanket impl for `T: Into<&'static str>` (strum enums) once coherence allows. +// Higher-tier impls live with their types (orphan rule allows it): +// `bun_sys::Error` (src/sys/Error.rs) and `SystemErrno` (src/errno/lib.rs). +// A blanket impl for `T: Into<&'static str>` (strum enums) is coherence-blocked; +// each enum impls `ErrName` explicitly instead. // ── ScopedDebugWriter ───────────────────────────────────────────────────── @@ -2646,7 +2669,6 @@ pub fn disable_scoped_debug_writer() { scoped_debug_writer::DISABLE_INSIDE_LOG.set(scoped_debug_writer::DISABLE_INSIDE_LOG.get() + 1); } -// TODO(port): move to bun_core_sys unsafe extern "C" { /// No preconditions; returns the calling process's PID. safe fn getpid() -> c_int; @@ -2677,10 +2699,9 @@ pub(crate) fn init_scoped_debug_writer_at_startup() { bstr::BStr::new(path), )), }; - // TODO(port): bun_sys::Fd::truncate (Windows-only); add to OutputSinkVTable. - // `create_file` above already opens for writing with truncate, so the explicit - // `fd.truncate(0)` from Zig is a no-op here until the vtable entry lands. - let _ = &fd; // windows + // Zig followed with an explicit Windows-only `fd.truncate(0)`; + // `create_file` above already opens for writing with truncate, so + // that call would be a no-op and is dropped here. // SAFETY: single-threaded startup. unsafe { scoped_debug_writer::SCOPED_FILE_WRITER @@ -2775,7 +2796,7 @@ impl BufferedStdin { /// Zig `BufferedReader.read` — fill `dest` from the buffer, refilling from /// the underlying fd until `dest` is full or EOF. Returns `Ok(0)` on EOF. /// - /// PORT NOTE: matches std `BufferedReader.read` fill-to-completion semantics + /// Matches std `BufferedReader.read` fill-to-completion semantics /// (loops on the underlying fd), not POSIX partial-read. pub fn read(&mut self, dest: &mut [u8]) -> Result { let mut written: usize = 0; @@ -2892,7 +2913,6 @@ pub fn stdin_reader() -> StdinReader { #[inline] pub fn buffered_stdin() -> *mut BufferedStdin { BUFFERED_STDIN.get() - // TODO(port): self-ref pointer escape — see error_writer() } /// `bun.Output.buffered_stdin.reader()` — same accessor as [`buffered_stdin`]; diff --git a/src/bun_core/result.rs b/src/bun_core/result.rs index d9d9db3dc145..a654f180dfed 100644 --- a/src/bun_core/result.rs +++ b/src/bun_core/result.rs @@ -265,15 +265,17 @@ impl From for Error { // Windows: `raw_os_error()` returns the raw Win32 `GetLastError()` // code (ERROR_ACCESS_DENIED=5, ERROR_SHARING_VIOLATION=32, …), // NOT a `SystemErrno`. Routing it through `ErrnoNames::SYS.name()` would - // alias garbage (5→EIO, 32→EPIPE). The Zig pipeline first runs - // `Win32Error.toSystemErrno()` (windows_errno.zig:290) before any - // `errno_map` lookup; that table lives in `bun_errno`, which is - // tier-above `bun_core` (dep cycle), so we can't call it here. - // Fall back to `Unexpected` rather than return a wrong name. - // TODO(port): plumb a Win32→SystemErrno hook (or duplicate the - // table) so `?`-propagated `io::Error`s name correctly on Windows. + // alias garbage (5→EIO, 32→EPIPE). Mirror the Zig pipeline, which + // first runs `Win32Error.toSystemErrno()` (windows_errno.zig:290) + // before any `errno_map` lookup; that table lives in `bun_errno` + // (tier-above `bun_core`, dep cycle), reached via the `win32_name` + // method on the `ErrnoNames` link-interface. Unknown codes still + // collapse to `Unexpected` rather than aliasing a wrong name. #[cfg(windows)] - Some(_code) => Self::UNEXPECTED, + Some(code) => match crate::ErrnoNames::SYS.win32_name(code as u32) { + Some(name) => Self::intern(name), + None => Self::UNEXPECTED, + }, None => Self::UNEXPECTED, } } diff --git a/src/bun_core/string/MutableString.rs b/src/bun_core/string/MutableString.rs index 3a7201e35a55..7cbb02c9fc27 100644 --- a/src/bun_core/string/MutableString.rs +++ b/src/bun_core/string/MutableString.rs @@ -156,10 +156,9 @@ impl MutableString { /// identifier, you're going to potentially cause trouble with non-BMP code /// points in target environments that don't support bracketed Unicode escapes. pub fn ensure_valid_identifier(str: &[u8]) -> Result, AllocError> { - // TODO(port): Zig returned `[]const u8` which could be either the input - // borrow or a fresh allocation. Rust cannot express that without a - // lifetime + Cow; for now we always return owned `Box<[u8]>` and copy - // on the borrow paths. Consider `Cow<'a, [u8]>`. + // Zig returned `[]const u8` which could be either the input borrow or a + // fresh allocation; Rust cannot express that without a lifetime + Cow, + // so we always return owned `Box<[u8]>` and copy on the borrow paths. if str.is_empty() { return Ok(Box::<[u8]>::from(b"_".as_slice())); } @@ -175,7 +174,6 @@ impl MutableString { return Ok(Box::<[u8]>::from(b"_".as_slice())); } - // TODO(port): lexer / lexer_tables arrive from move-in (MOVE_DOWN bun_js_parser::{lexer,lexer_tables} → string) use crate::string::lexer as js_lexer; use crate::string::lexer_tables as js_lexer_tables; @@ -257,8 +255,8 @@ impl MutableString { // Zig: list.insertSlice(allocator, 0, str) self.list.extend_from_slice(str); } else { - // Zig: list.replaceRange(allocator, 0, str.len, str) - // TODO(port): verify Vec::splice matches ArrayList.replaceRange semantics + // Zig: list.replaceRange(allocator, 0, str.len, str) — the manual + // overwrite-then-extend below is the same operation for range [0, str.len). let n = str.len().min(self.list.len()); self.list[..n].copy_from_slice(&str[..n]); if str.len() > n { @@ -385,9 +383,8 @@ impl MutableString { } pub fn to_dynamic_owned(&mut self) -> Box<[u8]> { - // TODO(port): Zig `DynamicOwned([]u8)` carried its allocator; with the - // global allocator this collapses to `Box<[u8]>`. Revisit if a distinct - // `bun_ptr::DynamicOwned` type is introduced. + // Zig `DynamicOwned([]u8)` carried its allocator; with the global + // allocator this collapses to `Box<[u8]>`. self.to_owned_slice() } @@ -433,9 +430,9 @@ impl MutableString { } pub fn index_of(&self, str: u8) -> Option { - // TODO(port): Zig signature is `str: u8` but body calls - // `std.mem.indexOf(u8, items, str)` which expects a slice — looks like - // a latent bug in the Zig source. Porting as single-byte search. + // Zig signature is `str: u8` but its body called + // `std.mem.indexOf(u8, items, str)` which expects a slice — a latent + // bug in the (frozen) Zig source. Implemented as single-byte search. self.list.iter().position(|&b| b == str) } @@ -513,8 +510,6 @@ impl<'a> BufferedWriter<'a> { if pending.len() + self.pos > Self::MAX { self.flush()?; } - // PORT NOTE: reshaped for borrowck (cannot call self.remain() while - // borrowing pending.len() against self.pos). let pos = self.pos; self.buffer[pos..pos + pending.len()].copy_from_slice(pending); self.pos += pending.len(); @@ -543,13 +538,11 @@ impl<'a> BufferedWriter<'a> { if pending.len() >= Self::MAX { self.flush()?; - // PORT NOTE: Zig wrote into `this.remain()[0..bytes.len*2]` here, + // The (frozen) Zig wrote into `this.remain()[0..bytes.len*2]` here, // which after `flush()` is `this.buffer[0..bytes.len*2]` — but - // `bytes.len*2 > MAX`, so that indexes past the stack buffer. This - // looks like a latent bug in the Zig (should write into - // `context.list`). Porting the apparent intent: write into the - // freshly-reserved context.list tail. - // TODO(port): confirm and fix upstream. + // `bytes.len*2 > MAX`, so that indexed past the stack buffer (a + // latent Zig bug). This implements the apparent intent: write into + // the freshly-reserved context.list tail. let old = self.context.list.len(); // SAFETY: copy_utf16_into_utf8 writes <= bytes.len*2; trimmed below. let tail = diff --git a/src/bun_core/string/SmolStr.rs b/src/bun_core/string/SmolStr.rs index dade67f31d91..d1a292d0f514 100644 --- a/src/bun_core/string/SmolStr.rs +++ b/src/bun_core/string/SmolStr.rs @@ -62,8 +62,6 @@ impl SmolStr { // ---- public API ------------------------------------------------------- - // TODO(port): Zig `jsonStringify` participates in std.json's structural protocol; - // map to bun's JSON-serialize trait once one exists. pub fn json_stringify(&self, writer: &mut W) -> Result<(), crate::Error> where W: JsonWriter, @@ -140,7 +138,6 @@ impl SmolStr { pub fn from_slice(values: &[u8]) -> Result { if values.len() > Inlined::MAX_LEN { - // TODO(port): verify Vec::::init_capacity / append_slice_assume_capacity API. let mut baby_list = Vec::::with_capacity(values.len()); baby_list.extend_from_slice(values); // PERF(port): was appendSliceAssumeCapacity — profile if hot. @@ -234,7 +231,6 @@ impl Drop for SmolStr { fn drop(&mut self) { if !self.is_inlined() { // SAFETY: ptr/len/cap describe a Vec allocation we own; reconstruct to free. - // TODO(port): verify Vec Drop frees; else dealloc via global allocator directly. let list = unsafe { Vec::::from_raw_parts( self.ptr(), @@ -247,7 +243,7 @@ impl Drop for SmolStr { } } -// TODO(port): placeholder for the std.json `writer: anytype` protocol used by json_stringify. +/// Minimal byte-writer protocol used by `json_stringify` (Zig: std.json's `writer: anytype`). pub trait JsonWriter { fn write(&mut self, bytes: &[u8]) -> Result<(), crate::Error>; } @@ -384,7 +380,7 @@ mod tests { #[test] fn inlined_does_not_allocate() { - // TODO(port): Zig used std.testing.allocator to assert no allocation; no direct + // Zig used std.testing.allocator to assert no allocation; no direct // equivalent here. The is_inlined() check is the observable proxy. let hello = SmolStr::from_slice(b"hello").unwrap(); assert_eq!(5, hello.len()); diff --git a/src/bun_core/string/StringBuilder.rs b/src/bun_core/string/StringBuilder.rs index 38b0f14b48b0..b9cc09af35ca 100644 --- a/src/bun_core/string/StringBuilder.rs +++ b/src/bun_core/string/StringBuilder.rs @@ -9,10 +9,10 @@ use bun_simdutf_sys::simdutf; /// append, then `allocate()` once, then `append()` each slice. Returned slices /// point into the single backing buffer. /// -// TODO(port): the `append*` methods return `&[u8]` borrowing `self.ptr` while -// also taking `&mut self`. Zig hands out aliasing slices freely; in Rust this -// needs either an explicit `'a` on the builder, interior mutability (`Cell` -// for len), or callers must use `StringPointer` offsets instead. +// Note: the `append*` methods return `&[u8]` borrowing `self.ptr` while also +// taking `&mut self`, so callers cannot hold two returned slices at once (Zig +// handed out aliasing slices freely). Callers that need to interleave appends +// use `StringPointer` offsets or the unsafe `append_raw` escape hatch below. #[derive(Default)] pub struct StringBuilder { pub len: usize, @@ -48,7 +48,7 @@ impl StringBuilder { } pub fn count16_z(&mut self, slice: &[u16]) { - // PORT NOTE: WStr has no len method on its DST slice yet; callers pass &[u16]. + // Callers pass &[u16] (WStr has no len method on its DST slice yet). // Zig's `elementLengthUTF16IntoUTF8` is the same simdutf length call when input // is valid; for WTF-16 with lone surrogates the slow path overestimates by 0-1 // bytes which is fine for a capacity reservation. @@ -56,9 +56,9 @@ impl StringBuilder { } pub fn append16(&mut self, slice: &[u16]) -> Option<&mut ZStr> { - // PORT NOTE: fallback_allocator param dropped (global mimalloc). - // PORT NOTE: borrowck — capture buf ptr, drop the &mut borrow before - // mutating self.len, then rebuild ZStr from the raw ptr. + // Zig's fallback_allocator param is dropped (global mimalloc). + // Borrowck: capture buf ptr, drop the &mut borrow before mutating + // self.len, then rebuild ZStr from the raw ptr. let buf = self.writable(); let buf_ptr = buf.as_mut_ptr(); if slice.is_empty() { @@ -163,7 +163,6 @@ impl StringBuilder { } pub fn add_concat(&mut self, slices: &[&[u8]]) -> StringPointer { - // PORT NOTE: reshaped for borrowck — capture self.len before borrowing alloc. let start = self.len; let alloc = self.allocated_slice(); let mut remain = &mut alloc[start..]; @@ -311,8 +310,8 @@ impl StringBuilder { /// After calling this, you are responsible for freeing the underlying memory. /// This StringBuilder should not be used after calling this function. pub fn move_to_slice(&mut self) -> Box<[u8]> { - // TODO(port): Zig wrote into `*[]u8` out-param and reset self. Here we - // reconstruct the Box (allocated in init_capacity/allocate) and hand it back. + // Zig wrote into a `*[]u8` out-param and reset self; here we reconstruct + // the Box (allocated in init_capacity/allocate) and hand it back. // // `take()` first: `*self = Self::default()` drops the old value, and // `Drop` frees the buffer when `ptr` is still `Some` — leaving it set @@ -323,9 +322,9 @@ impl StringBuilder { }; let cap = self.cap; *self = Self::default(); - // SAFETY: ptr came from Box::<[u8]>::new_uninit_slice(cap) leaked above; - // all `cap` bytes have been written iff caller appended everything counted. - // TODO(port): if not fully written this reads uninit bytes — Zig didn't care. + // SAFETY: ptr came from Box::<[u8]>::new_uninit_slice(cap) leaked above. + // Caller contract: every counted byte must have been appended — if not + // fully written, the returned Box exposes uninit bytes (UB to read). unsafe { crate::heap::take(std::ptr::slice_from_raw_parts_mut(ptr.as_ptr(), cap)) } } } diff --git a/src/bun_core/string/StringJoiner.rs b/src/bun_core/string/StringJoiner.rs index f70c32b7a9d3..c902411b3cbf 100644 --- a/src/bun_core/string/StringJoiner.rs +++ b/src/bun_core/string/StringJoiner.rs @@ -5,8 +5,8 @@ use crate::string::strings; use bun_alloc::AllocError; -// PORT NOTE: Zig's `std.mem.Allocator` param field dropped — global mimalloc is used for -// node and duplicated-string allocations. +// Node and duplicated-string allocations use the global allocator (mimalloc); +// there is no per-joiner allocator field. #[derive(Default)] pub struct StringJoiner<'a> { /// Total length of all nodes @@ -76,10 +76,9 @@ impl<'a> StringJoiner<'a> { self.push_owned(Box::from(data)); } - // PORT NOTE: Zig signature was `push(data: []const u8, ?Allocator param)`. - // The optional allocator only encoded ownership of `data`, which has no Rust - // analogue for a borrowed `&[u8]`; callers wanting owned semantics use - // `push_owned`/`push_cloned` instead. + // The Zig signature took an optional allocator that only encoded ownership + // of `data`, which has no Rust analogue for a borrowed `&[u8]`; callers + // wanting owned semantics use `push_owned`/`push_cloned` instead. pub fn push(&mut self, data: &'a [u8]) { if data.is_empty() { return; diff --git a/src/bun_core/string/escapeRegExp.rs b/src/bun_core/string/escapeRegExp.rs index b78434c016bc..4e3b4761952d 100644 --- a/src/bun_core/string/escapeRegExp.rs +++ b/src/bun_core/string/escapeRegExp.rs @@ -2,9 +2,6 @@ use crate::string::strings; const SPECIAL_CHARACTERS: &[u8] = b"|\\{}()[]^$+*?.-"; -// TODO(port): writer trait — Zig uses `*std.Io.Writer` (byte writer). Mapping to -// `&mut impl std::io::Write` per PORTING.md; swap to the concrete crate-local -// byte-writer trait once `bun_io` is wired. pub fn escape_reg_exp( input: &[u8], writer: &mut W, @@ -57,8 +54,7 @@ pub fn escape_reg_exp_for_package_name_matching( writer.write_all(remain) } -// PORT NOTE: the Zig file re-exported `jsEscapeRegExp` / `jsEscapeRegExpForPackageNameMatching` -// from `../jsc/bun_string_jsc.zig`. Per PORTING.md these `*_jsc` alias lines are deleted — -// the JS-facing wrappers live in the `*_jsc` crate as extension-trait methods. +// The JS-facing wrappers (`jsEscapeRegExp` / `jsEscapeRegExpForPackageNameMatching`) +// live in the `*_jsc` crate as extension-trait methods. // ported from: src/string/escapeRegExp.zig diff --git a/src/bun_core/string/identifier.rs b/src/bun_core/string/identifier.rs index 5819dfabc256..e77613fc68a7 100644 --- a/src/bun_core/string/identifier.rs +++ b/src/bun_core/string/identifier.rs @@ -51,9 +51,10 @@ pub fn is_identifier(text: &[u8]) -> bool { /// Surrogate decoding is open-coded on purpose: an unpaired high surrogate /// (0xD800..=0xDBFF not followed by a low surrogate) advances ONE unit and is /// fed raw to `is_identifier_start/part` — exactly matching Zig -/// `lexer.zig:3091-3096`. `crate::string::strings::utf16_codepoint` would advance -/// TWO units in that case (see immutable.rs:1644 PORT NOTE), so do NOT swap it -/// in here. +/// `lexer.zig:3091-3096`. `crate::string::strings::utf16_codepoint` (defined in +/// `immutable/unicode.rs`, re-exported through `immutable.rs`) would advance +/// TWO units in that case — its lead-surrogate arm returns `len: 2` even when +/// the next unit is not a trail surrogate — so do NOT swap it in here. pub fn is_identifier_utf16(text: &[u16]) -> bool { let n = text.len(); if n == 0 { @@ -78,12 +79,13 @@ pub fn is_identifier_utf16(text: &[u16]) -> bool { // ────────────────────────────────────────────────────────────────────────── // The remainder of this file is auto-generated. Do not edit. -// TODO(port): re-run the identifier-table generator with .rs output instead -// of post-processing the .zig; tables below were mechanically transcribed. +// The tables below were mechanically transcribed from the generated .zig; +// to regenerate, re-run the identifier-table generator with .rs output. // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: Zig `u21` codepoint type → `u32`. Callers must pass cp <= 0x10FFFF -// (stage1 tables are sized for that range); out-of-range indexes panic. +// Zig used a `u21` codepoint type; here it is `u32`. Callers must pass +// cp <= 0x10FFFF (stage1 tables are sized for that range); out-of-range +// indexes panic. /// isIDStartESNext checks if a codepoint is valid in the isIDStartESNext category pub(crate) fn is_id_start_es_next(cp: u32) -> bool { diff --git a/src/bun_core/string/immutable.rs b/src/bun_core/string/immutable.rs index e4f4549c3ec8..1b34ff45315c 100644 --- a/src/bun_core/string/immutable.rs +++ b/src/bun_core/string/immutable.rs @@ -467,7 +467,7 @@ pub fn contains_char(self_: &[u8], char: u8) -> bool { #[inline] pub fn contains_char_t>(self_: &[T], char: u8) -> bool { - // TODO(port): Zig dispatched on T at comptime; in Rust we branch on size_of. + // Zig dispatched on T at comptime; here we branch on size_of (const-folded). if core::mem::size_of::() == 1 { contains_char(reinterpret_to_u8(self_), char) } else { @@ -511,9 +511,9 @@ pub fn index_of_any16(self_: &[u16], str: &'static [u16]) -> Option(str: &[T], chars: &'static [T]) -> Option { - // TODO(port): Zig specialized T==u8 → index_of_any (highway). Rust cannot - // dispatch on type identity without specialization; callers with u8 should - // call index_of_any directly. + // Zig specialized T==u8 → index_of_any (highway). Rust cannot dispatch on + // type identity without specialization; callers with u8 should call + // index_of_any directly. for (i, c) in str.iter().enumerate() { // PERF(port): was `inline for` over chars — profile if hot. for a in chars { @@ -572,7 +572,8 @@ pub fn in_map_case_insensitive( #[inline] pub fn contains_any(in_: &[&[u8]], target: &[u8]) -> bool { - // TODO(port): Zig accepted `anytype` and handled both `[]const u8` and `u8` elements. + // Zig accepted `anytype` (both `[]const u8` and `u8` elements); Rust + // callers pass slices — use `contains_char` for single-byte targets. for str in in_ { if contains(str, target) { return true; @@ -680,7 +681,7 @@ pub fn index_equal_any(in_: &[&[u8]], target: &[u8]) -> Option { } pub fn repeating_alloc(count: usize, char: u8) -> Result, AllocError> { - // PORT NOTE: allocator param dropped (global mimalloc). + // allocator param dropped (global mimalloc). Ok(vec![char; count].into_boxed_slice()) } @@ -773,8 +774,8 @@ pub fn index_of(self_: &[u8], str: &[u8]) -> Option { } pub fn index_of_t(haystack: &[T], needle: &[T]) -> Option { - // TODO(port): Zig specialized T==u8 → index_of (memmem). Callers with u8 - // should call index_of directly; generic path uses naive search. + // Zig specialized T==u8 → index_of (memmem). Callers with u8 should call + // index_of directly; this generic path uses naive search. if needle.is_empty() { return Some(0); } @@ -832,7 +833,7 @@ impl<'a> SplitIterator<'a> { } pub fn cat(first: &[u8], second: &[u8]) -> Result, AllocError> { - // PORT NOTE: allocator param dropped (global mimalloc). + // allocator param dropped (global mimalloc). let mut out = Vec::with_capacity(first.len() + second.len()); out.extend_from_slice(first); out.extend_from_slice(second); @@ -906,9 +907,9 @@ impl StringOrTinyString { } } - // PORT NOTE: Zig deinit was a no-op (commented-out free). No Drop impl. + // Zig deinit was a no-op (commented-out free). No Drop impl. - // PORT NOTE: plain `#[inline]` (not `#[inline(always)]`). These are tiny + // plain `#[inline]` (not `#[inline(always)]`). These are tiny // generic delegators: a length check plus a tail call into the non-generic // `init`/`init_lower_case` or the `Appender` method. `#[inline]` lets the // small fast path fold into callers (and lets duplicate `A` instantiations @@ -1230,7 +1231,8 @@ pub fn eql(self_: &[u8], other: &[u8]) -> bool { } pub fn eql_comptime_t(self_: &[T], alt: &'static [u8]) -> bool { - // TODO(port): Zig dispatched on T at comptime (u16 → eql_comptime_utf16). + // Zig dispatched on T at comptime (u16 → eql_comptime_utf16); here we + // branch on size_of (const-folded). if core::mem::size_of::() == 2 { // `NoUninit` + size_of::()==2 lets bytemuck prove the &[T]→&[u16] // reinterpret is sound (align checked at runtime; T is u16 in practice). @@ -1276,7 +1278,7 @@ pub fn has_prefix_comptime_utf16(self_: &[u16], alt: &'static [u8]) -> bool { } pub fn has_prefix_comptime_type(self_: &[T], alt: &'static [T]) -> bool { - // TODO(port): Zig accepted heterogeneous `alt: anytype` and widened u8→u16 via `w(alt)`. + // Zig accepted heterogeneous `alt: anytype` and widened u8→u16 via `w(alt)`. // Rust callers must pass the correctly-typed literal (use `crate::string::w!` for u16). self_.len() >= alt.len() && eql_comptime_check_len_with_type::(&self_[0..alt.len()], alt) @@ -1322,7 +1324,7 @@ pub fn eql_comptime_check_len_with_type bool { - // PORT NOTE: Zig coerced array-by-value `b` to a pointer here. The Zig + // Zig coerced array-by-value `b` to a pointer here. The Zig // version's `comptime` literal is unenforceable in Rust, so accept any // slice; callers are still expected to pass literals. eql_comptime_check_len_with_known_type::(a, b) @@ -1341,7 +1343,7 @@ pub fn eql_case_insensitive_asciii_check_length(a: &[u8], b: &[u8]) -> bool { eql_case_insensitive_ascii(a, b, true) } -// PORT NOTE: Zig's `comptime check_len: bool` was first ported as a const +// Zig's `comptime check_len: bool` was first ported as a const // generic, but the dominant call shape across the tree passes it as a runtime // 3rd arg (`eql_case_insensitive_ascii(a, b, true)`). Accept it at runtime — // the branch is trivially predicted/inlined; callers wanting the @@ -1409,7 +1411,7 @@ pub fn eql_long_t(a_str: &[T], b_str: eql_long(reinterpret_to_u8(a_str), reinterpret_to_u8(b_str), false) } -// PORT NOTE: same rationale as `eql_case_insensitive_ascii` — Zig's +// same rationale as `eql_case_insensitive_ascii` — Zig's // `comptime check_len: bool` becomes a runtime 3rd arg to match the dominant // ported call shape (`eql_long(a, b, true)`). #[inline] @@ -1495,7 +1497,7 @@ pub fn append(self_: &[u8], other: &[u8]) -> Box<[u8]> { #[inline] pub fn concat_alloc_t(strs: &[&[T]]) -> Result, AllocError> { - // PORT NOTE: Zig took `strs: anytype` (tuple) and inline-for'd. Slice-of-slices here. + // Zig took `strs: anytype` (tuple) and inline-for'd. Slice-of-slices here. let len: usize = strs.iter().map(|s| s.len()).sum(); let mut buf = Vec::with_capacity(len); for s in strs { @@ -1616,7 +1618,7 @@ pub fn index_of_space_or_newline_or_non_ascii(slice_: &[u8], offset: u32) -> Opt } let i = highway::index_of_space_or_newline_or_non_ascii(remaining)?; - // PORT NOTE: Zig uses @truncate here (immutable.zig:1194); match wrapping semantics + // Zig uses @truncate here (immutable.zig:1194); match wrapping semantics // instead of try_from().unwrap() which would panic on >4GB inputs. Some(i as u32 + offset) } @@ -1641,7 +1643,7 @@ pub fn index_of_newline_or_non_ascii_check_start( } let i = highway::index_of_newline_or_non_ascii(remaining)?; - // PORT NOTE: Zig uses @truncate here (immutable.zig:1212); match wrapping semantics + // Zig uses @truncate here (immutable.zig:1212); match wrapping semantics // instead of try_from().unwrap() which would panic on >4GB inputs. Some(i as u32 + offset) } @@ -1700,7 +1702,7 @@ pub fn index_of_needs_url_encode(slice: &[u8]) -> Option { || char == b'|' || char == b'~' { - // PORT NOTE: Zig uses @truncate (immutable.zig:1292); match wrapping semantics. + // Zig uses @truncate (immutable.zig:1292); match wrapping semantics. return Some(i as u32); } } @@ -1714,7 +1716,7 @@ pub fn index_of_char_z(slice_z: &crate::string::ZStr, char: u8) -> Option { } pub fn index_of_char(slice: &[u8], char: u8) -> Option { - // PORT NOTE: Zig uses @truncate (immutable.zig:1304); match wrapping semantics. + // Zig uses @truncate (immutable.zig:1304); match wrapping semantics. index_of_char_usize(slice, char).map(|i| i as u32) } @@ -1760,7 +1762,7 @@ pub fn index_of_not_char(slice: &[u8], char: u8) -> Option { // PERF(port): Zig used @Vector(16,u8) != splat + @ctz. Scalar loop here. for (i, ¤t) in slice.iter().enumerate() { if current != char { - // PORT NOTE: Zig uses @truncate (immutable.zig:1360); match wrapping semantics. + // Zig uses @truncate (immutable.zig:1360); match wrapping semantics. return Some(i as u32); } } @@ -2010,7 +2012,7 @@ pub fn index_of_line_ranges( // PERF(port): was assume_capacity let _ = ranges.push(LineRange { start: 0, - // PORT NOTE: Zig uses @truncate(text.len) (immutable.zig:1595); match wrapping semantics. + // Zig uses @truncate(text.len) (immutable.zig:1595); match wrapping semantics. end: text.len() as u32, }); // OOM/capacity: Zig aborts; port keeps fire-and-forget } @@ -2048,7 +2050,7 @@ pub fn index_of_line_ranges( } let _ = ranges.push(LineRange { start: 0, - // PORT NOTE: Zig uses @truncate(text.len) (immutable.zig:1635); match wrapping semantics. + // Zig uses @truncate(text.len) (immutable.zig:1635); match wrapping semantics. end: text.len() as u32, }); return ranges; @@ -2141,7 +2143,7 @@ pub fn first_non_ascii16(slice: &[u16]) -> Option { // loop here; consider portable_simd or a simdutf utf16 validator if hot. for (i, &char) in slice.iter().enumerate() { if char > 127 { - // PORT NOTE: Zig uses @truncate(i) (immutable.zig:1766); match wrapping semantics. + // Zig uses @truncate(i) (immutable.zig:1766); match wrapping semantics. return Some(i as u32); } } @@ -2181,7 +2183,7 @@ pub fn length_of_leading_whitespace_ascii(slice: &[u8]) -> usize { } pub fn join(slices: &[&[u8]], delimiter: &[u8]) -> Result, AllocError> { - // PORT NOTE: std.mem.join — reimplemented over Vec (no allocator param). + // std.mem.join — reimplemented over Vec (no allocator param). if slices.is_empty() { return Ok(Box::default()); } @@ -2235,12 +2237,12 @@ pub fn cmp_strings_desc(_: (), a: &[u8], b: &[u8]) -> bool { pub type U3Fast = u8; pub fn sort_asc(in_: &mut [&[u8]]) { - // TODO: experiment with simd to see if it's faster + // Perf: a SIMD comparator might be faster here; never measured. in_.sort_unstable_by(|a, b| order(a, b)); } pub fn sort_desc(in_: &mut [&[u8]]) { - // TODO: experiment with simd to see if it's faster + // Perf: a SIMD comparator might be faster here; never measured. in_.sort_unstable_by(|a, b| order(b, a)); } @@ -2529,7 +2531,7 @@ pub fn concat_if_needed( let mut remain: &[u8] = dest; for arg in args { - // PORT NOTE: Zig has `args.len` here (likely a bug); preserved verbatim. + // Zig has `args.len` here (likely a bug); preserved verbatim. if args.len() > remain.len() { break 'brk true; } @@ -2626,7 +2628,8 @@ impl core::fmt::Display for QuoteEscapeFormat<'_> { /// Generic. Works on &[u8], &[u16], etc #[inline] pub fn index_of_scalar(input: &[T], scalar: T) -> Option { - // TODO(port): Zig specialized T==u8 → index_of_char_usize (highway). + // Zig specialized T==u8 → index_of_char_usize (highway); here we branch + // on size_of (const-folded). if core::mem::size_of::() == 1 { let scalar_u8 = reinterpret_to_u8(core::slice::from_ref(&scalar))[0]; return index_of_char_usize(reinterpret_to_u8(input), scalar_u8); @@ -2750,12 +2753,10 @@ pub fn percent_encode_write( // ───────────── re-exports from sibling modules ───────────── -// Unicode core is re-exported at the top of the file; the remaining submodule -// re-exports below are still incomplete. +// Unicode core is re-exported at the top of the file. Further transcoding +// helpers (unicode_draft) and path helpers (bun_paths) are re-exported on +// demand as callers need them — see the `crate::strings` re-export block below. pub use crate::string::escape_reg_exp::{escape_reg_exp, escape_reg_exp_for_package_name_matching}; -// TODO(port): re-export the rest of the transcoding suite from unicode_draft — -// to_utf8_alloc / to_utf16_alloc / convert_* / copy_*_into_* / EncodeIntoResult / BOM / etc. -// TODO(port): re-export paths::{to_w_path, basename, add_nt_path_prefix, ...} crate::declare_scope!(STR, hidden); // `log` is `bun.Output.scoped(.STR, .hidden)` — use `crate::scoped_log!(STR, ...)`. @@ -2828,7 +2829,6 @@ impl ANSIIterator { } } -// TODO(port): move to _sys unsafe extern "C" { // `&mut ANSIIterator` is ABI-identical to the C++ `ANSIIterator*` (thin // non-null pointer to a `#[repr(C)]` POD struct); C++ reads `input`/ @@ -3086,7 +3086,7 @@ pub fn to_utf8_list_with_type(mut list: Vec, utf16: &[u16]) -> Result for ExactSizeMatcher<12> { type T = u128; const ZERO: u128 = 0; @@ -97,8 +97,7 @@ where /// the non-const trait method `read_le` on stable. #[inline(always)] pub fn case(str: &'static [u8]) -> >::T { - // if (str.len < max_bytes) { zero-pad } else if (== max_bytes) { read } else { @compileError } - // PORT NOTE: reshaped — Zig branches on `<` vs `==` vs `@compileError`; + // Zig branches on `<` (zero-pad) vs `==` (read) vs `@compileError`; // here we assert `<=` (the compile error) and unify the two valid arms. assert!( str.len() <= MAX_BYTES, diff --git a/src/bun_core/string/immutable/unicode.rs b/src/bun_core/string/immutable/unicode.rs index fffb2df1b992..34360aa55159 100644 --- a/src/bun_core/string/immutable/unicode.rs +++ b/src/bun_core/string/immutable/unicode.rs @@ -57,7 +57,7 @@ fn append_u16_as_u8(dst: &mut Vec, src: &[u16]) { /// every caller in-tree is one of these. `ZERO_VALUE` is the per-type sentinel /// Zig threaded as a `comptime_int` (`-1` for i32, `0` for u32). /// -/// PORT NOTE: bounds widened from `From` to include the bit-ops needed by +/// Bounds widened from `From` to include the bit-ops needed by /// `decode_wtf8_rune_t_multibyte` plus a `from_u32` constructor (folds in the /// previously separate `FromU32`/`FromU32Const` helper traits). pub trait CodePointZero: @@ -533,7 +533,7 @@ pub fn copy_latin1_into_ascii(dest: &mut [u8], src: &[u8]) { // https://zig.godbolt.org/z/qezsY8T3W let remain_in_u64_len = remain.len() - (remain.len() % VECTOR_SIZE); let to_in_u64_len = to.len() - (to.len() % VECTOR_SIZE); - // PORT NOTE: reshaped for borrowck — operate on byte indices instead of bytesAsSlice(u64). + // Reshaped for borrowck — operate on byte indices instead of bytesAsSlice(u64). let end_vector_len = (remain_in_u64_len / 8).min(to_in_u64_len / 8); let mut idx = 0usize; // using the pointer instead of the length is super important for the codegen @@ -664,7 +664,7 @@ impl BOM { let n = Self::UTF8_BYTES.len(); let len = list.len(); list.copy_within(n.., 0); - // PORT NOTE: Zig returned a subslice without truncating; we mirror by returning a slice. + // Zig returned a subslice without truncating; we mirror by returning a slice. &list[..len - n] } BOM::Utf16Le => { @@ -675,8 +675,8 @@ impl BOM { ); list.clear(); list.extend_from_slice(&out); - // TODO(port): Zig returned `out` (the new alloc); returning list slice instead to honor - // "always points to the base of the input" doc comment. + // Zig returned `out` (the new alloc); we return the list slice instead to + // honor the "always points to the base of the input" doc comment. &list[..] } _ => { @@ -711,9 +711,6 @@ pub enum ToUTF16Error { crate::oom_from_alloc!(ToUTF16Error); -// TODO(port): move to *_jsc — `TestingAPIs` re-exported from bun_string_jsc. -// pub const TestingAPIs = @import("../../jsc/bun_string_jsc.zig").UnicodeTestingAPIs; - pub fn to_utf16_alloc_maybe_buffered( bytes: &[u8], ) -> Result, [u8; 3], u8)>, ToUTF16Error> { @@ -890,7 +887,7 @@ pub fn utf16_codepoint(input: &[u16]) -> UTF16Replacement { ..Default::default() }; } - // PORT NOTE (unicode.zig:1378): Zig falls through with len=2 even when input[1] + // Per unicode.zig:1378, Zig falls through with len=2 even when input[1] // is not a trail surrogate; preserve that iteration behaviour. UTF16Replacement { len: 2, @@ -922,7 +919,7 @@ macro_rules! to_utf16_literal { /// Zig: `pub fn literal(comptime T, comptime str) *const [N:0]T`. /// In Rust: `b"..."` for u8, `$crate::w!("...")` for u16. No runtime fn possible. -// TODO(port): callers should use byte/wide literals directly; this is a stub for diff parity. +/// New callers should use byte/wide literals directly; this exists for diff parity. #[macro_export] macro_rules! literal { (u8, $s:literal) => { @@ -1191,9 +1188,8 @@ pub fn element_length_utf8_into_utf16(utf8: &[u8]) -> usize { utf8_remaining = &utf8_remaining[i..]; - // PORT NOTE: Zig calls `utf16Codepoint` (which takes []const u16) on a []const u8 here — - // preserved as a TODO; this branch is dead when use_simdutf is true. - // TODO(port): dead non-simdutf path passes wrong slice type in Zig source + // The Zig source calls `utf16Codepoint` (which takes []const u16) on a []const u8 + // here; that quirk is preserved as-is. This branch is dead when USE_SIMDUTF is true. let replacement = { let n = utf8_remaining.len() / 2; if n == 0 { diff --git a/src/bun_core/string/mod.rs b/src/bun_core/string/mod.rs index acfb82a04290..64c287fb4460 100644 --- a/src/bun_core/string/mod.rs +++ b/src/bun_core/string/mod.rs @@ -292,7 +292,8 @@ impl String { callback: ExternalStringImplFreeFunction, ) -> Self { use core::ffi::c_void; - // PORT NOTE: Zig asserted `@typeInfo(Ctx) == .pointer` at comptime. + // `Ctx` must be a pointer-sized, pointer-aligned handle (the Zig + // original required a raw pointer type at comptime). struct AssertPtrSized(core::marker::PhantomData); impl AssertPtrSized { const OK: () = { @@ -309,11 +310,11 @@ impl String { callback(ctx, bytes.as_ptr().cast_mut().cast::(), bytes.len()); return Self::DEAD; } - // PORT NOTE: Zig asserted `@typeInfo(Ctx) == .pointer` (raw pointer, no - // destructor). The Rust const-assert only checks size, so an owning - // pointer-sized `Ctx` (e.g. `Box`) would otherwise be dropped here - // and later double-freed by the WTF finalizer. Ownership transfers to - // the external string; suppress the local drop. + // The const-assert above only checks size/alignment (Zig required a raw + // pointer with no destructor), so an owning pointer-sized `Ctx` (e.g. + // `Box`) would otherwise be dropped here and later double-freed by + // the WTF finalizer. Ownership transfers to the external string; + // suppress the local drop. let ctx = core::mem::ManuallyDrop::new(ctx); // SAFETY: Ctx is pointer-sized and pointer-aligned (const-asserted // above); read the bits as `*mut c_void`. @@ -364,8 +365,8 @@ impl String { /// `(comptime fmt, args: anytype)` into [`core::fmt::Arguments`]. pub fn create_format(args: core::fmt::Arguments<'_>) -> Self { use core::fmt::Write; - // PORT NOTE: Zig used a 512-byte stackFallback; this is a cold path - // (error messages), so a heap buffer is fine. + // Cold path (error messages), so a heap buffer is fine (the Zig + // original used a 512-byte stackFallback). if let Some(s) = args.as_str() { return Self::clone_utf8(s.as_bytes()); } @@ -657,9 +658,8 @@ impl String { /// outlive the result. pub fn trunc(&self, len: usize) -> String { if self.length() <= len { - // PORT NOTE: Zig returns `this` by value with no refcount bump; - // `String` is `Copy` here (POD #[repr(C)]), so a plain copy - // matches the Zig pass-by-value semantics. + // No refcount bump: `String` is `Copy` here (POD #[repr(C)]), so a + // plain copy matches the Zig pass-by-value semantics. return *self; } String::init(self.to_zig_string().trunc(len)) @@ -769,7 +769,7 @@ impl String { } pub fn eql_utf8(&self, other: &[u8]) -> bool { - // PORT NOTE: no `as_utf8()` fast-path here — for a 16-bit ZigString, + // No `as_utf8()` fast-path here — for a 16-bit ZigString, // `as_utf8()` would call `slice()` (which debug-asserts !is_16bit) and // `is_all_ascii` on the wrong byte view. Match Zig's `eqlUTF8` and go // straight through encoding-aware `to_utf8_without_ref`. @@ -1579,8 +1579,8 @@ impl ZigString { /// `ZigString.latin1ByteLength` (ZigString.zig:211). pub fn latin1_byte_length(self) -> usize { if self.is_utf8() { - // PORT NOTE: Zig: `@panic("TODO")` — never implemented for UTF-8 - // sources. Match Zig behaviour. + // Zig panics here ("TODO") — never implemented for UTF-8 sources. + // Match Zig behaviour. unreachable!("ZigString.latin1ByteLength from UTF-8 — unimplemented in Zig"); } self.len @@ -1651,8 +1651,6 @@ impl ZigString { if self.is_16bit() { return strings::eql_comptime_utf16(self.utf16_slice(), other); } - // PORT NOTE: Zig branched on `comptime strings.isAllASCII(other)`; - // demoted to runtime length-check + byte compare. if self.len != other.len() { return false; } @@ -1947,7 +1945,7 @@ impl ZigString { if self.len == 0 { return Vec::new(); } - // PORT NOTE: order matches ZigString.zig:233-253 — `isUTF8()` is tested + // Order matters (matches ZigString.zig:233-253) — `isUTF8()` is tested // before `is16Bit()` so a string with both tags set takes the UTF-8 arm. if self.is_utf8() { return with_sentinel(self.slice().to_vec()); @@ -1964,8 +1962,8 @@ impl ZigString { /// (ZigString.zig:693). Unlike `to_slice`, this never borrows the source /// bytes, so the result outlives a GC'd `JSString` that produced `self`. /// - /// PORT NOTE: Zig returned `OOM!Slice`; with mimalloc as the global - /// allocator OOM aborts the process, so this is infallible. + /// Infallible (Zig returned `OOM!Slice`): with mimalloc as the global + /// allocator, OOM aborts the process. pub fn to_slice_clone(&self) -> ZigStringSlice { if self.len == 0 { return ZigStringSlice::EMPTY; @@ -1977,11 +1975,9 @@ impl ZigString { /// the end (`slice().as_ptr()` is a valid C string of length `slice().len()`). /// `slice()` itself does *not* include the terminator. /// - /// PORT NOTE: the Zig method this targets was never instantiated (lazy - /// compilation); JSString/JSValue callers reached for it but no `.zig` - /// caller forced codegen. Semantics here match `toOwnedSliceZ` wrapped in - /// a `Slice` so `JSValue::to_slice_z` / `JSString::to_slice_z` get the - /// `[:0]` guarantee they document. + /// Semantics match `toOwnedSliceZ` wrapped in a `Slice` so + /// `JSValue::to_slice_z` / `JSString::to_slice_z` get the `[:0]` + /// guarantee they document. pub fn to_slice_z(&self) -> ZigStringSlice { if self.len == 0 { // Static "" already points at a NUL byte. diff --git a/src/bun_core/string/wtf.rs b/src/bun_core/string/wtf.rs index af41a5496460..3188b31913a4 100644 --- a/src/bun_core/string/wtf.rs +++ b/src/bun_core/string/wtf.rs @@ -1,5 +1,5 @@ use crate::string::strings; -// TODO(port): ZigString.Slice is a nested type in Zig; in Rust it lives alongside ZigString. +// Note (port): ZigString.Slice is a nested type in Zig; in Rust it lives alongside ZigString. use crate::string::ZigStringSlice; // Canonical layout lives in `bun_alloc` (lowest-tier crate) so the @@ -177,7 +177,7 @@ impl WTFStringImplExt for WTFStringImplStruct { } } -// PORT NOTE: Zig's `StringImplAllocator` was a `std.mem.Allocator` vtable trick +// Note (port): Zig's `StringImplAllocator` was a `std.mem.Allocator` vtable trick // (alloc() bumped ref, free() dropped it) so a `ZigString.Slice` would deref the // WTFStringImpl when freed. Replaced by `ZigStringSlice::WTF { .. }` explicit // ownership variant — see `to_latin1_slice` above. No allocator trait needed. diff --git a/src/bun_core/tty.rs b/src/bun_core/tty.rs index 1e0fc8f7d6ae..acd37695f9f1 100644 --- a/src/bun_core/tty.rs +++ b/src/bun_core/tty.rs @@ -50,7 +50,6 @@ impl Drop for RawModeGuard { } } -// TODO(port): move to bun_core_sys (or appropriate *_sys crate) unsafe extern "C" { safe fn Bun__ttySetMode(fd: c_int, mode: c_int) -> c_int; } diff --git a/src/bun_core/util.rs b/src/bun_core/util.rs index bb320020afa7..6892c2d34563 100644 --- a/src/bun_core/util.rs +++ b/src/bun_core/util.rs @@ -250,7 +250,7 @@ impl ZStr { // The slice metadata of the returned `Box` covers `bytes.len() + 1` // (i.e. INCLUDES the trailing NUL) so `Drop` deallocates the full // allocation; `as_bytes()` will therefore include the trailing NUL. - // TODO(port): retire once all `Box` fields are migrated to `ZBox`. + // Retire once all `Box` fields are migrated to `ZBox`. pub fn boxed(bytes: &[u8]) -> Box { let mut v = Vec::with_capacity(bytes.len() + 1); v.extend_from_slice(bytes); @@ -1464,9 +1464,10 @@ pub unsafe fn fd_path_raw(fd: Fd, buf: *mut u8, cap: usize) -> isize { } /// Wide-char fd → path (Windows `GetFinalPathNameByHandleW`). Returns code -/// units written (>0), <0 on error, 0 on non-Windows. Body is a single -/// kernel32 call, so it lives at T0 — moved down from `bun_sys` per -/// PORTING.md (no cross-crate extern). +/// units written (>0), -1 on lookup failure (Zig `error.FileNotFound`), +/// -2 when the buffer is too small (Zig `error.NameTooLong`), 0 on +/// non-Windows. Body is a single kernel32 call, so it lives at T0 — moved +/// down from `bun_sys` per PORTING.md (no cross-crate extern). /// /// SAFETY: `buf` must be valid for `cap` writable `u16` units. pub unsafe fn fd_path_raw_w(fd: Fd, buf: *mut u16, cap: usize) -> isize { @@ -1487,9 +1488,14 @@ pub unsafe fn fd_path_raw_w(fd: Fd, buf: *mut u16, cap: usize) -> isize { // out_buffer.len) return error.NameTooLong;` — `>=` because a return // value equal to `cap` is the buffer-too-small sentinel (required size // including NUL), not a successful write of `cap` chars. - if n == 0 || n >= cap { + if n == 0 { + // Lookup failure → Zig `error.FileNotFound`. return -1; } + if n >= cap { + // Buffer too small → Zig `error.NameTooLong`. + return -2; + } // Strip the `\\?\` prefix if present so callers see a plain DOS path // (matches `bun_sys::windows::GetFinalPathNameByHandle` post-processing). // Work entirely through raw-pointer reads/writes — never form a `&[u16]` @@ -2835,7 +2841,7 @@ pub fn is_writable(fd: Fd) -> Pollable { result, polls[0].revents ); - // PORT NOTE: faithful port of bun.zig:679 — yes, the `WRNORM`-set branch + // Faithful port of bun.zig:679 — yes, the `WRNORM`-set branch // returns `.hup` (not `.ready`). Kept verbatim to match upstream behaviour. if result && (polls[0].revents & ws2_32::POLLWRNORM) != 0 { Pollable::Hup @@ -2911,7 +2917,7 @@ pub fn self_exe_path() -> Result<&'static ZStr, crate::Error> { static CELL: Once> = Once::new(); let r = CELL.get_or_init(|| { let path = std::env::current_exe().map_err(crate::Error::from)?; - // PORT NOTE: Zig's `std.fs.selfExePath` resolves symlinks. Rust's + // Zig's `std.fs.selfExePath` resolves symlinks. Rust's // `current_exe()` already does on Linux (`readlink /proc/self/exe`), // but on Darwin it returns the raw `_NSGetExecutablePath` result and on // Windows it returns the raw `GetModuleFileNameW` result — neither @@ -2931,7 +2937,7 @@ pub fn self_exe_path() -> Result<&'static ZStr, crate::Error> { } #[cfg(windows)] { - // PORT NOTE: Zig stored the WTF-8 form. `into_string()` rejects unpaired + // Zig stored the WTF-8 form. `into_string()` rejects unpaired // surrogates; fall back to the lossy form (Windows exe paths are valid // Unicode in practice). let mut s = path @@ -3227,7 +3233,7 @@ macro_rules! __runtime_embed_impl { // for building a single contiguous buffer. Allocator param dropped per // PORTING.md §Allocators (always `bun.default_allocator`). // -// PORT NOTE: returned sub-slices borrow `*self`, but in Zig they alias the +// Returned sub-slices borrow `*self`, but in Zig they alias the // final `allocated_slice()` and outlive the builder. To keep that pattern // without self-referential lifetimes, callers stash `(offset, len)` via // `StringPointer` (see install/hosted_git_info.rs). @@ -4502,7 +4508,9 @@ pub fn which<'a>(buf: &'a mut PathBuffer, path: &[u8], cwd: &[u8], bin: &[u8]) - } #[cfg(not(unix))] { - // TODO(port): Windows X_OK via GetFileAttributesW; defer to bun_which. + // No X_OK probe here: this tier-0 helper is only reached from the + // linux/freebsd `spawn_sync_inherit` path. Windows callers resolve + // executables via `bun_which` (PATHEXT-aware) instead. } None }; @@ -4881,7 +4889,34 @@ pub mod spawn_ffi { } } +/// Stdin disposition for [`spawn_sync_inherit`] / [`spawn_sync_inherit_no_stdin`]. +/// Mirrors Zig `std.process.Child.stdin_behavior` (`.Inherit` / `.Ignore`); +/// the `.Ignore` case wires fd 0 to `/dev/null` (`NUL` on Windows) so the +/// child never blocks on a TTY read. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum StdinBehavior { + Inherit, + Ignore, +} + +/// Spawn argv, inherit stdout/stderr, **ignore stdin** (fd 0 ← /dev/null), +/// wait. Port of Zig `std.process.Child` with `stdin_behavior = .Ignore`, +/// `stdout_behavior = .Inherit`, `stderr_behavior = .Inherit` (e.g. +/// init_command.zig:1211-1222). +pub fn spawn_sync_inherit_no_stdin( + argv: &[impl AsRef<[u8]>], +) -> Result { + spawn_sync_inherit_impl(argv, StdinBehavior::Ignore) +} + pub fn spawn_sync_inherit(argv: &[impl AsRef<[u8]>]) -> Result { + spawn_sync_inherit_impl(argv, StdinBehavior::Inherit) +} + +fn spawn_sync_inherit_impl( + argv: &[impl AsRef<[u8]>], + stdin: StdinBehavior, +) -> Result { #[cfg(unix)] // SAFETY: argv strings are owned `ZBox`es (NUL-terminated) kept alive in // `cargs` for the duration of the spawn; `ptrs`/`environ` are null- @@ -4920,12 +4955,24 @@ pub fn spawn_sync_inherit(argv: &[impl AsRef<[u8]>]) -> Result]) -> Result(), environ.cast::<*mut core::ffi::c_char>(), ); + if !actions_ptr.is_null() { + libc::posix_spawn_file_actions_destroy(&raw mut actions); + } if rc != 0 { return Err(crate::Error::from_errno(rc)); } @@ -4980,6 +5054,16 @@ pub fn spawn_sync_inherit(argv: &[impl AsRef<[u8]>]) -> Result]) -> Result]) -> Result crate::err!("FileNotFound"), std::io::ErrorKind::PermissionDenied => crate::err!("AccessDenied"), @@ -5055,7 +5143,7 @@ pub fn spawn_sync_inherit(argv: &[impl AsRef<[u8]>]) -> Result` fields — no explicit impl needed. #[derive(Debug)] diff --git a/src/bun_core/wtf.rs b/src/bun_core/wtf.rs index 37ba8c832106..170a17d9989d 100644 --- a/src/bun_core/wtf.rs +++ b/src/bun_core/wtf.rs @@ -9,7 +9,7 @@ //! which forwards to `WTF::parseES5Date` in //! vendor/WebKit `Source/WTF/wtf/DateMath.{h,cpp}`. //! -//! PORT NOTE: WTF's `parseES5Date` sets an `isLocalTime` out-param so the JS +//! Note: WTF's `parseES5Date` sets an `isLocalTime` out-param so the JS //! `Date` constructor can later apply the VM's tz offset. The C shim discards //! it (matching `src/jsc/WTF.zig`), so local-time inputs return their naive //! UTC value here too. diff --git a/src/bundler/AstBuilder.rs b/src/bundler/AstBuilder.rs index e728bea17310..4406a9752da1 100644 --- a/src/bundler/AstBuilder.rs +++ b/src/bundler/AstBuilder.rs @@ -60,8 +60,10 @@ pub struct AstBuilder<'a, 'bump> { // // Zig used `comptime` zero-sized fields (`options`, `import_items_for_namespace`) // and a `parser_features` decl so `ImportScanner.scan` could duck-type over both -// the real parser and `AstBuilder`. In Rust this becomes a trait that both impl. -// TODO(port): define `ImportScannerHost` trait in `bun_js_parser` and impl it here. +// the real parser and `AstBuilder`. The Rust port instead open-codes the +// scanner transform in `to_bundled_ast` for the stmt shapes AstBuilder emits; +// if `ImportScanner` ever grows a host trait in `bun_js_parser`, these stubs +// are the surface it would formalize. impl<'a, 'bump> AstBuilder<'a, 'bump> { // stub for ImportScanner duck typing — Zig: `comptime import_items_for_namespace: struct { fn get(_, _) ?Map { return null; } }` pub fn import_items_for_namespace_get( @@ -123,7 +125,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { unsafe { &mut *self.current_scope } } - // PORT NOTE: Zig signature lacks `!` but body uses `try` — porting as fallible. + // Zig signature lacks `!` but body uses `try` — ported as fallible. pub fn push_scope(&mut self, kind: ScopeKind) -> Result<*mut Scope, OOM> { self.scopes.reserve(1); self.current_scope_mut().children.ensure_unused_capacity(1); @@ -199,7 +201,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { let non_unique = MutableString::ensure_valid_identifier(path_name.non_unique_name_string_base())?; let name = strings::append(b"import_", &non_unique); - // PORT NOTE: copy into the arena so the raw `*const [u8]` stored on the + // Copy into the arena so the raw `*const [u8]` stored on the // Symbol outlives this stack frame (Zig used the parser arena arena). let name: &[u8] = self.bump.alloc_slice_copy(&name); let namespace_ref = self.new_symbol(SymbolKind::Other, name)?; @@ -273,7 +275,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { Ok(ref_) } - // PORT NOTE: returns `BundledAst<'static>` (== `JSAst`) directly. The only + // Returns `BundledAst<'bump>` (== `JSAst`) directly. The only // `'arena`-carrying field, `url_for_css`, is always set to `b""` here, and // every other field stores arena data via raw pointers / `StoreSlice`, so // nothing borrows `&mut self` past this call. @@ -286,7 +288,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { let module_scope = self.current_scope; let mut parts = bun_ast::PartList::with_capacity_in(2, self.bump); - // PORT NOTE: Zig grew len then wrote `parts.mut(i).* = ...`, which is a + // Zig grew len then wrote `parts.mut(i).* = ...`, which is a // bitwise store on the SoA slot. In Rust `*parts.mut_(i) = ...` first // *drops* the (uninitialized) prior `Part` — and `Part` carries Drop // fields (`Vec`/`HashMap`), so that drop frees garbage and corrupts the @@ -323,7 +325,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { let module_scope_ref = unsafe { &*module_scope }; let generated_len = module_scope_ref.generated.len(); top_level_symbols_to_parts.ensure_total_capacity(generated_len)?; - // PORT NOTE: reshaped — Zig grew `entries` then wrote keys/values columns + // Reshaped — Zig grew `entries` then wrote keys/values columns // in lockstep + `reIndex`. Rust `ArrayHashMap` keeps keys/values in private // `Vec`s and rebuilds hashes on every `put_assume_capacity`, so a plain // pre-reserved insert loop is equivalent (and `re_index` is a no-op here). @@ -338,7 +340,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { // For more details on this section, look at js_parser.toAST // This is mimicking how it calls ImportScanner // - // PORT NOTE: Zig duck-typed `ImportScanner.scan(AstBuilder, ...)` and + // Zig duck-typed `ImportScanner.scan(AstBuilder, ...)` and // `ConvertESMExportsForHmr.{convertStmt,finalize}` over `AstBuilder` // via `anytype`. The Rust `ImportScanner` is currently monomorphized // over the concrete `P<'_, TS, J, SCAN>` parser only (see @@ -347,11 +349,11 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { // `S.Local{is_export}`, `S.ExportDefault(expr)`). Without this, the // generated server-component proxy keeps raw `export` keywords inside // the HMR function wrapper and JSC rejects the chunk with - // `SyntaxError: Unexpected keyword 'export'`. - // TODO(port): replace with a `ParserLike` trait so the real - // `ImportScanner`/`ConvertESMExportsForHmr` can accept `AstBuilder`. + // `SyntaxError: Unexpected keyword 'export'`. If `ImportScanner` / + // `ConvertESMExportsForHmr` ever grow a `ParserLike` host trait, this + // open-coded transform can be replaced by the real implementations. // - // PORT NOTE: Zig assigned `p.stmts.items` directly — its + // Zig assigned `p.stmts.items` directly — its // `ArrayListUnmanaged` storage is owned by `worker.allocator` and // outlives the `AstBuilder` stack value. Rust's `Vec` would // drop with `self`, leaving `parts[1].stmts` dangling once the @@ -521,7 +523,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { // `named_imports` / `import_records_for_current_part`; without it // the linker can't bind imports against this generated module // (e.g. `import { ssrManifest } from "bun:bake/server"` → - // "No matching export"). See PORT NOTE above re: monomorphization. + // "No matching export"). See the note above re: monomorphization. let in_stmts = core::mem::take(&mut self.stmts); for stmt in in_stmts.iter() { match stmt.data { @@ -640,7 +642,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { match binding.data { B::BMissing(_) => {} B::BIdentifier(ident) => { - // PORT NOTE: reshaped for borrowck — capture original_name before calling &mut self method + // Reshaped for borrowck — capture original_name before calling &mut self method let original_name = self.symbols[ident.r#ref.inner_index() as usize].original_name; self.record_export(binding.loc, original_name.slice(), ident.r#ref) .expect("unreachable"); diff --git a/src/bundler/BundleThread.rs b/src/bundler/BundleThread.rs index 5e4c689e7852..4147941cc341 100644 --- a/src/bundler/BundleThread.rs +++ b/src/bundler/BundleThread.rs @@ -14,13 +14,12 @@ pub(crate) extern "C" fn timer_callback(_: *mut bun_sys::windows::libuv::Timer) /// Port of `std.Thread.ResetEvent` — single-shot manual-reset event used to /// block `spawn()` until the bundle thread has initialized its `Waker`. -// PORT NOTE: re-export `bun_threading::ResetEvent` (futex-backed); the local -// `wait()`/`set()`/`Default` API is identical, and the futex impl preserves -// the "set-before-wait does not deadlock" property the parking_lot draft had. +// Re-exports `bun_threading::ResetEvent` (futex-backed); the futex impl +// preserves the "set-before-wait does not deadlock" property `spawn()` relies on. pub use bun_threading::ResetEvent; /// Result of a `Bun.build` invocation handed back to the JS thread. -// PORT NOTE: mirrors `BundleV2.JSBundleCompletionTask.Result` (bundle_v2.zig). +// Mirrors `BundleV2.JSBundleCompletionTask.Result` (bundle_v2.zig). // Defined here (not re-exported from `bundle_v2`) because the un-gated // `bundle_v2` module keeps the draft body private; T6 (`bundler_jsc`) consumes // this via the `CompletionStruct` trait. @@ -44,7 +43,7 @@ pub enum BundleV2Result { /// /// - `configureBundler` is used to configure `Bundler`. /// - `completeOnBundleThread` is used to tell the task that it is done. -// PORT NOTE: trait bound lives on the `impl` (not the struct) so the +// The trait bound lives on the `impl` (not the struct) so the // `singleton` static can name `BundleThread` before T6 // provides the `CompletionStruct` impl for the forward-decl. pub struct BundleThread { @@ -117,10 +116,8 @@ pub trait CompletionStruct: Node + Send + 'static { /// → on success `self.set_result(Value(..))` → `this.deinitWithoutFreeingArena()`; /// on error drain `this.linker.source_maps.*_wait_group` then deinit. /// - /// PORT NOTE: the `BundleV2` impl does not yet expose `init` / - /// `run_from_js_in_new_thread` (pending the linker pipeline). The Zig - /// `@compileError` already - /// proves this body is `JSBundleCompletionTask`-specific, so the + /// The Zig `@compileError` already proves this body is + /// `JSBundleCompletionTask`-specific, so the /// construction + run is delegated to the trait impl in T6, which has /// access to the concrete event-loop / work-pool wiring. The shared /// scaffolding (arena, AST arena push/pop, log copy, @@ -138,24 +135,16 @@ pub trait CompletionStruct: Node + Send + 'static { impl BundleThread { /// To initialize, put this somewhere in memory, and then call `spawn()` - // PORT NOTE: Zig `uninitialized` left `waker` as `undefined`. We can't use - // `mem::zeroed()` here — on macOS `Waker` holds a `Box<[u8]>` and on - // Windows a `&'static` loop, both of which have a NonNull validity - // invariant (zeroing them is *language-level* UB even if never read). + // Zig's `uninitialized` left `waker` as `undefined`. We can't use + // `mem::zeroed()` here — the platform `Waker`s hold NonNull-validity + // fields (a `Box<[u8]>` on macOS, a niche-optimised `Option` on + // Windows), so zeroing them is *language-level* UB even if never read. // `placeholder()` yields a fully-initialized inert value instead. // `ready_event.wait()` in `spawn()` blocks until `thread_main` overwrites // it via `ptr::write`, so the placeholder is never observed live. pub fn uninitialized() -> Self { Self { - #[cfg(unix)] waker: Async::Waker::placeholder(), - #[cfg(windows)] - // TODO(port,windows): `Waker { loop_: &'static _ }` is also - // NonNull; provide a `placeholder()` once the Windows event-loop - // port lands. Kept as-is here to avoid an untestable change. - // SAFETY: see TODO — this is technically invalid_value UB on - // Windows; the field is overwritten before any read. - waker: unsafe { bun_core::ffi::zeroed_unchecked() }, queue: UnboundedQueue::new(), generation: 0, ready_event: ResetEvent::default(), @@ -168,8 +157,8 @@ impl BundleThread { /// `*instance`; callers must only touch it via the raw-pointer methods on this /// impl (e.g. `enqueue`) and never materialize a `&mut Self`. pub unsafe fn spawn(instance: *mut Self) -> std::io::Result> { - // PORT NOTE: `std.Thread.spawn(.{}, threadMain, .{instance})` → - // `std::thread::Builder` so the spawn error is surfaced (Zig used `try`). + // `std::thread::Builder` (not `std::thread::spawn`) so the spawn error + // is surfaced to the caller. struct SendPtr(*mut T); // SAFETY: the pointer is only dereferenced on the bundle thread via raw // projections; `BundleThread` itself is never moved across threads. @@ -225,7 +214,7 @@ impl BundleThread { // SAFETY: raw-ptr field projection; spawning thread is blocked in `ready_event.wait()`. unsafe { (*instance).ready_event.set() }; - // PORT NOTE: libuv Timer lives on stack for the lifetime of this never-returning fn. + // The libuv Timer lives on stack for the lifetime of this never-returning fn. // It MUST be declared at function scope (not inside the `#[cfg(windows)] { ... }` // block below) because `timer.init()`/`timer.start()` register `&timer`'s address // into the uv loop's intrusive handle queue / timer min-heap, and `waker.wait()` @@ -290,8 +279,6 @@ impl BundleThread { completion: &mut C, generation: bun_core::Generation, ) -> Result<(), bun_core::Error> { - // PORT NOTE: `ThreadLocalArena.init()` → `bun_alloc::Arena::new()` (bumpalo - // bump arena; `defer heap.deinit()` is handled by Drop). let heap = Arena::new(); let bump = &heap; @@ -322,7 +309,7 @@ impl BundleThread { std::ptr::from_ref(bun_threading::work_pool::WorkPool::get()).cast_mut(), ); - // PORT NOTE: Zig's overlapping `defer { ast_memory_store.pop(); + // Zig's overlapping `defer { ast_memory_store.pop(); // this.deinitWithoutFreeingArena(); }` + `errdefer { wait_groups; copy log }` // captured ≥2 disjoint &mut borrows. Restructured as straight-line: log copy // runs on both paths; `completeOnBundleThread` only on success (the error @@ -375,7 +362,7 @@ impl BundleThread { /// Lazily-initialized singleton. This is used for `Bun.build` since the /// bundle thread may not be needed. -// PORT NOTE: Zig had a per-monomorphization `singleton` struct with +// Zig had a per-monomorphization `singleton` struct with // `static var instance`. Rust forbids generic statics, so the storage is // type-erased (`*mut ()`) and the accessor functions are generic over `C`. // The Zig source already `@compileError`s for any `CompletionStruct` other than diff --git a/src/bundler/Cargo.toml b/src/bundler/Cargo.toml index 8da369721f53..9c1828e5dfde 100644 --- a/src/bundler/Cargo.toml +++ b/src/bundler/Cargo.toml @@ -27,10 +27,7 @@ bumpalo.workspace = true typed-arena.workspace = true bun_base64.workspace = true bun_alloc.workspace = true -# TODO(b2-blocked): bun_analytics — adding the dep triggers a fresh build of -# upstream crates (css/logger/js_parser) with in-progress breakage. Re-add once -# the tree is green; usages in options.rs are gated or stubbed locally. -# bun_analytics.workspace = true +bun_analytics.workspace = true bun_resolver.workspace = true # `bun_resolve_builtins` only depends on `bun_options_types` + `bun_string` # (both already in this graph), so no cycle. diff --git a/src/bundler/Chunk.rs b/src/bundler/Chunk.rs index cb06d1ec6758..ae76f917e108 100644 --- a/src/bundler/Chunk.rs +++ b/src/bundler/Chunk.rs @@ -8,7 +8,7 @@ use bun_ast::{ImportKind, ImportRecord}; use bun_ast::{Ref, Stmt}; use bun_collections::{ArrayHashMap, AutoBitSet, VecExt}; use bun_core::{FeatureFlags, Output}; -// PORT NOTE: `bun.ast.Index` is mirrored as both `crate::Index` +// Note: `bun.ast.Index` is mirrored as both `crate::Index` // (`bun_ast::Index`) and `bun_ast::Index` via a // TYPE_ONLY split. `CssImportOrderKind::SourceIndex` carries the js_parser // flavor because its sole producer (`findImportedFilesInCSSOrder`) constructs @@ -36,9 +36,12 @@ pub struct ChunkImport { pub import_kind: ImportKind, } -// TODO(port): arena lifetime — string/slice fields below borrow from the bundler arena -// (no deinit in Zig). Currently uses &'static [u8] / Box<[T]> as placeholders; -// should thread a `'bump` lifetime or use arena slice newtypes. +// Lifetime note: string/slice fields below conceptually borrow from the +// bundler arena (Zig never frees them). The Rust port erases that borrow to +// `&'static [u8]` (the arena is owned by `BundleV2` and outlives every +// `Chunk`; see the lifetime-erasure note on `LinkerGraph::bump`) or owns a +// `Box<[T]>` instead of threading a `'bump` lifetime through the chunk +// pipeline. pub struct Chunk { /// This is a random string and is used to represent the output path of this /// chunk before the final output path has been computed. See OutputPiece @@ -56,7 +59,7 @@ pub struct Chunk { /// We must not keep pointers to this type until all chunks have been allocated. pub entry_bits: AutoBitSet, - /// PORT NOTE: Zig stored this as an arena-owned `[]const u8` (linker arena); + /// Note: Zig stored this as an arena-owned `[]const u8` (linker arena); /// the Rust `Chunk` owns it as a `Box<[u8]>` so dropping the chunk slice /// frees it (matches `c.arena().dupe(u8, ..)` ownership without leaking). pub final_rel_path: Box<[u8]>, @@ -75,17 +78,17 @@ pub struct Chunk { pub intermediate_output: IntermediateOutput, pub isolated_hash: u64, - // TODO(port): was `= undefined` in Zig (set before use). The Zig field is - // the `renamer.Renamer` union; the Rust enum borrows from the symbol table - // (`Renamer<'r,'src>`), which can't live in a 'static-ish struct yet. - // `ChunkRenamer` is an owned-erased placeholder (see `crate::bun_renamer`). + // Was `= undefined` in Zig (set before use). The Zig field is the + // `renamer.Renamer` union; the borrowed Rust enum (`Renamer<'r,'src>`) + // borrows from the symbol table and so can't live in this owning struct. + // `ChunkRenamer` is the owning equivalent (see `crate::bun_renamer`). pub renamer: bun_renamer::ChunkRenamer, pub compile_results_for_chunk: CompileResultSlots, /// Pre-built JSON fragment for this chunk's metafile output entry. /// Generated during parallel chunk generation, joined at the end. - /// PORT NOTE: owned `Box<[u8]>` (was arena-owned `[]const u8` in Zig). + /// Note: owned `Box<[u8]>` (was arena-owned `[]const u8` in Zig). pub metafile_chunk_json: Box<[u8]>, /// Pack boolean flags to reduce padding overhead. @@ -125,7 +128,7 @@ impl Default for Content { // `files_with_parts_in_chunk` values are bumped via atomic RMW (Zig // `@atomicRmw`); the renamer is fully populated before fan-out and treated as // read-only by the printer. -// TODO(ub-audit): `Renamer<'r>` still borrows `&'r mut {Number,Minify}Renamer`, +// Caveat: `Renamer<'r>` still borrows `&'r mut {Number,Minify}Renamer`, // so the per-chunk renamer is reborrowed mutably from each part-range task; // the printer never writes through it, but the borrow should become `&'r`. unsafe impl Send for Chunk {} @@ -134,7 +137,7 @@ unsafe impl Send for Chunk {} // `files_with_parts_in_chunk` atomic counters; the remaining fields are // frozen before fan-out and read single-threaded after the pool join — // **except** `renamer`, which the per-part-range printer reborrows `&mut` -// from each worker (read-only in practice). See `TODO(ub-audit)` above: +// from each worker (read-only in practice). See the renamer caveat above: // once `Renamer<'r>` borrows `&'r` instead of `&'r mut`, this caveat (and // the matching split-borrow in `generate_compile_result_for_js_chunk`) goes // away. Pre-existing; this impl mirrors `unsafe impl Send for Chunk` and @@ -182,6 +185,14 @@ impl CompileResultSlots { // SAFETY: reads happen only after the pool join; no concurrent writer. self.0.iter().map(|c| unsafe { &*c.get() }) } + + /// Post-join exclusive access to one slot (e.g. to transfer ownership of + /// the result out of the chunk). `&mut self` proves no concurrent writer, + /// so `UnsafeCell::get_mut` needs no unsafe here. + #[inline] + pub fn get_mut(&mut self, i: usize) -> &mut CompileResult { + self.0[i].get_mut() + } } impl core::ops::Index for CompileResultSlots { @@ -293,7 +304,7 @@ impl Chunk { // Look up the CSS chunk via the JS chunk's css_chunks indices. // This correctly handles deduplicated CSS chunks that are shared // across multiple HTML entry points (see issue #23668). - // PORT NOTE: reshaped for borrowck — Zig calls getJSChunkForHTML(chunks) and then + // Note: reshaped for borrowck — Zig calls getJSChunkForHTML(chunks) and then // indexes into the same `chunks`. Here we scan immutably for the JS chunk, copy the // css-chunk index into a local, drop the borrow, then re-borrow mutably. let entry_point_id = self.entry_point.entry_point_id(); @@ -385,7 +396,7 @@ pub enum IntermediateOutput { /// Owns the joined output buffer alongside the `OutputPiece` slices that /// point into it. /// -/// PORT NOTE: In Zig, `breakOutputIntoPieces` calls `j.done(alloc)` with the +/// Note: In Zig, `breakOutputIntoPieces` calls `j.done(alloc)` with the /// per-worker arena, so the joined buffer outlives the chunk by construction /// and `OutputPiece.data` stays valid. The Rust `StringJoiner::done()` /// returns a `Box<[u8]>`; if that box is dropped at the end of @@ -425,7 +436,7 @@ pub struct CodeResult { pub shifts: Vec, } -// PORT NOTE: Zig used `std.mem.Allocator`; the Rust crate exposes a global +// Note: Zig used `std.mem.Allocator`; the Rust crate exposes a global // mimalloc — we don't need a vtable here yet. `()` is kept as a token so the // caller's `Option<&DynAlloc>` plumbing matches the Zig signature; the actual // allocation goes through `alloc_buf` (global mimalloc) regardless. Real @@ -464,8 +475,9 @@ fn additional_output_file_index(f: &AdditionalFile) -> usize { impl IntermediateOutput { pub fn allocator_for_size(_size: usize) -> &'static DynAlloc { - // PERF(port): Zig picks page_allocator for large buffers vs mimalloc default. - // TODO(port): expose page_allocator / default_allocator as &'static dyn Allocator + // Zig picked page_allocator for large buffers vs the mimalloc default; + // mimalloc serves large allocations via mmap already, so the global + // allocator is a behavior match (see `alloc_buf`). &() } @@ -544,12 +556,12 @@ impl IntermediateOutput { parse_graph: &Graph, linker_graph: &LinkerGraph<'_>, import_prefix: &[u8], - // PORT NOTE: Zig passed `*Chunk` / `[]Chunk` (freely aliased — `chunk` + // Note: Zig passed `*Chunk` / `[]Chunk` (freely aliased — `chunk` // is `&chunks[i]`). The body only reads both, so take `&` to avoid // overlapping `&mut Chunk` + `&mut [Chunk]` UB at every call site. chunk: &Chunk, chunks: &[Chunk], - // PORT NOTE: `?*usize` in Zig — accept both `&mut usize` and + // Note: `?*usize` in Zig — accept both `&mut usize` and // `Option<&mut usize>` so call sites that ported either way compile. display_size: impl Into>, force_absolute_path: bool, @@ -595,10 +607,10 @@ impl IntermediateOutput { parse_graph: &Graph, linker_graph: &LinkerGraph<'_>, import_prefix: &[u8], - // See `code()` PORT NOTE — `chunk` aliases `chunks[i]`; body is read-only. + // See `code()` note — `chunk` aliases `chunks[i]`; body is read-only. chunk: &Chunk, chunks: &[Chunk], - // PORT NOTE: `?*usize` in Zig — accept both `&mut usize` and + // Note: `?*usize` in Zig — accept both `&mut usize` and // `Option<&mut usize>` so call sites that ported either way compile. display_size: impl Into>, force_absolute_path: bool, @@ -640,7 +652,7 @@ impl IntermediateOutput { graph: &Graph, linker_graph: &LinkerGraph<'_>, import_prefix: &[u8], - // See `code()` PORT NOTE — `chunk` aliases `chunks[i]`; body is read-only. + // See `code()` note — `chunk` aliases `chunks[i]`; body is read-only. chunk: &Chunk, chunks: &[Chunk], display_size: Option<&mut usize>, @@ -1026,7 +1038,6 @@ impl IntermediateOutput { let buffer = 'brk: { if ENABLE_SOURCE_MAP_SHIFTS && FeatureFlags::SOURCE_MAP_DEBUG_ID { // This comment must go before the //# sourceMappingURL comment - // TODO(port): graph.heap.arena() — arena arena from Graph let mut debug_id_fmt = Vec::new(); write!( &mut debug_id_fmt, @@ -1037,7 +1048,7 @@ impl IntermediateOutput { ) .ok(); - let _ = arena; // PORT NOTE: StringJoiner::done* allocates from global mimalloc; arena token is plumbing-only. + let _ = arena; // Note: StringJoiner::done* allocates from global mimalloc; arena token is plumbing-only. break 'brk joiner.done_with_end(&debug_id_fmt)?; } @@ -1072,7 +1083,7 @@ impl IntermediateOutput { /// An output piece is the concatenation of source code text and an output /// path, in that order. An array of pieces makes up an entire file. /// -/// PORT NOTE: Zig split ptr+u32 len to shave 8 bytes. The Rust port stores a +/// Note: Zig split ptr+u32 len to shave 8 bytes. The Rust port stores a /// `RawSlice` (encapsulates the unsafe re-borrow) — the per-chunk piece count /// is bounded by the number of unique-key boundaries, so the extra word per /// piece is negligible against the safety win. @@ -1279,7 +1290,10 @@ pub struct JavaScriptChunk { pub parts_in_chunk_in_order: Box<[PartRange]>, // for code splitting - // TODO(port): Zig uses ArrayHashMapUnmanaged(Ref, string, Ref.ArrayHashCtx, false) — custom hash ctx + // Zig uses ArrayHashMapUnmanaged(Ref, string, Ref.ArrayHashCtx, false); the + // Rust map hashes via `Ref`'s `Hash` impl, which matches that ctx. Values + // are `&'static`-erased slices into bundler-owned storage (see the + // lifetime note on `Chunk`). pub exports_to_other_chunks: ArrayHashMap, pub imports_from_other_chunks: ImportsFromOtherChunks, pub cross_chunk_prefix_stmts: Vec, @@ -1364,11 +1378,12 @@ pub enum CssImportOrderKind { SourceIndex(Index), } -// TODO(port): bun.ptr.Cow(Vec, { copy = deepCloneInfallible, deinit = clearAndFree }) -// LayerName payload allocations live in the arena, so the Zig deinit is a shallow clearAndFree. -// `std::borrow::Cow<'_, Vec<_>>` requires `Vec: Clone` (not implemented). Port the -// Zig `bun.ptr.Cow` shape directly: a tag + raw pointer for the borrowed arm. Should -// thread `'bump` (arena-borrowed) and confirm Clone semantics match deepCloneInfallible. +// Port of Zig `bun.ptr.Cow(Vec, { copy = deepCloneInfallible, deinit = clearAndFree })`. +// `std::borrow::Cow<'_, Vec<_>>` requires `Vec: Clone` (not implemented), so this +// mirrors the `bun.ptr.Cow` shape directly: a tag + raw pointer for the borrowed +// arm. LayerName payload allocations live in the arena, so the Zig deinit is a +// shallow clearAndFree, and `to_owned`'s element-wise clone matches +// deepCloneInfallible. pub enum Layers { /// Borrowed from another `CssImportOrder`'s `Layers` or the parsed stylesheet. Borrowed(bun_ptr::BackRef>), @@ -1391,7 +1406,7 @@ impl Layers { /// `crate::bun_css::LayerName` to the real `::bun_css::LayerName` via a /// raw-pointer cast — that nominal-type erasure cannot go through `&`. /// The pointee is arena-owned storage that outlives the chunk pipeline - /// (see TODO(port) above re: `'bump`); `BackRef` encapsulates that + /// (see the lifetime note on `Chunk`); `BackRef` encapsulates that /// invariant so `inner()`/`to_owned()` deref sites are safe. #[inline] pub(crate) fn borrow(p: core::ptr::NonNull>) -> Self { @@ -1446,7 +1461,7 @@ impl CssImportOrder { hasher.update(b"\x00"); } CssImportOrderKind::ExternalPath(path) => hasher.update(path.text), - // PORT NOTE: `Index` is a `#[repr(transparent)]` u32 newtype but + // Note: `Index` is a `#[repr(transparent)]` u32 newtype but // doesn't impl `AsBytes`; hash the inner u32 (Zig hashed the // `Index.Int` bytes directly). CssImportOrderKind::SourceIndex(idx) => { @@ -1467,7 +1482,7 @@ impl CssImportOrder { #[allow(dead_code)] pub(crate) struct CssImportOrderDebug<'a, 'ctx> { inner: &'a CssImportOrder, - // PORT NOTE: split lifetimes — `LinkerContext<'ctx>` is invariant over `'ctx`, + // Note: split lifetimes — `LinkerContext<'ctx>` is invariant over `'ctx`, // so coupling the borrow lifetime to the struct param (`&'a LinkerContext<'a>`) // forces every caller's `&CssImportOrder` and `&LinkerContext` to share one // region. The Display impl only reads `ctx.parse_graph` (a raw `*mut Graph`), diff --git a/src/bundler/DeferredBatchTask.rs b/src/bundler/DeferredBatchTask.rs index fb5197da6f29..73ae3dc499eb 100644 --- a/src/bundler/DeferredBatchTask.rs +++ b/src/bundler/DeferredBatchTask.rs @@ -24,7 +24,7 @@ pub struct DeferredBatchTask { impl DeferredBatchTask { pub fn init(&mut self) { - // PORT NOTE: kept as `&mut self` (not `-> Self`) — this struct is embedded + // Kept as `&mut self` (not `-> Self`) — this struct is embedded // by value in BundleV2 (recovered via container_of in `get_bundle_v2`), so // it is reset in place, never separately constructed. #[cfg(debug_assertions)] @@ -64,7 +64,7 @@ impl DeferredBatchTask { } pub fn run_on_js_thread(&mut self) { - // PORT NOTE: reshaped for borrowck — Zig's `defer this.deinit()` only resets + // Zig's `defer this.deinit()` only resets // the debug `running` flag; since nothing follows `drainDeferred`, ignoring // its error and resetting the flag afterwards is equivalent on both paths. { @@ -79,7 +79,7 @@ impl DeferredBatchTask { self.deinit(); } - // PORT NOTE: not `impl Drop` — this struct is an intrusive field of `BundleV2` + // Not `impl Drop` — this struct is an intrusive field of `BundleV2` // and `deinit` is a debug-flag reset, not resource teardown. fn deinit(&mut self) { #[cfg(debug_assertions)] diff --git a/src/bundler/Graph.rs b/src/bundler/Graph.rs index b2f2c3fd9ce1..4165d0fee22f 100644 --- a/src/bundler/Graph.rs +++ b/src/bundler/Graph.rs @@ -35,8 +35,8 @@ pub struct Graph<'a> { pub input_files: MultiArrayList, /// Every source index has an associated Ast /// When a parse is in progress / queued, it is `Ast.empty` - // PORT NOTE: BundledAst<'arena> borrows from self.heap (sibling-field self-ref); - // 'static here is a placeholder. TODO(refactor): thread the lifetime via raw ptr or Ouroboros. + // `JSAst<'a>` borrows from the arena behind `self.heap`; `'a` ties the AST + // entries to that arena's lifetime (sibling-field relationship). pub ast: MultiArrayList>, /// During the scan + parse phase, this value keeps a count of the remaining @@ -103,9 +103,9 @@ pub struct InputFile { pub secondary_path: AstVec, pub loader: options::Loader, pub side_effects: SideEffects, - // PORT NOTE: Zig stored `arena: std.mem.Allocator = bun.default_allocator` - // here so deinit could free `source`/`secondary_path` with the right alloc. - // In Rust the owned fields (Box/Vec) carry their arena; field dropped. + // No `arena` field (Zig kept one so deinit could free `source`/ + // `secondary_path` with the right allocator); in Rust the owned fields + // (Box/Vec) carry their allocator. pub additional_files: AstVec, pub unique_key_for_additional_file: Box<[u8], AstAlloc>, pub content_hash_for_additional_file: u64, diff --git a/src/bundler/HTMLScanner.rs b/src/bundler/HTMLScanner.rs index 7d8b17e1e54e..638b3ba26114 100644 --- a/src/bundler/HTMLScanner.rs +++ b/src/bundler/HTMLScanner.rs @@ -135,9 +135,9 @@ pub(crate) trait HTMLProcessorHandler { fn on_write_html(&mut self, bytes: &[u8]); fn on_html_parse_error(&mut self, message: &[u8]); - // Only required when VISIT_DOCUMENT_TAGS == true. - // TODO(port): split into a separate trait if const-generic specialization - // is unwieldy; Zig only references these inside `if (visit_document_tags)`. + // Only required when VISIT_DOCUMENT_TAGS == true; Zig only references + // these inside `if (visit_document_tags)`, so the defaults are never + // reached for handlers that don't visit document tags. fn on_body_tag(&mut self, _element: &mut lol::Element) -> bool { unreachable!() } @@ -454,7 +454,7 @@ impl let mut sink = Sink::(this_ptr); - // PORT NOTE: Zig `errdefer { ... this.onHTMLParseError(last_error) }` + // Note: Zig `errdefer { ... this.onHTMLParseError(last_error) }` // reshaped — fallible tail wrapped in an inner block so the side effect // runs on error without a scopeguard double-borrowing `this`. let res: Result<(), Error> = (|| { diff --git a/src/bundler/IndexStringMap.rs b/src/bundler/IndexStringMap.rs index 34bd7a3f6610..7dfb1a40ea65 100644 --- a/src/bundler/IndexStringMap.rs +++ b/src/bundler/IndexStringMap.rs @@ -8,9 +8,6 @@ pub struct IndexStringMap { map: ArrayHashMap>, } -// PORT NOTE: `deinit` only freed owned values + the map; with `Box<[u8]>` values and -// `ArrayHashMap`'s own Drop, no explicit `impl Drop` is needed. - impl IndexStringMap { pub fn get(&self, index: IndexInt) -> Option<&[u8]> { self.map.get(&index).map(|v| v.as_ref()) diff --git a/src/bundler/LinkerContext.rs b/src/bundler/LinkerContext.rs index dc5971ab2a57..d9abb7306e35 100644 --- a/src/bundler/LinkerContext.rs +++ b/src/bundler/LinkerContext.rs @@ -11,18 +11,17 @@ use bun_core::{MutableString, string_joiner::StringJoiner, strings}; use bun_sourcemap::{ self as SourceMap, DebugIDFormatter, LineOffsetTable, SourceMapPieces, SourceMapState, }; -// PORT NOTE: alias the *module* (not the `ThreadPool` struct) so +// Note: alias the *module* (not the `ThreadPool` struct) so // `ThreadPoolLib::Task` / `ThreadPoolLib::Batch` resolve as nested items. use bun_ast::{ImportKind, ImportRecord}; use bun_threading::{WaitGroup, thread_pool as ThreadPoolLib}; -// TODO(port): bake_types arrives from move-in (TYPE_ONLY → bundler) use crate::bake_types as bake; use crate::BundledAst as JSAst; use bun_ast::{ Binding, DeclaredSymbol, Dependency, ExportsKind, Expr, NamedImport, Part, Ref, Stmt, TlaCheck, }; -// PORT NOTE: `crate::Index` (= `bun_ast::Index`) — the +// Note: `crate::Index` (= `bun_ast::Index`) — the // bundler's source-index newtype. `bun_ast::Index` is layout-identical // but a distinct type; LinkerGraph/JSMeta/etc. are typed against the crate // re-export, so use that here. @@ -413,7 +412,7 @@ impl<'a> LinkerContext<'a> { /// Spec: `LinkerContext.zig:checkForMemoryCorruption`. /// - /// PORT NOTE: the Zig body calls `parse_graph.heap.helpCatchMemoryIssues()` + /// Note: the Zig body calls `parse_graph.heap.helpCatchMemoryIssues()` /// (a `MimallocArena` debug hook). `Graph.heap` is currently /// `bun_alloc::Arena = bumpalo::Bump`, which has no such hook, so this is a /// no-op until the arena type is swapped to the real `MimallocArena`. The @@ -422,8 +421,9 @@ impl<'a> LinkerContext<'a> { pub fn check_for_memory_corruption(&self) { // For this to work, you need mimalloc's debug build enabled. // make mimalloc-debug - // TODO(port): `unsafe { (*self.parse_graph).heap.help_catch_memory_issues() }` - // once `Graph.heap: MimallocArena`. + // Becomes `unsafe { (*self.parse_graph).heap.help_catch_memory_issues() }` + // if `Graph.heap` ever grows the `MimallocArena` debug hook (see the + // doc comment above). } } @@ -441,7 +441,7 @@ type DeclaredSymbolList = bun_ast::DeclaredSymbolList; impl<'a> LinkerContext<'a> { pub fn arena(&self) -> &Bump { - // TODO(port): bundler is an AST crate; LinkerGraph owns the arena + // LinkerGraph owns (a backref to) the bundle arena; see `LinkerGraph::arena`. self.graph.arena() } @@ -515,7 +515,7 @@ impl<'a> LinkerContext<'a> { // `*mut Log` (same value aliased into `linker.log` / `resolver.log`). self.log = transpiler.log; - // PORT NOTE: lifetime — `self.resolver` is `ParentRef>` + // Note: lifetime — `self.resolver` is `ParentRef>` // but `transpiler.resolver` is `Resolver<'_>` (anonymous `bundle` // lifetime); erase via a pointer cast (LIFETIMES.tsv: GRAPHBACKED — // resolver outlives the link step). Read-only — `from_raw` provenance @@ -527,7 +527,7 @@ impl<'a> LinkerContext<'a> { }); self.cycle_detector = Vec::new(); - // PORT NOTE: `reachable_files` is `Vec`; clone the + // Note: `reachable_files` is `Vec`; clone the // caller-owned slice into the linker arena. PERF(port): Zig pointed at // the slice in-place; revisit once Vec grows a borrowed-view ctor. self.graph.reachable_files = reachable.to_vec(); @@ -572,7 +572,7 @@ impl<'a> LinkerContext<'a> { } if self.options.output_format == Format::Cjs || self.options.output_format == Format::Iife { - // PORT NOTE: reshaped for borrowck — `Slice` is a value-type + // Note: reshaped for borrowck — `Slice` is a value-type // snapshot of column pointers (does not borrow `self.graph.ast`), // so `split_mut()` on the local can coexist with the // `self.graph.meta` borrow below. The slab does not reallocate for @@ -612,8 +612,9 @@ impl<'a> LinkerContext<'a> { debug_assert!(self.options.source_maps != SourceMapOption::None); self.source_maps.line_offset_wait_group = WaitGroup::init_with_count(reachable.len()); self.source_maps.quoted_contents_wait_group = WaitGroup::init_with_count(reachable.len()); - // TODO(port): arena alloc of task arrays - // PORT NOTE: `SourceMapDataTask` is not `Clone` (embeds an intrusive + // Zig arena-allocated the task arrays; boxed slices on the global + // allocator are equivalent here (freed when `source_maps` is replaced). + // Note: `SourceMapDataTask` is not `Clone` (embeds an intrusive // `ThreadPoolLib::Task` node); build via iterator instead of `vec![x;n]`. self.source_maps.line_offset_tasks = (0..reachable.len()) .map(|_| SourceMapDataTask::default()) @@ -624,7 +625,7 @@ impl<'a> LinkerContext<'a> { .collect::>() .into_boxed_slice(); - // PORT NOTE: erase `'a` → `'static` for the task backref. The tasks are + // Note: erase `'a` → `'static` for the task backref. The tasks are // joined before `self` is dropped (see `SourceMapData.*_wait_group`). // SAFETY: write provenance from `ptr::from_mut`; outlives every task. let ctx: Option>> = Some(unsafe { @@ -681,7 +682,7 @@ impl<'a> LinkerContext<'a> { // field of `BundleV2.linker` (= `*self`). The two are disjoint, and no // other `&`/`&mut` to `BundleV2.graph` is live for this scope — // `self.graph` below is `LinkerGraph`, a distinct allocation. - // PORT NOTE: reshaped for borrowck — Zig held overlapping `&`/`&mut` + // Note: reshaped for borrowck — Zig held overlapping `&`/`&mut` // into `parse_graph.html_imports` and `parse_graph.input_files`; here // we go through raw pointers and reborrow per use. let parse_graph: *mut Graph<'a> = self.parse_graph; @@ -915,7 +916,7 @@ impl<'a> LinkerContext<'a> { self.graph.parts_live = parts_live; } - // PORT NOTE: reshaped for borrowck — these slices alias into self.graph; + // Note: reshaped for borrowck — these slices alias into self.graph; // Zig held them simultaneously. The SoA columns are physically disjoint // and the underlying slabs don't reallocate during tree-shaking, so we // cache raw column base pointers and reborrow at each recursive call. @@ -1031,7 +1032,7 @@ impl<'a> LinkerContext<'a> { let worker = crate::thread_pool::Worker::get(ctx.bundle()); let mut worker = scopeguard::guard(worker, |w| w.unget()); let worker: &mut crate::thread_pool::Worker = &mut **worker; - // PORT NOTE: dispatch on a discriminant copy so `chunk` isn't borrowed + // Note: dispatch on a discriminant copy so `chunk` isn't borrowed // across the post-process call (which takes `&mut Chunk`). let result = match chunk.content { crate::chunk::Content::Javascript(_) => { @@ -1073,7 +1074,7 @@ impl<'a> LinkerContext<'a> { chunk_index: usize, ) { let _ = chunk_index; - // PORT NOTE: reshaped for borrowck — `rename_symbols_in_chunk` needs + // Note: reshaped for borrowck — `rename_symbols_in_chunk` needs // `&mut Chunk` and a borrow of `chunk.content.javascript.files_in_chunk_order` // simultaneously; cache the files slice via raw pointer (it lives in // the chunk arena, address-stable for the renamer pass). @@ -1129,7 +1130,7 @@ impl<'a> LinkerContext<'a> { let path = &sources[index as usize].path; source_id_map.put_no_clobber(index, 0)?; - // PORT NOTE: Zig mutated a local copy's `path.pretty` from the + // Note: Zig mutated a local copy's `path.pretty` from the // worker arena; we keep the relative path in a local owned // buffer instead (drops at scope exit — same lifetime as the // arena slice). @@ -1247,7 +1248,8 @@ impl<'a> LinkerContext<'a> { if FeatureFlags::SOURCE_MAP_DEBUG_ID { j.push_static(b"\",\n \"debugId\": \""); - // TODO(port): allocPrint into arena — using Vec + write! + // Zig used `allocPrint` into the arena; a tiny owned buffer the + // joiner frees is equivalent (one 33-byte debug-id per chunk map). let mut buf = Vec::::new(); use std::io::Write; write!(&mut buf, "{}", DebugIDFormatter { id: isolated_hash }) @@ -1298,9 +1300,18 @@ pub enum LinkError { bun_core::oom_from_alloc!(LinkError); impl From for LinkError { - fn from(_: BunError) -> Self { - // TODO(port): narrow error set — Zig's `try this.load()` is `!void` (anyerror) - LinkError::BuildFailed + fn from(e: BunError) -> Self { + // Mirror Zig's `LinkError = OOM || error{BuildFailed, ImportResolutionFailed}` + // (LinkerContext.zig): `error.OutOfMemory` keeps its identity through + // `try this.load()`, so OOMs travelling as `bun_core::Error` must not be + // misreported as build failures. Everything else collapses to + // `BuildFailed`; user-facing diagnostics flow through the bundler `Log`, + // not this variant. + if e == BunError::OUT_OF_MEMORY { + LinkError::OutOfMemory + } else { + LinkError::BuildFailed + } } } bun_core::named_error_set!(LinkError); @@ -1474,7 +1485,7 @@ impl SourceMapDataTask { // stack traces can show the source code, even after incremental // rebuilds occur. // - // PORT NOTE: Zig branched on `worker.ctx.transpiler.options.dev_server` + // Note: Zig branched on `worker.ctx.transpiler.options.dev_server` // to pick `dev.arena()` vs `worker.arena`, but // `computeQuotedSourceContents` discards the arena parameter // (`_: std.mem.Allocator`) — it always allocates via @@ -1486,8 +1497,6 @@ impl SourceMapDataTask { } } -// TODO(port): see SourceMapDataTask above. - impl SourceMapData { /// Runs concurrently across the worker pool (one task per `source_index`). /// Takes [`ParentRef`](bun_ptr::ParentRef) (not `&mut`) @@ -1642,7 +1651,7 @@ pub struct ChunkMeta { pub(crate) type ChunkMetaMap = ArrayHashMap; -/// PORT NOTE: raw-pointer fields (was `&'a mut`) because `each_ptr` requires +/// Note: raw-pointer fields (was `&'a mut`) because `each_ptr` requires /// `Ctx: Sync + Copy` and the same context is observed from every worker /// thread. Each task only writes to its own `*mut Chunk` slot; reads of /// `c`/`chunks` are disjoint or read-only per the Zig spec. @@ -1662,7 +1671,7 @@ pub struct GenerateChunkCtx<'a> { /// [`bun_ptr::BackRef::as_ptr`], shared reads go through safe `Deref`. pub chunk: bun_ptr::BackRef, } -// SAFETY: see PORT NOTE above — mirrors Zig's freely-aliased `*LinkerContext`. +// SAFETY: see note above — mirrors Zig's freely-aliased `*LinkerContext`. unsafe impl<'a> Send for GenerateChunkCtx<'a> {} // SAFETY: see the `Send` impl above — same backref-lifetime / disjoint-write invariants. unsafe impl<'a> Sync for GenerateChunkCtx<'a> {} @@ -1796,7 +1805,7 @@ impl<'a> LinkerContext<'a> { .path_with_pretty_initialized(&source.path, arena) .expect("OOM"); } - // PORT NOTE: `Path::assert_pretty_is_valid` lives on the + // Note: `Path::assert_pretty_is_valid` lives on the // resolver-side `Path<'a>`; the logger `Path` has no // such debug hook yet. debug_assert!(source.path.text.as_ptr() != source.path.pretty.as_ptr()); @@ -1902,7 +1911,7 @@ impl<'a> LinkerContext<'a> { meta_flags: &mut [crate::js_meta::Flags], ast_import_records: &[bun_ast::import_record::List<'a>], ) -> Result { - // PORT NOTE: reshaped for borrowck — Zig held &mut tla_checks[source_index] across recursive + // Note: reshaped for borrowck — Zig held &mut tla_checks[source_index] across recursive // calls that also mutate tla_checks. We re-index after each recursion. if tla_checks[source_index as usize].depth == 0 { tla_checks[source_index as usize].depth = 1; @@ -2207,15 +2216,12 @@ impl<'a> LinkerContext<'a> { // across `RequireOrImportMetaCallback::init(self)` (`&mut self`) below. let parse_graph = unsafe { &*self.parse_graph }; - // PORT NOTE: `Options.arena` / `source_map_allocator` were removed in + // Note: `Options.arena` / `source_map_allocator` were removed in // the Rust port (printer uses global mimalloc + the explicit `bump` - // argument to `print_with_writer`). The dev-server source-map-arena - // selection is folded into TODO(port) until arena threading lands. - let _ = self.dev_server.is_some() - && parse_graph.input_files.items_loader()[source_index.get() as usize] - .is_javascript_like(); + // argument to `print_with_writer`), so Zig's dev-server-specific + // source-map-allocator selection has no Rust equivalent. - // PORT NOTE: reshaped for borrowck — `Options` borrows `ts_enums` / + // Note: reshaped for borrowck — `Options` borrows `ts_enums` / // `line_offset_tables` / `mangled_props` from `self.graph`, but the // `require_or_import_meta_for_source_callback` field below needs // `&mut self`. Detach the read-only borrows via raw-pointer round-trip @@ -2288,7 +2294,7 @@ impl<'a> LinkerContext<'a> { }; writer.buffer.reset(); - // PORT NOTE: Zig moved `*writer` into the printer by value and wrote it + // Note: Zig moved `*writer` into the printer by value and wrote it // back via `defer writer.* = printer.ctx;`. `BufferWriter` isn't // `Clone`/`Default` in Rust; move it through `mem::replace` with a // freshly-initialized writer instead. @@ -2297,7 +2303,7 @@ impl<'a> LinkerContext<'a> { js_printer::BufferWriter::init(), )); - // PORT NOTE: Zig's `ast.toAST()` bitwise-copies every field of + // Note: Zig's `ast.toAST()` bitwise-copies every field of // `*const BundledAst` into a stack `Ast` that is never deinit'd. The // Rust collections aren't `Copy`, so mirror the Zig shallow copy via // `ptr::read` + `ManuallyDrop` (the resulting `Ast` aliases `ast`'s @@ -2306,7 +2312,7 @@ impl<'a> LinkerContext<'a> { // the read is a bitwise copy whose result is never dropped. let printer_ast = core::mem::ManuallyDrop::new(unsafe { core::ptr::read(ast) }.to_ast()); - // PORT NOTE: `print_with_writer<'a>` requires `Renamer<'a,'a>` (the + // Note: `print_with_writer<'a>` requires `Renamer<'a,'a>` (the // printer struct stores it with a single lifetime), but `Renamer`'s // `'src` is invariant behind `&mut`, so the caller's `Renamer<'r,'src>` // cannot unify with the local `'a` picked from `alloc`/`mangled_props`. @@ -2402,7 +2408,7 @@ impl<'a> LinkerContext<'a> { let mut symbol = symbol_; if symbol.kind == bun_ast::symbol::Kind::LocalCss { let r#ref = 'follow: { - // PORT NOTE: Zig set `.tag = .symbol` after `init`; + // Note: Zig set `.tag = .symbol` after `init`; // `Ref` is packed in Rust — construct via `new`. let mut r#ref = Ref::new( u32::try_from(inner_index).expect("int cast"), @@ -2446,7 +2452,8 @@ impl<'a> LinkerContext<'a> { bstr::BStr::new(path_hash) ) .expect("infallible: in-memory write"); - // TODO(port): arena() is arena; mangled_props key/value lifetime + // Zig dupes the name into the arena; the Rust map owns + // its boxed values instead (freed with `mangled_props`). self.mangled_props .put(r#ref, final_generated_name.into_boxed_slice()) .expect("OOM"); @@ -2473,7 +2480,7 @@ impl<'a> LinkerContext<'a> { chunk_visit_map.set(index as usize); // Visit the other chunks that this chunk imports before visiting this chunk - // PORT NOTE: reshaped for borrowck — collect imports first to avoid aliasing &chunks[index] with recursive &mut chunks + // Note: reshaped for borrowck — collect imports first to avoid aliasing &chunks[index] with recursive &mut chunks let cross_chunk_imports: Vec = chunks[index as usize] .cross_chunk_imports .slice() @@ -2493,7 +2500,7 @@ impl<'a> LinkerContext<'a> { // express cross-chunk dependencies via `cross_chunk_imports` above, but // HTML (and CSS) chunks only reference other chunks through pieces, so // recurse on those too. - // PORT NOTE: reshaped for borrowck — collect piece queries first so the + // Note: reshaped for borrowck — collect piece queries first so the // `&chunks[index]` borrow is dropped before the recursive `&mut chunks` // calls in the Chunk/Scb arms below. `final_rel_path` is re-indexed per // Asset arm (not hoisted) because it is now `Box<[u8]>` (not `Copy`). @@ -2563,7 +2570,7 @@ impl<'a> LinkerContext<'a> { // Mix in the hash for this chunk let chunk = &chunks[index as usize]; - // PORT NOTE: Zig `std.mem.asBytes(&u64)` → native-endian byte view. + // Note: Zig `std.mem.asBytes(&u64)` → native-endian byte view. hash.write(&chunk.isolated_hash.to_ne_bytes()); } @@ -2575,7 +2582,7 @@ impl<'a> LinkerContext<'a> { ) { list.clear(); list.reserve(export_refs.count()); - // PORT NOTE: Zig set .items.len = count() then indexed; Rust pushes + // Note: Zig set .items.len = count() then indexed; Rust pushes for &export_ref in export_refs.keys() { #[cfg(debug_assertions)] { @@ -2771,7 +2778,7 @@ impl<'a> LinkerContext<'a> { .path .pretty ), - // PORT NOTE: Zig printed `target.bakeGraph()` (a `bake.Graph` tag); + // Note: Zig printed `target.bakeGraph()` (a `bake.Graph` tag); // `bake_graph()` lives in `bun_bake` (tier-6 — would back-edge). // The debug log only needs a stable label, so print the `Target` // tag directly via its `IntoStaticStr` derive. @@ -2824,7 +2831,7 @@ impl<'a> LinkerContext<'a> { let part_count = ctx.parts[source_index as usize].len(); for part_index in 0..part_count { - // PORT NOTE: reshaped for borrowck — re-borrow part each iteration since recursion mutates `parts` + // Note: reshaped for borrowck — re-borrow part each iteration since recursion mutates `parts` let part = &ctx.parts[source_index as usize].as_slice()[part_index]; let mut can_be_removed_if_unused = part.can_be_removed_if_unused; @@ -3037,12 +3044,12 @@ impl<'a> LinkerContext<'a> { /// Spec: `LinkerContext.zig:489 source_`. /// - /// PORT NOTE: returns `'static` so callers can hold the source across a + /// Note: returns `'static` so callers can hold the source across a /// `&mut self.log` borrow; the underlying `parse_graph.input_files` slab /// is append-only and outlives the link step (LIFETIMES.tsv: GRAPHBACKED). #[inline] pub fn get_source>(&self, index: I) -> &'static Source { - // PORT NOTE: Zig spec is `index: anytype`; callers pass both `u32` and + // Note: Zig spec is `index: anytype`; callers pass both `u32` and // `usize`. Route through `TryInto` so the SoA index works for // either width without forcing `as`-casts at every call site. let index: usize = match index.try_into() { @@ -3126,7 +3133,7 @@ impl<'a> LinkerContext<'a> { &mut self, wrap: WrapKind, wrapper_ref: Ref, - // PORT NOTE: `crate::Index` (`bun_ast::Index`), + // Note: `crate::Index` (`bun_ast::Index`), // not `bun_ast::Index` — the SoA `wrapper_part_index` column is // typed via the crate-root re-export. wrapper_part_index: &mut crate::Index, @@ -3151,7 +3158,7 @@ impl<'a> LinkerContext<'a> { let common_js_parts = self.top_level_symbols_to_parts_for_runtime(self.cjs_runtime_ref); - // PORT NOTE: reshaped for borrowck — Zig held `runtime_parts` + // Note: reshaped for borrowck — Zig held `runtime_parts` // simultaneously with the mutable graph borrows below; the inner // loop is empty (`if r#ref.eql(...) continue;` only) so it's a // no-op kept for parity with the original. @@ -3353,7 +3360,7 @@ impl<'a> LinkerContext<'a> { /// Spec: `LinkerContext.zig:1710 advanceImportTracker`. pub fn advance_import_tracker(&mut self, tracker: &ImportTracker) -> ImportTrackerIterator { let id = tracker.source_index.get(); - // PORT NOTE: reshaped for borrowck — Zig held `&mut named_imports[id]` + // Note: reshaped for borrowck — Zig held `&mut named_imports[id]` // and `&import_records[id]` simultaneously; here we read `named_import` // out first, then borrow the rest. let named_import: &NamedImport = @@ -3536,7 +3543,7 @@ impl<'a> LinkerContext<'a> { re_exports: &mut bun_alloc::AstVec, ) -> MatchImport { let cycle_detector_top = self.cycle_detector.len(); - // PORT NOTE: Zig's `defer cycle_detector.shrinkRetainingCapacity` is + // Note: Zig's `defer cycle_detector.shrinkRetainingCapacity` is // lowered to an explicit `truncate` after the `'loop_` below — the only // exits are the three `return`s that follow it, so a single post-loop // truncate covers every path. A scopeguard holding a raw `*mut` into @@ -3900,7 +3907,7 @@ impl<'a> LinkerContext<'a> { imports_to_bind: &mut crate::RefImportData, source_index: crate::IndexInt, ) { - // PORT NOTE: Zig clones into a local, sorts, iterates, then writes back. + // Note: Zig clones into a local, sorts, iterates, then writes back. // `ArrayHashMap` has no in-place key sort and `NamedImport` is non-Clone // (owns a `Vec`), so we sort an index vector over the live // keys/values instead — same observable iteration order (ascending @@ -4154,10 +4161,10 @@ impl<'a> LinkerContext<'a> { return Ok(crate::chunk::IntermediateOutput::Joiner(core::mem::take(j))); } - // PORT NOTE: Zig had `errdefer j.deinit()` around the initCapacity — Drop handles it. + // Note: Zig had `errdefer j.deinit()` around the initCapacity — Drop handles it. let mut pieces: Vec = Vec::with_capacity(count as usize); // errdefer pieces.deinit() — Drop handles it - // PORT NOTE: Zig used `j.done(alloc)` (worker arena), so the joined + // Note: Zig used `j.done(alloc)` (worker arena), so the joined // buffer outlived this function. The Rust `StringJoiner::done()` // returns a `Box<[u8]>`; we must keep it alive alongside the pieces // (each `OutputPiece` stores a raw `*const u8` into it). It is moved @@ -4233,7 +4240,7 @@ impl<'a> LinkerContext<'a> { _ => unreachable!(), } - // PORT NOTE: `Query` is a packed `u32` (`index: u29`, `kind: u3`); + // Note: `Query` is a packed `u32` (`index: u29`, `kind: u3`); // construct via `new` rather than field-init. pieces.push(OutputPiece::init( &output[0..boundary], @@ -4253,7 +4260,7 @@ impl<'a> LinkerContext<'a> { // PartialEq for MatchImport (needed for std.meta.eql in match_import_with_export) impl PartialEq for MatchImport { fn eq(&self, other: &Self) -> bool { - // PORT NOTE: Zig `std.meta.eql` on a slice compares ptr+len, not contents — + // Note: Zig `std.meta.eql` on a slice compares ptr+len, not contents — // compare the raw fat pointer (address + length metadata). std::ptr::eq(self.alias.as_raw(), other.alias.as_raw()) && self.kind == other.kind @@ -4271,8 +4278,9 @@ impl PartialEq for MatchImport { // ────────────────────────────────────────────────────────────────────────── pub struct StmtList { - // TODO(port): arena field dropped — Vec uses global mimalloc; bundler is AST crate but - // these are temporary scratch buffers, not arena-backed in the original (uses generic arena param) + // Zig threaded a generic arena param; these are temporary scratch buffers, + // so plain `Vec`s on the global allocator are equivalent (cleared/reused + // per chunk, freed by Drop). pub inside_wrapper_prefix: InsideWrapperPrefix, pub outside_wrapper_prefix: Vec, pub inside_wrapper_suffix: Vec, @@ -4304,9 +4312,6 @@ impl InsideWrapperPrefix { } } -// TODO(port): `Expr`/`Stmt` builder helpers (`E::Call`, `S::SExpr` etc.) -// — bun_js_parser AST builder surface not yet stable. - impl InsideWrapperPrefix { pub(crate) fn append_non_dependency(&mut self, stmt: Stmt) -> Result<(), AllocError> { self.stmts.push(stmt); @@ -4353,7 +4358,7 @@ impl InsideWrapperPrefix { return Ok(()); } - // PORT NOTE: deep AST mutation chain — `s_expr_mut`/`e_await_mut`/ + // Note: deep AST mutation chain — `s_expr_mut`/`e_await_mut`/ // `e_call_mut`/`e_array_mut` return `Option`; `.unwrap()` mirrors Zig's // untagged-union field reads (panic on shape mismatch). let mut first_dep_call_expr = self.stmts[self.sync_dependencies_end] diff --git a/src/bundler/LinkerGraph.rs b/src/bundler/LinkerGraph.rs index 6ab2d8502cd4..a18f4e684dd8 100644 --- a/src/bundler/LinkerGraph.rs +++ b/src/bundler/LinkerGraph.rs @@ -12,10 +12,9 @@ use bun_core::RawSlice; use crate::IndexStringMap::IndexStringMap; use crate::{ImportTracker, Index, JSAst, Part, Ref, UseDirective, import_record, index, part}; // `items_()` column accessors — bring the `*ListExt` traits into scope. -// PORT NOTE: `BundledAstColumns` is emitted by `` -// on `BundledAst`; un-gating here is paired with that derive landing in -// `crate::bundled_ast` (same dependency `scanImportsAndExports.rs` -// already imports as `BundledAstField`). +// Note: `BundledAstColumns` is emitted by `bun_collections::multi_array_columns!` +// on `BundledAst` in `crate::bundled_ast` (the same macro output +// `scanImportsAndExports.rs` already imports as `BundledAstField`). bun_core::declare_scope!(LinkerGraph, visible); pub mod entry_point { @@ -200,12 +199,12 @@ pub struct LinkerGraph<'a> { pub entry_points: entry_point::List, pub symbols: symbol::Map, - // PORT NOTE: lifetime-erased. Zig stores `std.mem.Allocator`; the Rust + // Note: lifetime-erased. Zig stores `std.mem.Allocator`; the Rust // arena is owned by `BundleV2` and outlives every `LinkerGraph` — kept as // a raw pointer (matching `LinkerContext.parse_graph: *mut Graph`) so the // struct stays `'static`-ish and `LinkerContext`/`Chunk` callers don't - // grow a `'bump` parameter yet. TODO(refactor): thread `'bump` once `Chunk` - // and `html_import_manifest` gain lifetimes. + // grow a `'bump` parameter; threading `'bump` would require `Chunk` and + // `html_import_manifest` to gain lifetimes first. pub bump: bun_ptr::BackRef, pub code_splitting: bool, @@ -277,7 +276,6 @@ impl<'a> LinkerGraph<'a> { impl<'a> LinkerGraph<'a> { pub fn init(bump: &Arena, file_count: usize) -> Result { - // TODO(port): narrow error set Ok(LinkerGraph { files: FileList::default(), files_live: BitSet::init_empty(file_count)?, @@ -304,7 +302,7 @@ impl Default for LinkerGraph<'_> { parts_live: Vec::new(), entry_points: entry_point::List::default(), symbols: symbol::Map::default(), - // PORT NOTE: `bump` is a backref assigned in `init`/`LinkerContext::load`; + // Note: `bump` is a backref assigned in `init`/`LinkerContext::load`; // dangling sentinel mirrors Zig's `undefined` (never read before assignment). bump: bun_ptr::BackRef::from(core::ptr::NonNull::dangling()), code_splitting: false, @@ -345,7 +343,7 @@ pub fn generate_new_symbol( ) -> Ref { let source_symbols = &mut symbols.symbols_for_source.slice_mut()[source_index as usize]; - // PORT NOTE: Zig built `Ref.init(..)` then assigned `ref.tag = .symbol`. + // Note: Zig built `Ref.init(..)` then assigned `ref.tag = .symbol`. // The Rust `Ref` is a packed `u64` with no public `tag` field, so use // the `Ref::new` constructor that takes the tag explicitly. let ref_ = Ref::new( @@ -357,7 +355,7 @@ pub fn generate_new_symbol( // TODO: will this crash on resize due to using threadlocal mimalloc heap? source_symbols.push(Symbol { kind, - // PORT NOTE: `Symbol.original_name` is a `StoreStr` — + // Note: `Symbol.original_name` is a `StoreStr` — // arena-owned slice whose lifetime is erased (matches the Zig // `[]const u8`); caller guarantees it outlives the symbol table. original_name: bun_ast::StoreStr::new(original_name), @@ -393,7 +391,7 @@ pub(crate) fn add_part_to_file( let part_id = parts[id as usize].len() as u32; // @truncate (u32) parts[id as usize].push(part); - // PORT NOTE: borrowck reshape. The Zig closure simultaneously holds + // Note: borrowck reshape. The Zig closure simultaneously holds // * `&mut parts[part_id].declared_symbols` (column `parts` of `ast`) // * `&meta.top_level_symbol_to_parts_overlay[id]` (`meta`) // * `&ast.top_level_symbols_to_parts[id]` (another `ast` column) @@ -447,7 +445,7 @@ pub fn generate_symbol_import_and_use( part_index: u32, ref_: Ref, use_count: u32, - // PORT NOTE: callers are split between `crate::Index` (options_types) + // Note: callers are split between `crate::Index` (options_types) // and the structurally identical `bun_ast::Index` until the two newtypes // unify. Accept either via `Into` and normalize once. source_index_to_import_from: impl Into, @@ -514,7 +512,7 @@ pub fn generate_symbol_import_and_use( debug_assert_eq!(part_ids.len(), new_dependencies.len()); for (part_id, dependency) in part_ids.iter().zip(new_dependencies.iter_mut()) { *dependency = Dependency { - // PORT NOTE: `Dependency.source_index` is the structurally + // Note: `Dependency.source_index` is the structurally // identical `bun_ast::Index`; convert by value until the // two `Index` newtypes unify. source_index: bun_ast::Index::init(source_index_to_import_from.get()), @@ -663,7 +661,6 @@ impl<'a> LinkerGraph<'a> { dynamic_import_entry_points: &[index::Int], entry_point_original_names: &IndexStringMap, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let scb = server_component_boundaries.slice(); self.files.set_capacity(sources.len())?; self.files.zero(); @@ -671,7 +668,7 @@ impl<'a> LinkerGraph<'a> { // SAFETY: capacity reserved above; columns zeroed by `zero()`. unsafe { self.files.set_len(sources.len()) }; - // PORT NOTE: `Slice` caches raw column pointers and does not borrow + // Note: `Slice` caches raw column pointers and does not borrow // `self.files`, so the `split_mut()` borrows (tied to the local // `files_slice`) can stay live across other `&mut self.*` accesses // below. The columns are not reallocated during `load`. @@ -690,7 +687,7 @@ impl<'a> LinkerGraph<'a> { // SAFETY: capacity reserved; columns initialized below. unsafe { self.entry_points.set_len(entry_points.len()) }; - // PORT NOTE: borrowck reshape — Zig held `source_indices` / + // Note: borrowck reshape — Zig held `source_indices` / // `path_strings` / `output_path_was_auto_generated` simultaneously // (disjoint columns of the same `MultiArrayList`). `split_mut()` // hands out all three at once; `self.entry_points` is not @@ -746,7 +743,7 @@ impl<'a> LinkerGraph<'a> { let import_records_len = self.ast.items_import_records().len(); self.meta.set_capacity(import_records_len)?; - // PORT NOTE: Zig does `meta.len = ast.len; meta.zero()` — a raw + // Note: Zig does `meta.len = ast.len; meta.zero()` — a raw // memset(0) is the valid empty state for Zig's unmanaged // containers. Rust `Vec`/`Box` require a non-null dangling // pointer when empty, so zeroed bytes violate their invariants @@ -823,7 +820,7 @@ impl<'a> LinkerGraph<'a> { // Setup files { // set it to max value so that if we access an invalid one, it crashes - // PORT NOTE: Zig used `@memset(sliceAsBytes(...), 255)` to fill raw + // Note: Zig used `@memset(sliceAsBytes(...), 255)` to fill raw // bytes; here we fill with `Index::INVALID` whose bytes are all // 0xFF (`#[repr(transparent)]` over `u32::MAX`). let stable_source_indices = self @@ -842,7 +839,7 @@ impl<'a> LinkerGraph<'a> { } { - // PORT NOTE: Zig built a borrowed `Symbol.NestedList` over the + // Note: Zig built a borrowed `Symbol.NestedList` over the // `ast.items(.symbols)` column then `clone`d it (memcpy). The Rust // `Vec::clone` requires `T: Clone` which `Symbol` does not // derive (it carries a raw `*const [u8]`), so spell out the @@ -895,7 +892,7 @@ impl<'a> LinkerGraph<'a> { debug_assert_eq!(ts_enums.keys().len(), ts_enums.values().len()); for (key, value) in ts_enums.keys().iter().zip(ts_enums.values().iter()) { // PERF(port): was assume_capacity_no_clobber - // PORT NOTE: Zig copied the inner `StringHashMap` by + // Note: Zig copied the inner `StringHashMap` by // value (shallow struct copy). Rust clones the backing // `HashMap`; the per-file maps are not mutated after // this point so aliasing is not required. @@ -962,7 +959,6 @@ impl<'a> LinkerGraph<'a> { } pub fn propagate_async_dependencies(&mut self) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set struct State<'a> { visited: AutoBitSet, import_records: &'a [import_record::List<'a>], @@ -1076,7 +1072,7 @@ impl File { impl Default for File { fn default() -> Self { Self { - // PORT NOTE: Zig had `entry_bits: AutoBitSet = undefined` — using an + // Note: Zig had `entry_bits: AutoBitSet = undefined` — using an // empty static-arm bitset here; load() overwrites before any read. entry_bits: AutoBitSet::init_empty(0).expect("static AutoBitSet"), input_file: Index::source(0u32), diff --git a/src/bundler/OutputFile.rs b/src/bundler/OutputFile.rs index c3a1d8fefb06..d269b83396d7 100644 --- a/src/bundler/OutputFile.rs +++ b/src/bundler/OutputFile.rs @@ -127,8 +127,8 @@ pub type IndexOptional = bun_core::GenericIndexOptional; // We may use a different system call #[derive(Clone)] pub struct FileOperation { - // TODO(port): lifetime — Zig never frees `pathname`; may be borrowed from - // `Options.output_path`. Using owned `Box<[u8]>` for now. + // Zig never frees `pathname` (it may alias `Options.output_path`); the + // Rust port owns a copy so the field has a single, obvious lifetime. pub pathname: Box<[u8]>, pub fd: Fd, pub dir: Fd, @@ -163,7 +163,7 @@ impl FileOperation { pub fn get_pathname(&self) -> &[u8] { if self.is_tmpdir { - // PORT NOTE: `resolve_path.joinAbs` writes into a threadlocal buffer in + // Note: `resolve_path.joinAbs` writes into a threadlocal buffer in // Zig; the Rust port returns a borrow into that TLS buffer (`'static`), // which coerces to the `&self` lifetime here. return resolve_path::join_abs::(RealFS::tmpdir_path(), &self.pathname); @@ -195,7 +195,7 @@ pub enum Value { // global mimalloc arena backs `Box<[u8]>`, so the field is dropped. bytes: Box<[u8]>, }, - // PORT NOTE: boxed to avoid blowing up `Value`'s inline size (`resolver::Result` + // Note: boxed to avoid blowing up `Value`'s inline size (`resolver::Result` // is several hundred bytes). Pending(Box), Saved(SavedFile), @@ -325,7 +325,7 @@ pub struct SavedFile { impl OutputFile { pub fn init_pending(loader: Loader, pending: bun_resolver::Result) -> OutputFile { - // PORT NOTE: Zig copied the whole `Fs.Path` struct (`pending.pathConst().?.*`). + // Note: Zig copied the whole `Fs.Path` struct (`pending.pathConst().?.*`). // The Rust `bun_paths::fs::Path<'static>` and `bun_resolver::fs::Path<'static>` are // distinct nominal types with identical layout; re-init from `text` (the // resolver path borrows arena/static memory, so the `'static` bound holds). @@ -357,7 +357,7 @@ impl OutputFile { ) -> OutputFile { let mut res = Self::init_file(file, pathname, size); if let Value::Copy(op) = &mut res.value { - // PORT NOTE: Zig wrote `res.value.copy.dir_handle = .fromStdDir(dir)` but + // Note: Zig wrote `res.value.copy.dir_handle = .fromStdDir(dir)` but // `FileOperation` has no `dir_handle` field — looks like a latent bug; the // intended field is `dir`. op.dir = dir; @@ -442,7 +442,6 @@ impl OutputFile { } } - // TODO(port): narrow error set pub fn write_to_disk(&self, root_dir: Fd, root_dir_path: &[u8]) -> Result<(), Error> { match &self.value { Value::Noop => {} @@ -488,7 +487,6 @@ impl OutputFile { Ok(()) } - // TODO(port): narrow error set pub fn move_to(&self, _: &[u8], rel_path: &[u8], dir: Fd) -> Result<(), Error> { let Value::Move(mv) = &self.value else { unreachable!() @@ -504,9 +502,8 @@ impl OutputFile { Ok(()) } - // TODO(port): narrow error set pub fn copy_to(&self, _: &[u8], rel_path: &[u8], dir: Fd) -> Result<(), Error> { - // PORT NOTE: Zig used `dir.stdDir().createFile(rel_path, .{})` and + // Note: Zig used `dir.stdDir().createFile(rel_path, .{})` and // `std.fs.cwd().openFile(...)`. Mapped to `bun_sys::openat` (which takes // a NUL-terminated `&ZStr`). let mut out_buf = PathBuffer::uninit(); diff --git a/src/bundler/ParseTask.rs b/src/bundler/ParseTask.rs index 8795ba98ed5f..05ef5f1557ce 100644 --- a/src/bundler/ParseTask.rs +++ b/src/bundler/ParseTask.rs @@ -16,14 +16,14 @@ use bun_collections::VecExt; use bun_core::strings; use bun_core::{self, Error as AnyError, FeatureFlags, declare_scope, err, scoped_log}; use bun_sys::Fd; -// PORT NOTE: Zig `bun.threading.ThreadPool` is the *module*; the draft used the +// Zig `bun.threading.ThreadPool` is the *module*; the draft used the // struct alias which made `ThreadPoolLib::Task` unresolvable. Import the module. use bun_threading::thread_pool as ThreadPoolLib; use bun_ast::Index; use bun_ast::{self as ast, E, Expr, G, Part}; use bun_js_parser as js_parser; -// PORT NOTE: `BundledAst<'arena>` — the bundler graph stores `'static`-erased +// `BundledAst<'arena>` — the bundler graph stores `'static`-erased // ASTs (arena outlives the link step). Use the crate-level alias so the // `Success`/helper signatures don't carry an explicit `'static` everywhere. use crate::JSAst; @@ -50,7 +50,7 @@ mod EventLoop { pub(super) type Task = bun_event_loop::ConcurrentTask::ConcurrentTask; } -// PORT NOTE: the per-file parse arena is held as `bump: &'static Bump` (the +// the per-file parse arena is held as `bump: &'static Bump` (the // worker arena is pinned for the entire bundle pass — see `run_with_source_code`), // so `bump.alloc_*` / `ArenaString::into_bump_str` already yield `&'static` // borrows directly. No erasure helper is needed; `StoreStr::new` covers the @@ -60,7 +60,7 @@ mod EventLoop { // live on the canonical `impl Plugin` in `bundle_v2.rs::api::JSBundler` next to // the other FFI wrappers; `bundler::JSBundlerPlugin` re-exports that type. // -// PORT NOTE: `FileMap::get` now lives on the real `JSBundler::FileMap` in +// `FileMap::get` now lives on the real `JSBundler::FileMap` in // bundle_v2.rs (no longer an opaque forward-decl). The placeholder // always-miss `get` shim that used to sit here has been removed so the two // inherent impls don't collide. @@ -73,8 +73,9 @@ mod EventLoop { #[enum_tag(existing = ContentsOrFdTag)] pub enum ContentsOrFd { Fd { dir: Fd, file: Fd }, - // TODO(port): arena lifetime — contents may be arena-owned, plugin-owned, - // or &'static (runtime). Using &'static as a placeholder. + // The `'static` is ownership-erased: contents may be arena-owned, + // plugin-owned, or truly static (runtime source). The producer keeps the + // backing allocation alive for the duration of the bundle pass. Contents(&'static [u8]), } @@ -89,7 +90,7 @@ pub(crate) enum ContentsOrFdTag { // ─────────────────────────────────────────────────────────────────────────── pub struct ParseTask { - // PORT NOTE: lifetime-erased `'static` — paths borrow from `DirnameStore` + // lifetime-erased `'static` — paths borrow from `DirnameStore` // (process-lifetime BSS string pool); see `bun_resolver::fs::Path<'a>`. pub path: Fs::Path<'static>, pub secondary_path_for_commonjs_interop: Option>, @@ -285,9 +286,6 @@ impl ParseTask { stage: ParseTaskStage::NeedsSourceCode, tree_shaking: false, is_entry_point: false, - // TODO(port): Zig struct-field defaults; Rust has no per-field - // default syntax. Consider impl Default for ParseTask and use - // `..Default::default()` here. } } @@ -576,7 +574,7 @@ pub mod parse_worker { is_entry_point: false, }; let source = Source { - // PORT NOTE: `bun_ast::Source.path` is `bun_paths::fs::Path<'static>`, distinct + // `bun_ast::Source.path` is `bun_paths::fs::Path<'static>`, distinct // from `bun_resolver::fs::Path` (TYPE_ONLY mirror). Construct // directly rather than `clone()` across the type boundary. path: bun_paths::fs::Path { @@ -587,7 +585,7 @@ pub mod parse_worker { is_symlink: false, }, contents: std::borrow::Cow::Borrowed(runtime_code.as_bytes()), - // PORT NOTE: `Source.index` is `bun_ast::Index` (newtype `u32`), + // `Source.index` is `bun_ast::Index` (newtype `u32`), // distinct from `bun_ast::Index`. Runtime source is index 0. index: bun_ast::Index(Index::RUNTIME.get()), ..Default::default() @@ -610,7 +608,7 @@ pub mod parse_worker { // (`Parser::to_lazy_export_ast`); `bun_css::BundlerStyleSheet` (gated // upstream); `Expr::init` overload set for arbitrary `E::*` defaults. - // PORT NOTE: `transpiler: *mut Transpiler` (raw, Zig `*Transpiler`). Callers + // `transpiler: *mut Transpiler` (raw, Zig `*Transpiler`). Callers // (`get_ast`, `run_with_source_code`) may also hold a raw pointer to // `(*transpiler).resolver`; materializing `&mut Transpiler` here would assert // exclusive access to the whole struct and invalidate that sibling pointer. @@ -724,7 +722,7 @@ pub mod parse_worker { // The signature now names the real `ParserOptions`; body un-gates in lockstep // with the above. - // PORT NOTE: `transpiler`/`resolver` are raw `*mut` (Zig `*Transpiler` / + // `transpiler`/`resolver` are raw `*mut` (Zig `*Transpiler` / // `*Resolver`). In Zig the caller passes `resolver = &transpiler.resolver`, so // the two may point into the same allocation. Taking `&mut Transpiler` + // `&mut Resolver` would be aliased-`&mut` UB. We instead reborrow only the @@ -752,7 +750,7 @@ pub mod parse_worker { match loader { Loader::Jsx | Loader::Tsx | Loader::Js | Loader::Ts => { let _trace = perf::trace("Bundler.ParseJS"); - // PORT NOTE: `ParserOptions` is not `Clone` (holds `&'a mut MacroContext`). + // `ParserOptions` is not `Clone` (holds `&'a mut MacroContext`). // Zig (.zig:335-342) passes the *same* `opts` (bitwise copy) to the // empty-AST fallback; since Rust moves `opts` into `.parse()`, // snapshot a faithful field-by-field copy via @@ -761,13 +759,13 @@ pub mod parse_worker { let fallback_opts = opts.clone_for_lazy_export(); let module_type = opts.module_type; return if let Some(res) = (crate::cache::JavaScript {}).parse( - bump, // TODO(port): zig passed transpiler.arena + bump, // Zig passed transpiler.arena; the per-file bump serves that role here. opts, &topts.define, log, source, )? { - // PORT NOTE: Zig's `js_parser.Result` is a bare-union whose + // Zig's `js_parser.Result` is a bare-union whose // `.ast` field is read unconditionally. The Rust port models it // as an enum; `Cached`/`AlreadyBundled` are runtime-loader // states that never reach the bundler's `getAST`, so unwrap. @@ -814,7 +812,7 @@ pub mod parse_worker { Loader::Toml => { let _trace = perf::trace("Bundler.ParseTOML"); let mut temp_log = Log::init(); - // PORT NOTE: Zig `defer { temp_log.cloneToWithRecycled(log, true); + // Zig `defer { temp_log.cloneToWithRecycled(log, true); // temp_log.msgs.clearAndFree() }` runs on the error path too. // scopeguard would alias `log`/`temp_log` (both borrowed mutably // below); reshape as a closure so every `?` exits through one @@ -1142,7 +1140,7 @@ pub mod parse_worker { )); } Loader::Html => { - // PORT NOTE: scope the scanner so its `&mut log` / `&source` + // scope the scanner so its `&mut log` / `&source` // borrows release before `new_lazy_export_ast` re-borrows them. let import_records = { let mut scanner = HTMLScanner::init(log, source); @@ -1209,14 +1207,14 @@ pub mod parse_worker { let mut import_records = Vec::::default(); let source_code = &source.contents; let mut temp_log = Log::init(); - // PORT NOTE: Zig `defer { temp_log.appendToMaybeRecycled(log, source) }` — + // Zig `defer { temp_log.appendToMaybeRecycled(log, source) }` — // folded into linear control flow (scopeguard would alias `log`/`temp_log`). const CSS_MODULE_SUFFIX: &[u8] = b".module.css"; let enable_css_modules = source.path.pretty.len() > CSS_MODULE_SUFFIX.len() && &source.path.pretty[source.path.pretty.len() - CSS_MODULE_SUFFIX.len()..] == CSS_MODULE_SUFFIX; - // PORT NOTE: `parse_bundler` takes `ParserOptions<'static>` (the + // `parse_bundler` takes `ParserOptions<'static>` (the // `'a` on `ParserOptions` is PhantomData-only; storage is a raw // `NonNull`). Construct via `default(None)` to get `'static`, // then poke the logger pointer in directly — `temp_log` outlives @@ -1271,13 +1269,13 @@ pub mod parse_worker { } // If this is a css module, the final exports object wil be set in `generateCodeForLazyExport`. let root = Expr::init(E::Object::default(), Loc { start: 0 }); - // PORT NOTE: `StylesheetExtra.symbols` is + // `StylesheetExtra.symbols` is // `Vec`; `new_lazy_export_ast_impl` takes // `Vec`. Both port the same Zig // `js_ast.Symbol`; convert field-by-field so CSS-module local refs // index a populated symbol table (.zig:613). let symbols = css_symbols_to_parser_symbols(&extra.symbols, bump); - // PORT NOTE: Zig `defer temp_log.appendToMaybeRecycled(log, source)` + // Zig `defer temp_log.appendToMaybeRecycled(log, source)` // (.zig:564-566) flushes on EVERY exit including this `try`; mirror // by matching explicitly so accumulated CSS-module diagnostics are // not dropped on the error path. @@ -1385,7 +1383,7 @@ pub mod parse_worker { // `bake_types::Framework.built_in_modules` value variant carrying `&[u8]` (vs // `Box<[u8]>` here) and `resolver.caches.fs.read_file_with_allocator` shape. - // PORT NOTE: `transpiler`/`resolver` are raw `*mut` (Zig `*Transpiler` / + // `transpiler`/`resolver` are raw `*mut` (Zig `*Transpiler` / // `*Resolver`). Callers pass `resolver = &mut (*transpiler).resolver`; taking // `&mut Transpiler` + `&mut Resolver` would be aliased-`&mut` UB. We only // touch the disjoint `(*transpiler).fs` and `(*resolver).caches.fs` fields. @@ -1478,7 +1476,7 @@ pub mod parse_worker { read_arena, ) { Ok(e) => { - // PORT NOTE: `bun_resolver::cache::Entry` ↔ `crate::cache::Entry` + // `bun_resolver::cache::Entry` ↔ `crate::cache::Entry` // are structurally identical twins; convert // by-variant so ownership of `Owned(Vec)` transfers. use bun_resolver::cache::Contents as RC; @@ -1501,7 +1499,12 @@ pub mod parse_worker { } Err(e) => { let source = Source::init_empty_file( - // TODO(port): zig duped via log.msgs.arena + // Zig duped via log.msgs.arena; here `file_path.text` + // borrows either the process-lifetime DirnameStore + // pool (resolver paths) or, after the + // `BuiltInModule::Import` reassignment above, the + // framework-owned `built_in_modules` storage held by + // the BundleV2 — both outlive the log's consumption. file_path.text, ); if e == err!("ENOENT") || e == err!("FileNotFound") { @@ -1550,7 +1553,7 @@ pub mod parse_worker { // `dispatch::PluginVTable` slot). Also calls the gated // `get_code_for_parse_task_without_plugins`. - // PORT NOTE: `transpiler`/`resolver` are raw `*mut` — see + // `transpiler`/`resolver` are raw `*mut` — see // `get_code_for_parse_task_without_plugins`. #[allow(clippy::too_many_arguments)] fn get_code_for_parse_task<'b>( @@ -1618,7 +1621,7 @@ pub mod parse_worker { pub struct OnBeforeParsePlugin<'a, 'b: 'a> { task: &'a mut ParseTask, log: &'a mut Log, - // PORT NOTE: raw `*mut` (Zig `*Transpiler` / `*Resolver`). Callers pass + // raw `*mut` (Zig `*Transpiler` / `*Resolver`). Callers pass // `resolver = &mut (*transpiler).resolver`; storing `&'a mut Transpiler<'b>` // alongside `&'a mut Resolver<'b>` would be aliased-`&mut` UB. The data // lifetime `'b` is retained on the pointee for variance only. @@ -1716,7 +1719,7 @@ pub mod parse_worker { // These structs are passed by-pointer to **third-party** native plugins via // `packages/bun-native-bundler-plugin-api/bundler_plugin.h`, so layout drift // is a silent ABI break for every plugin in the wild. Literals are the 64-bit - // C layout; TODO(port): replace with codegen-probed constants. + // C layout from `bundler_plugin.h`. bun_core::assert_ffi_layout!( OnBeforeParseArguments, 64, 8; struct_size @ 0, context @ 8, path_ptr @ 16, path_len @ 24, @@ -1854,9 +1857,9 @@ pub mod parse_worker { pub loader: Loader, #[cfg(debug_assertions)] pub check: u32, // Value to ensure OnBeforeParseResult is wrapped in this struct - // TODO(port): zig used `if (debug) u32 else u0`; in release this field - // must be zero-sized to keep extern layout matching headers. Verify - // with static_assert against bun.c. + // (Zig used `if (debug) u32 else u0`; the `cfg(debug_assertions)` gate + // above removes the field entirely in release, matching the zero-sized + // `u0` layout.) pub result: OnBeforeParseResult, } @@ -1950,7 +1953,7 @@ pub mod parse_worker { return 1; } }; - // PORT NOTE: in Zig (`.zig:953-975`) `entry.contents` is a slice into + // in Zig (`.zig:953-975`) `entry.contents` is a slice into // the worker arena (`this.arena`) with no destructor, so storing // `entry.contents.ptr` and letting `entry` go out of scope is sound. // In Rust `Contents::Owned(Vec)` (the file-read path — see @@ -2055,7 +2058,6 @@ pub mod parse_worker { impl<'a, 'b: 'a> OnBeforeParsePlugin<'a, 'b> { pub fn run( &mut self, - // TODO(port): jsc::api arrives from move-in (TYPE_ONLY → bundler) plugin: &bundler::JSBundlerPlugin, from_plugin: &mut bool, ) -> core::result::Result { @@ -2177,7 +2179,7 @@ pub mod parse_worker { if !wrapper.result.source_ptr.is_null() { let ptr = wrapper.result.source_ptr; - // PORT NOTE: `ExternalFreeFunction.function` is `Option`; + // `ExternalFreeFunction.function` is `Option`; // `OnBeforeParseResult.free_user_context` is `Option` (safe ABI per // the C header). Coerce safe→unsafe via cast. let free_fn = wrapper @@ -2251,14 +2253,14 @@ pub mod parse_worker { // `has_created` ⇒ `data`/`transpiler` were initialized in `create()`. let data = this.data.as_mut().expect("Worker.data set in create()"); - // PORT NOTE: `resolver` is a field of `*transpiler` (Zig + // `resolver` is a field of `*transpiler` (Zig // `&transpiler.resolver`). Hold both as raw `*mut` and never materialize // `&mut Transpiler` while `resolver` is live — the callee chain takes raw // pointers and reborrows disjoint fields only. // SAFETY: `data.transpiler` is initialized (see above) and pinned for the // bundle pass. let transpiler: *mut Transpiler<'static> = &raw mut data.transpiler; - // PORT NOTE: errdefer transpiler.resetStore() — reshaped: call on the err + // errdefer transpiler.resetStore() — reshaped: call on the err // path explicitly (scopeguard would alias `transpiler` access below). // SAFETY: `transpiler` is live; `resolver` projects a field of it. let resolver: *mut Resolver = unsafe { core::ptr::addr_of_mut!((*transpiler).resolver) }; @@ -2307,7 +2309,7 @@ pub mod parse_worker { log: &mut Log, entry: &mut CacheEntry, ) -> core::result::Result { - // PORT NOTE: reshaped for borrowck — `transpiler_for_target` borrows `this` + // reshaped for borrowck — `transpiler_for_target` borrows `this` // mutably; we may need to call it again below (server-components branch), // so hold it as a raw pointer and reborrow per use site. // @@ -2331,12 +2333,12 @@ pub mod parse_worker { // SAFETY: `worker_raw` just derived from the live `this: &mut Worker`. let mut transpiler: *mut Transpiler<'static> = std::ptr::from_mut(unsafe { (*worker_raw).transpiler_for_target(task.known_target) }); - // PORT NOTE: Zig errdefers (`transpiler.resetStore()` .zig:1123 and + // Zig errdefers (`transpiler.resetStore()` .zig:1123 and // `if (.fd) entry.deinit(arena)` .zig:1148) are reshaped into the // explicit `match ast_result { Err(e) => ... }` cleanup below — scopeguard // would alias the `&mut Transpiler` / `&mut CacheEntry` borrows that // follow. There are no other fallible `?` between here and that match. - // PORT NOTE: `resolver` is a field of `*transpiler` (Zig + // `resolver` is a field of `*transpiler` (Zig // `&transpiler.resolver`). Keep raw — never materialize `&mut Transpiler` // while a `&mut` derived from `resolver` is live. Per Zig, `resolver` is // bound *before* the possible `transpiler` reassignment below and stays @@ -2417,7 +2419,7 @@ pub mod parse_worker { { // separate_ssr_graph makes boundaries switch to client because the server file uses that generated file as input. // this is not done when there is one server graph because it is easier for plugins to deal with. - // SAFETY: route through `worker_raw` (see top-of-function PORT NOTE) + // SAFETY: route through `worker_raw` (see top-of-function note) // so this call's `&mut self` is a child of the same raw and does not // pop the SharedRW tag backing `resolver` (which still points into the // original target's transpiler per Zig .zig:1189). @@ -2432,7 +2434,7 @@ pub mod parse_worker { // Allocated in the worker arena so `js_parser::new_lazy_export_ast`'s // `&'bump Source` parameter is satisfied (`bump` is the same arena). let source: &'static Source = bump.alloc(Source { - // PORT NOTE: `Source.path` is `bun_paths::fs::Path<'static>`, distinct from + // `Source.path` is `bun_paths::fs::Path<'static>`, distinct from // `bun_resolver::fs::Path` (TYPE_ONLY mirror). Construct // field-by-field across the type boundary. path: bun_paths::fs::Path { @@ -2443,7 +2445,7 @@ pub mod parse_worker { is_symlink: file_path.is_symlink, }, index: bun_ast::Index(task.source_index.get()), - // PORT NOTE: `entry.contents` is owned by `task.stage` (written back by + // `entry.contents` is owned by `task.stage` (written back by // the caller after parse — see `ParseTask::run`). `Source` is stored in // `Success` which lives no longer than the `ParseTask` itself, so this // borrow is sound. Routed through the audited `StoreStr` arena-erasure @@ -2517,8 +2519,8 @@ pub mod parse_worker { opts.features.trim_unused_imports = loader.is_typescript() || topts.trim_unused_imports.unwrap_or(false); opts.features.inlining = topts.minify_syntax; - // TODO(port): TYPE_ONLY divergence — `bun_options_types::Format` vs - // `bun_js_parser::options::Format`. Map by discriminant. + // `bun_options_types::Format` and `bun_js_parser::options::Format` are + // distinct enums; map explicitly. opts.output_format = match output_format { options::Format::Esm => js_parser::options::Format::Esm, options::Format::Cjs => js_parser::options::Format::Cjs, @@ -2536,7 +2538,7 @@ pub mod parse_worker { opts.features.standard_decorators = !loader.is_typescript() || !(task.experimental_decorators || task.emit_decorator_metadata); opts.features.unwrap_commonjs_packages = topts.unwrap_commonjs_packages; - // PORT NOTE: Zig stores a `*const StringSet` (shared); Rust models it as + // Zig stores a `*const StringSet` (shared); Rust models it as // `Option>` on both sides, so we deep-clone (small — // CLI-supplied flag set). PERF(port): retype // `RuntimeFeatures.bundler_feature_flags` to `Option<&'a StringSet>` so @@ -2719,7 +2721,7 @@ pub mod parse_worker { ast, source: source.clone(), log: core::mem::take(log), - // PORT NOTE: Zig returned `log.*` by value; here we take ownership. + // Zig returned `log.*` by value; here we take ownership. use_directive, unique_key_for_additional_file: unique_key_for_additional_file.key, side_effects: task.side_effects, @@ -2754,7 +2756,7 @@ pub mod parse_worker { // SAFETY: ctx backref valid for the bundle pass (outlives this task). let ctx = unsafe { this.ctx() }; let worker: &mut crate::Worker = crate::Worker::get(ctx); - // PORT NOTE: `defer worker.unget()` — handled at function exit (scopeguard + // `defer worker.unget()` — handled at function exit (scopeguard // would alias the `&mut worker` borrows below). scoped_log!( ParseTask, @@ -2801,7 +2803,7 @@ pub mod parse_worker { } } - // PORT NOTE: reshaped for borrowck — `this` and `this.stage.needs_parse` + // reshaped for borrowck — `this` and `this.stage.needs_parse` // both borrowed mutably. Zig (.zig:1369) passes `&this.stage.needs_parse` // in-place so the entry's `Contents::Owned` buffer survives in // `task.stage` for the bundle's lifetime (Success.source.contents @@ -2855,7 +2857,7 @@ pub mod parse_worker { // Zig `.task = .{}` (.zig:1407) — default-init, NOT `undefined`. task: EventLoop::Task::default(), value, - // PORT NOTE: `ExternalFreeFunction` is POD in Zig (copied); Rust port + // `ExternalFreeFunction` is POD in Zig (copied); Rust port // doesn't derive `Copy`, so move it out (task is consumed here). external: core::mem::take(&mut this.external_free_function), watcher_data: match this.contents_or_fd { diff --git a/src/bundler/ServerComponentParseTask.rs b/src/bundler/ServerComponentParseTask.rs index 72a46ff7772b..750c908dad21 100644 --- a/src/bundler/ServerComponentParseTask.rs +++ b/src/bundler/ServerComponentParseTask.rs @@ -56,7 +56,7 @@ pub struct ReferenceProxy { } pub struct ClientEntryWrapper { - // TODO(port): lifetime — Zig `[]const u8` borrowed from caller; never freed in this file. + // Owned copy (Zig borrowed the caller's `[]const u8`). pub path: Box<[u8]>, } @@ -82,7 +82,7 @@ fn task_callback_wrap(thread_pool_task: *mut ThreadPoolTask) { .ctx .expect("ServerComponentParseTask.ctx set at enqueue"); let worker = Worker::get(ctx.get()); - // PORT NOTE: `defer worker.unget()` — handled at end of fn (no early returns). + // `defer worker.unget()` — handled at end of fn (no early returns). let mut log = Log::new(); // SAFETY: `worker.arena` is set in `Worker::create` to point at the @@ -165,7 +165,7 @@ fn task_callback( .ctx .as_deref() .expect("ServerComponentParseTask.ctx set at enqueue"); - // PORT NOTE: `Source` is not `Clone`; the original is consumed here + // `Source` is not `Clone`; the original is consumed here // (Zig copied by value). Take it up-front so `ab`'s borrow of it ends // (via NLL) before we move it into `Success`. let source = core::mem::take(&mut task.source); diff --git a/src/bundler/ThreadPool.rs b/src/bundler/ThreadPool.rs index 236f17412b9b..9eac30e53414 100644 --- a/src/bundler/ThreadPool.rs +++ b/src/bundler/ThreadPool.rs @@ -19,11 +19,11 @@ use bun_threading::{Mutex, thread_pool as ThreadPoolLib}; use crate::cache::{Contents, Entry as CacheEntry, ExternalFreeFunction}; use crate::linker_context_mod::StmtList; -// PORT NOTE: `crate::options::Target` is the lower-tier `bun_options_types` +// `crate::options::Target` is the lower-tier `bun_options_types` // enum (re-exported for downstream crates); `BundleOptions.target` is the // file-backed `options_impl::Target`. Compare against the latter so -// `primary.options.target == target` type-checks. TODO(refactor): collapse -// the two enums into one (see lib.rs `pub mod options` shadow note). +// `primary.options.target == target` type-checks. (The two enums could be +// collapsed into one; see lib.rs `pub mod options` shadow note.) use crate::BundleV2; use crate::options_impl::Target; use crate::parse_task::{ContentsOrFd, ParseTask, ParseTaskStage}; @@ -42,19 +42,19 @@ pub struct ThreadPool { /// On Windows, this seemed to be a small performance improvement. /// On Linux, this was a performance regression. /// In some benchmarks on macOS, this yielded up to a 60% performance improvement in microbenchmarks that load ~10,000 files. - // PORT NOTE: Zig left this `undefined` when `!uses_io_pool()`; `Option` makes + // Zig left this `undefined` when `!uses_io_pool()`; `Option` makes // that explicit. `ParentRef` (not raw `NonNull`) because the pointee is the // module-static `io_thread_pool::THREAD_POOL`, live while `ref_count > 0`, // and all `ThreadPoolLib` driver methods (`schedule`, `warm`, // `wake_for_idle_events`) take `&self` — so the safe `Deref` projection is // sufficient and the per-read `unsafe { p.as_ref() }` disappears. pub io_pool: Option>, - // TODO(port): lifetime — TSV class UNKNOWN. Conditionally owned via - // `worker_pool_is_owned`; kept raw so callers (bundle_v2.rs draft) can - // dereference for `wake_for_idle_events()` without a borrow on `ThreadPool`. + // Conditionally owned via `worker_pool_is_owned`; kept raw so callers + // (bundle_v2.rs) can dereference for `wake_for_idle_events()` without a + // borrow on `ThreadPool`. pub worker_pool: *mut ThreadPoolLib::ThreadPool, pub worker_pool_is_owned: bool, - // PORT NOTE: Zig had `workers_assignments` + sibling `workers_assignments_lock`. + // Zig had `workers_assignments` + sibling `workers_assignments_lock`. // Per PORTING.md §Concurrency ("Mutex owns T"), the lock is folded into // the field so `get_worker` can take `&self` — `Worker::get` is entered // concurrently from arbitrary worker-pool threads, and a `&mut self` here @@ -107,7 +107,7 @@ mod io_thread_pool { bun_core::RacyCell::new(MaybeUninit::uninit()); /// Protects initialization and deinitialization of the IO thread pool. static MUTEX: Mutex = { - // PORT NOTE: `Mutex` derives `Default` but `Default::default()` isn't + // `Mutex` derives `Default` but `Default::default()` isn't // `const`. The Zig source used `bun.threading.Mutex{}` (zero-init); // an all-zero `Mutex` is the documented unlocked state on every impl. // SAFETY: `Mutex` is `repr(Rust)` over an atomic / Futex word; zero is @@ -161,7 +161,7 @@ mod io_thread_pool { // 2 means initialized and referenced by one `ThreadPool`. REF_COUNT.store(2, Ordering::Release); } else { - // PORT NOTE: Zig fell through to `return &thread_pool` without + // Zig fell through to `return &thread_pool` without // bumping the ref count here, which is a latent bug in the source // (the racing acquirer's reference isn't counted). Mirrored. } @@ -200,7 +200,7 @@ mod io_thread_pool { unsafe { (*THREAD_POOL.get()).assume_init_drop(); } - // PORT NOTE: Zig source falls off the end of a `bool`-returning fn here + // Zig source falls off the end of a `bool`-returning fn here // (`thread_pool = undefined;` is the last statement). Assuming `true`. true } @@ -222,7 +222,7 @@ impl ThreadPool { // end-to-end. worker_pool: Option>, ) -> Result { - // PORT NOTE: Spec ThreadPool.zig:85 allocated via the bundle arena + // Spec ThreadPool.zig:85 allocated via the bundle arena // (`v2.arena().create`), so the `false` ownership flag was // harmless — the arena reclaimed it. Here we `heap::alloc` (global // heap), so `deinit()` must `heap::take` it back; record ownership. @@ -347,7 +347,7 @@ impl ThreadPool { let ContentsOrFd::Contents(contents) = parse_task.contents_or_fd else { unreachable!() }; - // PORT NOTE: Zig moved the `[]const u8` slice into the cache entry + // Zig moved the `[]const u8` slice into the cache entry // by value. `cache::Contents` has no borrowed-slice variant; the // contract (see ParseTask.rs `run_with_source_code` defer) is that // `entry.deinit()` is *skipped* when `contents_or_fd == .contents`, @@ -396,7 +396,7 @@ impl ThreadPool { } } - // PORT NOTE: takes `*mut` (Zig: `*ParseTask`) so callers can pass either a + // takes `*mut` (Zig: `*ParseTask`) so callers can pass either a // raw heap pointer (e.g. `load.parse_task`) or a `&mut` (auto-coerces). pub fn schedule(&self, parse_task: *mut ParseTask) { self.schedule_with_options(parse_task, false); @@ -406,7 +406,7 @@ impl ThreadPool { self.schedule_with_options(parse_task, true); } - // PORT NOTE: returns `&'static mut` — the `Worker` is `heap::alloc`'d + // returns `&'static mut` — the `Worker` is `heap::alloc`'d // below and lives until `Worker::deinit`; detaching from `&self` lets // callers re-borrow `ThreadPool` while holding the worker (Zig: `*Worker`). // Takes `&self` (not `&mut`) because this is called concurrently from @@ -513,7 +513,7 @@ pub struct Worker { /// Thread-local memory arena /// All allocations are freed in `deinit` at the very end of bundling. - // PORT NOTE: self-referential borrow of `heap` — `BackRef` (not a real + // self-referential borrow of `heap` — `BackRef` (not a real // `&'self ThreadLocalArena`) so it can be reseated in `create()` without a // self-borrow and so call sites read it via safe `Deref` instead of // open-coding a raw deref. Zig stored the `std.mem.Allocator` vtable; here @@ -549,7 +549,7 @@ pub struct Worker { impl Worker { /// Reborrow the self-referential `arena` (= `&self.heap`) as a shared /// reference. `BackRef` field, so the deref is encapsulated in - /// [`bun_ptr::BackRef::get`]; see PORT NOTE on the field. + /// [`bun_ptr::BackRef::get`]; see note on the field. /// /// `arena` is set to `&self.heap` in [`Worker::create`] before any caller /// can observe the `Worker`, and is never dangling after that point. The @@ -571,11 +571,11 @@ impl Worker { } pub struct WorkerData { - // TODO(port): lifetime — TSV class ARENA (`&'arena mut bun_ast::Log`); kept - // raw because the arena is the sibling field `Worker.heap`. + // Kept raw (Zig: `*Log` into the worker arena) because the pointee's arena + // is the sibling field `Worker.heap`, which Rust cannot express as a borrow. pub log: *mut bun_ast::Log, pub estimated_input_lines_of_code: usize, - // PORT NOTE: lifetime erased to `'static` — the inner `&'a Arena` borrows + // lifetime erased to `'static` — the inner `&'a Arena` borrows // `Worker.heap`, which Rust can't express on a sibling field. Zig used // `transpiler: Transpiler` with a copied `std.mem.Allocator`. // @@ -669,9 +669,8 @@ impl Worker { // via `*ast_memory_store = ...`. Dropped exactly once here, *outside* // the `has_created` guard so the default-constructed arena is freed // even when `create()` never ran (Zig left it `undefined`; Rust does - // not). Ordered before `heap = None` in case the TODO(port) in - // `ASTMemoryAllocator::new` ever threads `arena_ref` (= `&self.heap`) - // through. + // not). Ordered before `heap = None` in case `ASTMemoryAllocator::new` + // ever threads `arena_ref` (= `&self.heap`) through. unsafe { ManuallyDrop::drop(&mut worker.ast_memory_store) }; if worker.has_created { worker.heap = None; @@ -683,7 +682,7 @@ impl Worker { unsafe { bun_core::heap::destroy(this) }; } - // PORT NOTE: returns `&'static mut` (detached) — the `Worker` is + // returns `&'static mut` (detached) — the `Worker` is // heap-pinned (heap::alloc in `get_worker`) and outlives any `ctx` // borrow; Zig returned `*Worker`. Tying it to `ctx`'s lifetime would // forbid the `worker` ↔ `ctx` re-borrows in `ParseTask::run_*`. @@ -715,7 +714,7 @@ impl Worker { } fn create(&mut self, ctx: &BundleV2<'_>) { - // PORT NOTE: `bun_perf::trace` takes a generated `PerfEvent` enum, and + // `bun_perf::trace` takes a generated `PerfEvent` enum, and // the generator hasn't emitted `Bundler.Worker.create` yet (only // `_Stub`). Dropped to avoid mis-attributing the span. // let _trace = bun_perf::trace("Bundler.Worker.create"); @@ -761,7 +760,7 @@ impl Worker { /// Build a per-worker `Transpiler` from `from` (Zig: `transpiler.* = from.*`). /// - /// PORT NOTE: reshaped for borrowck — associated fn (no `&mut self`) so + /// reshaped for borrowck — associated fn (no `&mut self`) so /// callers can borrow `self.data.log` disjointly. The returned value is a /// fully-owned `Transpiler` whose `Drop` is sound; `wire_after_move` must /// be called once it is at its final address. diff --git a/src/bundler/analyze_transpiled_module.rs b/src/bundler/analyze_transpiled_module.rs index 8c5b6b308216..f96209da5658 100644 --- a/src/bundler/analyze_transpiled_module.rs +++ b/src/bundler/analyze_transpiled_module.rs @@ -232,7 +232,7 @@ impl ModuleInfoDeserialized { unsafe { match (*this).owner { Owner::ModuleInfo(mi) => { - // PORT NOTE: Zig recovered the parent via + // Zig recovered the parent via // `@fieldParentPtr("_deserialized", self)`. The Rust port // stores the `*mut ModuleInfo` directly because the printer // crate's `ModuleInfo` no longer embeds this struct. @@ -342,14 +342,13 @@ impl ModuleInfoDeserialized { /// cache or standalone module graph). Returns `None` instead of panicking on /// corrupt/truncated data. pub fn create_from_cached_record(source: &[u8]) -> Option> { - // PORT NOTE: Zig matched on error.OutOfMemory → bun.outOfMemory(); in + // Zig matched on error.OutOfMemory → bun.outOfMemory(); in // Rust, allocation failure aborts via the global arena, so only // BadModuleInfo remains. Self::create(source).ok() } pub fn serialize(&self, writer: &mut impl bun_io::Write) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let record_kinds = self.record_kinds(); writer.write_all(&(record_kinds.len() as u32).to_le_bytes())?; writer.write_all(slice_as_bytes(record_kinds))?; @@ -482,14 +481,14 @@ impl ModuleInfoExt for ModuleInfo { drop(unsafe { bun_core::heap::take(this) }); } fn into_deserialized(mut self: Box) -> Box { - // PORT NOTE: Zig wrote a self-referential `_deserialized` view inside + // Zig wrote a self-referential `_deserialized` view inside // `ModuleInfo` during `finalize()`. The Rust printer-crate `ModuleInfo` // exposes a borrowed `as_deserialized()` instead; here we materialise the // raw-pointer FFI shape and tie its lifetime to the leaked `Box`. if !self.finalized { let _ = self.finalize(); } - // PORT NOTE: reshaped for borrowck — capture lifetime-erased `RawSlice` + // Reshaped for borrowck — capture lifetime-erased `RawSlice` // views before `heap::into_raw(self)` consumes the box. let (strings_buf, strings_lens, rm_keys, rm_values, rm_phases, buffer, record_kinds, flags); { diff --git a/src/bundler/barrel_imports.rs b/src/bundler/barrel_imports.rs index c58fcf28a439..753cc9167328 100644 --- a/src/bundler/barrel_imports.rs +++ b/src/bundler/barrel_imports.rs @@ -52,7 +52,7 @@ impl RequestedExports { } } -// PORT NOTE: `original_alias` is stored as the raw arena `*const [u8]` (the +// `original_alias` is stored as the raw arena `*const [u8]` (the // `NamedImport.alias` representation) instead of a tied `&'a [u8]` so the BFS // loop can hold a `BarrelExportResolution` across a `&mut graph.ast` reborrow // without the borrow checker seeing an overlap. The pointee outlives the @@ -181,7 +181,7 @@ fn apply_barrel_optimization_impl( // handles the case where file A imports Alpha from the barrel (previous // build) and file B adds Beta (current build). Without this, Alpha would // be re-deferred because only B's requests are in requested_exports. - // PORT NOTE: `DevServerHandle` is `Copy`; copied out so the `&self` borrow + // `DevServerHandle` is `Copy`; copied out so the `&self` borrow // doesn't conflict with later `&mut this.requested_exports`. let dev_handle = this.dev_server_handle().copied(); if let Some(dev) = dev_handle { @@ -238,7 +238,7 @@ fn apply_barrel_optimization_impl( // Mark unneeded named re-export records as is_unused. let mut has_deferrals = false; - // PORT NOTE: reshaped for borrowck — collect (ref_, import_record_index) + // Borrowck: collect (ref_, import_record_index) // pairs first so we can mutate `ast.import_records` without aliasing // `ast.named_exports`/`ast.named_imports`. for i in 0..ast.named_exports.count() { @@ -303,7 +303,7 @@ fn un_defer_record(import_records: &mut import_record::List, record_idx: u32) -> } /// BFS work queue item: un-defer an export from a barrel. -// PORT NOTE: 'a borrows arena-backed AST alias strings. +// `'a` borrows arena-backed AST alias strings. struct BarrelWorkItem<'a> { barrel_source_index: u32, alias: &'a [u8], @@ -370,7 +370,7 @@ pub(crate) fn schedule_barrel_deferred_imports( result_source_index: u32, result_ast_target: bun_ast::Target, ) -> Result { - // PORT NOTE: Zig passed `*ParseTask.Result.Success` and read `result.ast` + // The Zig original passed `*ParseTask.Result.Success` and read `result.ast` // after `graph.ast.set(idx, result.ast)` value-copied it. Rust *moves* // `result.ast` into `graph.ast`, so this fn reads the just-written // `graph.ast[result_source_index]` instead. Phase 1/2 only read @@ -388,7 +388,7 @@ pub(crate) fn schedule_barrel_deferred_imports( let file_named_imports: bun_ptr::BackRef = bun_ptr::BackRef::new(&this.graph.ast.items_named_imports()[result_source_index as usize]); - // PORT NOTE: `DevServerHandle` copied out so `&mut this.*` field borrows + // `DevServerHandle` copied out so `&mut this.*` field borrows // don't conflict with the `&self` accessor. let dev_handle = this.dev_server_handle().copied(); @@ -402,7 +402,7 @@ pub(crate) fn schedule_barrel_deferred_imports( // barrel optimization requires source_indices to seed requested_exports and to // BFS un-defer records. Resolve paths → source_indices here as a fallback. // - // PORT NOTE: reshaped for borrowck — `path_to_source_index_map` borrows + // Borrowck: `path_to_source_index_map` borrows // `&mut this.graph`; wrap in `BackRef` so the long-lived read borrow // doesn't conflict with `&mut this.requested_exports` / // `&mut this.graph.ast` below. The map is not mutated for the duration of @@ -417,7 +417,7 @@ pub(crate) fn schedule_barrel_deferred_imports( None }; - // See PORT NOTE above — read-only deref valid through Phase 2. + // Read-only deref — valid through Phase 2 (see the raw-read note above). let file_import_records = file_import_records.get(); // Build a set of import_record_indices that have named_imports entries, @@ -451,7 +451,7 @@ pub(crate) fn schedule_barrel_deferred_imports( } } - // See PORT NOTE above — read-only deref valid through Phase 2. + // Read-only deref — valid through Phase 2 (see the raw-read note above). for ni in file_named_imports.values() { if ni.import_record_index as usize >= file_import_records.len() { continue; @@ -546,7 +546,7 @@ pub(crate) fn schedule_barrel_deferred_imports( // PERF(port): was stack-fallback (8192) — profile if hot. let mut queue: Vec = Vec::new(); - // See PORT NOTE above — read-only deref valid through Phase 2. + // Read-only deref — valid through Phase 2 (see the raw-read note above). for ni in file_named_imports.values() { if ni.import_record_index as usize >= file_import_records.len() { continue; @@ -667,7 +667,7 @@ pub(crate) fn schedule_barrel_deferred_imports( let mut newly_scheduled: i32 = 0; let mut qi: usize = 0; while qi < queue.len() { - // PORT NOTE: reshaped for borrowck — copy item fields out before pushing to queue later + // Copy item fields out before pushing to `queue` later (borrowck). let item_barrel_idx = queue[qi].barrel_source_index; let item_alias = queue[qi].alias; let item_is_star = queue[qi].is_star; @@ -705,11 +705,11 @@ pub(crate) fn schedule_barrel_deferred_imports( // Use a helper to get barrel_ir freshly each time, since // resolveBarrelRecords can reallocate graph.ast and invalidate pointers. - // PORT NOTE: reshaped for borrowck — re-borrow graph.ast column after each mutation + // Re-borrow the `graph.ast` column after each mutation (borrowck). let barrel_ir = &mut this.graph.ast.items_import_records_mut()[barrel_idx as usize]; if item_is_star { - // PORT NOTE: reshaped for borrowck — read flags by index, then mutate + // Read flags by index, then mutate (borrowck). let len = barrel_ir.len(); for idx in 0..len { let flags = barrel_ir.as_slice()[idx].flags; @@ -733,7 +733,7 @@ pub(crate) fn schedule_barrel_deferred_imports( ); let Some(resolution) = resolution else { // Name not in named re-exports — might come from export *. - // PORT NOTE: clone the small u32 slice so iterating it doesn't + // Clone the small u32 slice so iterating it doesn't // alias the mutable `barrel_ir` borrow taken inside the loop. let star_records: Vec = this.graph.ast.items_export_star_import_records()[barrel_idx as usize].to_vec(); @@ -773,7 +773,7 @@ pub(crate) fn schedule_barrel_deferred_imports( } // `original_alias` is an arena-backed `StoreStr` valid for the - // bundler-arena lifetime (see `BarrelExportResolution` PORT NOTE). + // bundler-arena lifetime (see the comment on `BarrelExportResolution`). let propagate_alias: &[u8] = match resolution.original_alias { Some(p) => p.slice(), None => alias, diff --git a/src/bundler/bundle_v2.rs b/src/bundler/bundle_v2.rs index 5c73a36ce9a8..86870e1a870a 100644 --- a/src/bundler/bundle_v2.rs +++ b/src/bundler/bundle_v2.rs @@ -61,7 +61,7 @@ pub struct PendingImport { } pub struct BundleV2<'a> { - // PORT NOTE: Zig stored `*Transpiler` (and aliased the same pointer into + // Zig stored `*Transpiler` (and aliased the same pointer into // `ssr_transpiler` when SSR graph isn't separate). `ssr_transpiler` stays // `*mut` so the alias is legal; `transpiler` is `&'a mut` for ergonomic // field access throughout the bundler bodies. @@ -162,7 +162,7 @@ pub struct BakeOptions<'a> { impl<'a> BundleV2<'a> { // ── raw-ptr accessors ───────────────────────────────────────────────── - // PORT NOTE: `transpiler`/`ssr_transpiler` are `*mut` because Zig stored + // `transpiler`/`ssr_transpiler` are `*mut` because Zig stored // aliased `*Transpiler` (same pointer in both slots when no SSR graph). // Callers go through these accessors so the unsafe deref is centralized. #[inline] @@ -277,7 +277,7 @@ impl<'a> BundleV2<'a> { } } - // PORT NOTE: draft `on_parse_task_complete` / `deinit_without_freeing_arena` + // draft `on_parse_task_complete` / `deinit_without_freeing_arena` // removed — canonical bodies live in the later impl blocks below. } // ══════════════════════════════════════════════════════════════════════════ @@ -355,7 +355,6 @@ pub mod bv2_impl { use bun_resolver::fs::PathResolverExt as _; use bun_resolver::{self as _resolver, is_package_path}; use bun_threading::ThreadPool as ThreadPoolLib; - // TODO(b0): bake_types arrives from move-in (TYPE_ONLY Side/Graph/BuiltInModule/Framework → bundler) use self::bake_types as bake; /// CYCLEBREAK(b0) TYPE_ONLY: pure value types from bake that bundler needs without @@ -465,8 +464,6 @@ pub mod bv2_impl { /// the bundler/parser actually consult (see ParseTask.zig:1253 /// `opts.framework = transpiler.options.framework`); `file_system_router_types` /// stays in T6 because only `bake::FrameworkRouter` reads it. - // TODO(b0-genuine): remaining Framework field `file_system_router_types` - // stays in T6; only bake::FrameworkRouter reads it. #[non_exhaustive] pub struct Framework { pub built_in_modules: bun_collections::StringArrayHashMap, @@ -722,7 +719,6 @@ pub mod bv2_impl { } } } - // TODO(b0): jsc::api arrives from move-in (TYPE_ONLY → bundler) use self::api as jsc_api; /// CYCLEBREAK(b0) TYPE_ONLY: data-only halves of `jsc::api::JSBundler` and @@ -1188,7 +1184,7 @@ pub mod bv2_impl { } pub fn run_on_js_thread(&mut self) { let kind = self.import_record.kind; - // PORT NOTE: reshaped for borrowck — capture the erased self + // reshaped for borrowck — capture the erased self // pointer before borrowing fields immutably for the FFI call. let self_ptr = std::ptr::from_mut::(self).cast::(); // SAFETY: `bv2` is a valid backref set by `init`; the plugin @@ -1337,7 +1333,7 @@ pub mod bv2_impl { pub fn run_on_js_thread(&mut self) { let is_server_side = self.bake_graph() != crate::bake_types::Graph::Client; let default_loader = self.default_loader; - // PORT NOTE: reshaped for borrowck — capture the erased self + // reshaped for borrowck — capture the erased self // pointer before borrowing fields immutably for the FFI call. let self_ptr = std::ptr::from_mut::(self).cast::(); // SAFETY: `bv2` is a valid backref set by `init`; the plugin @@ -1627,7 +1623,7 @@ pub mod bv2_impl { pub fn initialize_client_transpiler(&mut self) -> Result<&mut Transpiler<'a>, Error> { // bundle_v2.zig:198-241. // - // PORT NOTE: Zig does `client_transpiler.* = this_transpiler.*` (bitwise + // Zig does `client_transpiler.* = this_transpiler.*` (bitwise // struct copy into an arena slot — no destructors). The Rust port // builds a fresh owned `Transpiler` via `Transpiler::for_worker` // (per-field deep clone), mutates the browser-specific options with @@ -1742,7 +1738,7 @@ pub mod bv2_impl { pub all_loaders: &'a [Loader], pub all_urls_for_css: &'a [&'a [u8]], pub redirects: &'a [u32], - // PORT NOTE: Zig copied the map by value (cheap shallow copy). The Rust + // Zig copied the map by value (cheap shallow copy). The Rust // `PathToSourceIndexMap` is `!Clone` and the field is unread in `visit`, so // store a raw backref to satisfy the struct shape without forcing `Clone`. pub redirect_map: *const PathToSourceIndexMap, @@ -1808,7 +1804,7 @@ pub mod bv2_impl { let import_record_list_id = source_index; // when there are no import records, v index will be invalid if (import_record_list_id.get() as usize) < self.all_import_records.len() { - // PORT NOTE: reshaped for borrowck — split borrow of all_import_records + // reshaped for borrowck — split borrow of all_import_records let import_records_len = self.all_import_records[import_record_list_id.get() as usize].len() as usize; for ir_idx in 0..import_records_len { @@ -1821,7 +1817,7 @@ pub mod bv2_impl { while let Some(redirect_id) = get_redirect_id(self.redirects[other_source.get() as usize]) { - // PORT NOTE: reshaped for borrowck — copy out the redirect target's + // reshaped for borrowck — copy out the redirect target's // (source_index, path) before re-borrowing `all_import_records` mutably. let (other_src_idx, other_path) = { let other_import_records = @@ -1947,7 +1943,7 @@ pub mod bv2_impl { self.dynamic_import_entry_points = ArrayHashMap::new(); - // PORT NOTE: reshaped for borrowck — hoist the values that would + // reshaped for borrowck — hoist the values that would // otherwise re-borrow `self`/`self.graph` while the visitor holds // disjoint column refs (Zig pulled multiple `items(.field)` columns at // once with no aliasing model). @@ -1959,7 +1955,7 @@ pub mod bv2_impl { // when `scb_bitset` is `None`). let scb_list = self.graph.server_component_boundaries.slice(); - // PORT NOTE: reshaped for borrowck — `Slice` is a value-type + // reshaped for borrowck — `Slice` is a value-type // snapshot of column pointers (does not borrow `self.graph.ast`), so // `split_mut()` on the local can coexist with the shared borrows // below. The slab does not resize for the duration of this function. @@ -2021,12 +2017,12 @@ pub mod bv2_impl { } } - // PORT NOTE: reshaped for borrowck — release the visitor's `&mut` + // reshaped for borrowck — release the visitor's `&mut` // borrows on the two bitsets and `input_files` columns before the // cleanup loop reads them. let ReachableFileVisitor { reachable, .. } = visitor; - // PORT NOTE: reshaped for borrowck — three disjoint mutable SoA + // reshaped for borrowck — three disjoint mutable SoA // columns via `split_mut()` on a value-type `Slice` snapshot. let mut input_files_slice = self.graph.input_files.slice(); let input_files_cols = input_files_slice.split_mut(); @@ -2057,7 +2053,7 @@ pub mod bv2_impl { if self.graph.pending_items == 0 { let this: *mut Self = self; - // PORT NOTE: reshaped for borrowck — Zig passed `&self.graph` and + // reshaped for borrowck — Zig passed `&self.graph` and // `self` to the same call. Take a raw ptr so the two `&mut` don't // overlap from rustc's view. // SAFETY: `drain_deferred_tasks` only touches `self.graph.deferred_*` @@ -2074,7 +2070,7 @@ pub mod bv2_impl { pub fn wait_for_parse(&mut self) { // bundle_v2.zig:488-491 — `this.loop().tick(this, &isDone)`. // - // PORT NOTE: `tick_raw` (not `tick`) — `is_done` reborrows `*ctx` as + // `tick_raw` (not `tick`) — `is_done` reborrows `*ctx` as // `&mut BundleV2`, and `BundleV2` (via `linker.r#loop`) owns the // `AnyEventLoop` slot, so holding `&mut AnyEventLoop` across the // callback would be a Stacked-Borrows violation. @@ -2130,7 +2126,7 @@ pub mod bv2_impl { // pick the "module" field and the package is imported with "require" then // code expecting a function will crash. // - // PORT NOTE: reshaped for borrowck — Zig pulled the mutable + // reshaped for borrowck — Zig pulled the mutable // `import_records` column alongside shared columns. `split_mut()` on a // value-type `Slice` snapshot yields the one mutable column without // borrowing `self.graph.ast`; read the per-target map through the @@ -2175,7 +2171,7 @@ pub mod bv2_impl { import_record: &jsc_api::JSBundler::MiniImportRecord, target: options::Target, ) { - // PORT NOTE: reshaped for borrowck — Zig held a `*Transpiler` raw pointer alongside + // reshaped for borrowck — Zig held a `*Transpiler` raw pointer alongside // other `this.*` accesses. `transpiler_for_target` borrows `&mut self`, so launder // through a raw pointer to keep `*self` available below. // SAFETY: the returned `&mut Transpiler` lives for `'a` (set in `init`), is not @@ -2193,7 +2189,7 @@ pub mod bv2_impl { ) { let file_map_result = _file_map_result; let mut path_primary = file_map_result.path_pair.primary; - // PORT NOTE: reshaped for borrowck — `get_or_put` borrows `*self` mutably via + // reshaped for borrowck — `get_or_put` borrows `*self` mutably via // `self.graph`; capture the slot as `*mut u32` so subsequent `self.*` calls // type-check. SAFETY: `path_to_source_index_map(target)` is not mutated again // until after the last `*value_ptr` access below. @@ -2307,7 +2303,7 @@ pub mod bv2_impl { } let handles_import_errors; - // PORT NOTE: reshaped for borrowck — `log_for_resolution_failures` borrows + // reshaped for borrowck — `log_for_resolution_failures` borrows // `&mut self`; the returned log is backed by either a DevServer-owned slot or // `*self.transpiler.log` (both raw-pointer-derived), so detach the lifetime // so `self.graph.*` / `self.transpiler.*` reads below type-check. @@ -2400,7 +2396,7 @@ pub mod bv2_impl { let out_source_index: Option; - // PORT NOTE(borrowck): Zig held a `*Fs.Path` into `resolve_result` while + // borrowck: Zig held a `*Fs.Path` into `resolve_result` while // also reading other fields and re-borrowing `self`. Rust borrowck rejects // that, so we clone the active path out and operate on an owned value. let mut path: Fs::Path<'static> = match resolve_result.path() { @@ -2438,7 +2434,7 @@ pub mod bv2_impl { path.assert_pretty_is_valid(); path.assert_file_path_is_absolute(); - // PORT NOTE(borrowck): split Zig's `getOrPut` into get-then-put so the map + // borrowck: split Zig's `getOrPut` into get-then-put so the map // borrow doesn't span `enqueue_parse_task` (which needs `&mut self`). if let Some(existing) = self.path_to_source_index_map(target).get(path.text) { out_source_index = Some(Index::init(existing)); @@ -2446,7 +2442,7 @@ pub mod bv2_impl { path = self .path_with_pretty_initialized(&path, target) .expect("oom"); - // PORT NOTE: Zig wrote through `path.* = …` (a `*Fs.Path` into + // Zig wrote through `path.* = …` (a `*Fs.Path` into // `resolve_result.path_pair`); the borrowck-reshape above cloned // `path` out, so write the prettified path back so // `ParseTask::init(&resolve_result, ..)` (via `enqueue_parse_task`) @@ -2499,7 +2495,7 @@ pub mod bv2_impl { // For example, it is silly to bundle index.css depended on by client+server twice. // It makes sense to separate these for JS because the target affects DCE if self.transpiler.options.server_components && !loader.is_javascript_like() { - // PORT NOTE: reshaped for borrowck — cannot hold two `&mut` into + // reshaped for borrowck — cannot hold two `&mut` into // `self.graph` simultaneously, so re-derive the map per insert. let key_text: Box<[u8]> = path.text.to_vec().into_boxed_slice(); let main_target = self.transpiler.options.target; @@ -2541,7 +2537,7 @@ pub mod bv2_impl { target: options::Target, ) -> Result<(), Error> { // TODO: plugins with non-file namespaces - // PORT NOTE(borrowck): split Zig's `getOrPut` into get-then-put so the map + // borrowck: split Zig's `getOrPut` into get-then-put so the map // borrow doesn't span the resolver / `&mut self` calls below. if self .path_to_source_index_map(target) @@ -2566,7 +2562,7 @@ pub mod bv2_impl { path = self.path_with_pretty_initialized(&path, target)?; path.assert_pretty_is_valid(); - // PORT NOTE: see `enqueue_entry_item` — write the prettified path back + // see `enqueue_entry_item` — write the prettified path back // into `result` so `ParseTask::init(&result, ..)` reads the relativized // `pretty` (Zig mutated `result.path_pair.primary` in place via `path.*`). result.path_pair.primary = path; @@ -2628,7 +2624,7 @@ pub mod bv2_impl { target: options::Target, ) -> Result, Error> { let result = &mut *resolve; - // PORT NOTE(borrowck): clone the active path out so we don't hold a `&mut` + // borrowck: clone the active path out so we don't hold a `&mut` // into `result` across the `&mut self` calls below. let mut path: Fs::Path<'static> = match result.path() { Some(p) => *p, @@ -2636,7 +2632,7 @@ pub mod bv2_impl { }; path.assert_file_path_is_absolute(); - // PORT NOTE(borrowck): split Zig's `getOrPut` into get-then-put. + // borrowck: split Zig's `getOrPut` into get-then-put. if self .path_to_source_index_map(target) .get(path.text) @@ -2659,7 +2655,7 @@ pub mod bv2_impl { .into_static() }; path.assert_pretty_is_valid(); - // PORT NOTE: intern via `dupe_alloc` BEFORE writing back into `result` / + // intern via `dupe_alloc` BEFORE writing back into `result` / // the path-to-source-index map. Zig didn't need this — its dev-server // `EntryPointList` keys borrow `dev.server_graph.bundled_files.keys()` // (DevServer-owned), and `genericPathWithPrettyInitialized` returns the @@ -2675,7 +2671,7 @@ pub mod bv2_impl { // 'bun-framework-react/server.tsx'" when the worker can no longer match // `built_in_modules`. path = path.dupe_alloc(self.arena()).expect("oom"); - // PORT NOTE: Zig's `var path = result.path()` is a `*Fs.Path` *into* + // Zig's `var path = result.path()` is a `*Fs.Path` *into* // `result.path_pair`, so the `path.* = pathWithPrettyInitialized(...)` // assignment mutates the resolver result in place. The borrowck-reshape // above cloned `path` out, which left `result.path_pair` with the @@ -2757,7 +2753,9 @@ pub mod bv2_impl { thread_pool: Option>, heap: &'a ThreadLocalArena, ) -> Result>, Error> { - // TODO(port): arena-allocate self via bump.alloc — Box::new is wrong arena (Zig: arena.create(@This()) on arena) + // (Zig arena-allocated self via `arena.create(@This())`; the Box here + // is heap-owned and dropped by the caller, which is the correct Rust + // ownership model.) transpiler.env().load_tracy(); transpiler.options.mark_builtins_as_external = @@ -2828,7 +2826,7 @@ pub mod bv2_impl { } } } - // PORT NOTE: Zig wired `heap.arena()` into `transpiler.arena` / + // Zig wired `heap.arena()` into `transpiler.arena` / // `resolver.arena` / `linker.arena` / `log.msgs.arena`. The // Rust `Transpiler<'a>`/`Resolver<'a>` store `&'a Arena` and `Log.msgs` // is a `Vec` (global alloc), so only `linker.graph.bump` needs the @@ -2980,8 +2978,9 @@ pub mod bv2_impl { } } - // PORT NOTE: split because data type varies by variant — cannot express `switch(variant)`-typed param with const-generic enum on stable - // TODO(port): comptime variant enum param + dependent data type — split into three monomorphic fns + // Zig used a comptime variant enum param with a variant-dependent data + // type; that cannot be expressed with a const-generic enum on stable + // Rust, so this is split into three monomorphic fns. pub fn enqueue_entry_points_normal>( &mut self, data: &[P], @@ -3241,7 +3240,8 @@ pub mod bv2_impl { fn clone_ast(&mut self) -> Result<(), Error> { let _trace = crate::perf::trace("Bundler.cloneAST"); - // TODO(port): bun.safety.alloc.assertEq + // (Zig's `bun.safety.alloc.assertEq` allocator check has no Rust + // analogue: `clone` does not take an allocator.) self.linker.graph.ast = self.graph.ast.clone()?; for module_scope in self.linker.graph.ast.items_module_scope_mut() { @@ -3335,7 +3335,7 @@ pub mod bv2_impl { // "production build" part of Bake. let keys = named_exports_array[*source_id as usize].keys(); - // PORT NOTE: `G::Property: !Clone` — build via iterator instead of `vec![v; n]`. + // `G::Property: !Clone` — build via iterator instead of `vec![v; n]`. let mut client_manifest_items: Box<[G::Property]> = (0..keys.len()).map(|_| G::Property::default()).collect(); @@ -3539,7 +3539,7 @@ pub mod bv2_impl { side_effects: loader.side_effects(), ..Default::default() })?; - // PORT NOTE: `ParseTask::init` takes `bun_ast::Index`; both Index newtypes + // `ParseTask::init` takes `bun_ast::Index`; both Index newtypes // are `repr(transparent)` u32 so reconstruct via `.get()`. // Arena-owned (Zig: `arena.create(ParseTask)`); freed on heap reset. let task_val = ParseTask::init( @@ -3599,7 +3599,7 @@ pub mod bv2_impl { // matches Zig, which copies `source.*` by value and then reads the // still-intact original. let stored = &self.graph.input_files.items_source()[source_index.get() as usize]; - // PORT NOTE: Zig had a single `fs.Path`; Rust split it into + // Zig had a single `fs.Path`; Rust split it into // `bun_paths::fs::Path<'static>` (on `Source`) and `bun_resolver::fs::Path` // (on `ParseTask`). Convert field-by-field — `pretty`/`namespace` MUST // be preserved here (the SCB `separate_ssr_graph=false` caller passes a @@ -3686,7 +3686,7 @@ pub mod bv2_impl { let mut new_source = source_without_index; let source_index = self.graph.input_files.len(); new_source.index = bun_ast::Index(source_index as u32); - // PORT NOTE: `bun_ast::Source: !Clone` — manually dup the (all-Clone) fields. + // `bun_ast::Source: !Clone` — manually dup the (all-Clone) fields. let task_source = bun_ast::Source { path: new_source.path, contents: new_source.contents.clone(), @@ -3702,7 +3702,7 @@ pub mod bv2_impl { })?; let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: Zig aborts; port keeps fire-and-forget - // PORT NOTE: `bun.new(ServerComponentParseTask, …)` — heap-owned by the + // `bun.new(ServerComponentParseTask, …)` — heap-owned by the // worker pool; freed via `bun.destroy` in `on_complete` after the // result posts back to the bundle thread. let task = bun_core::heap::into_raw(Box::new(ServerComponentParseTask { @@ -4120,14 +4120,14 @@ pub mod bv2_impl { pub fn process_files_to_copy(&mut self, reachable_files: &[Index]) -> Result<(), Error> { if self.graph.estimated_file_loader_count > 0 { - // PORT NOTE: Zig per-file `arena` column dropped — Box owns its alloc. + // Zig per-file `arena` column dropped — Box owns its alloc. // SAFETY: MultiArrayList columns are disjoint backing storage; raw-ptr // sidestep so we can hold several read-only column slices, one mutable // column slice (`additional_files`), and call `transpiler_for_target` // (which needs `&mut self`) inside the loop. Zig accessed all of these // as raw `.items(.field)` slices with no borrow-checking. let self_ptr: *mut Self = self; - // SAFETY: see PORT NOTE above — disjoint MultiArrayList columns, + // SAFETY: see note above — disjoint MultiArrayList columns, // raw-ptr sidestep for split-borrow against `transpiler_for_target` // inside the loop. All six column derefs share the same invariant. let ( @@ -4365,7 +4365,7 @@ pub mod bv2_impl { load.source_index.get(), core::mem::discriminant(&load.value) ); - // PORT NOTE: `helpCatchMemoryIssues` was a mimalloc TLH probe; bumpalo has no equivalent. + // `helpCatchMemoryIssues` was a mimalloc TLH probe; bumpalo has no equivalent. let _ = FeatureFlags::HELP_CATCH_MEMORY_ISSUES; // `log_mut()` returns an unbounded `&mut Log` (backref to the // arena/DevServer-owned log) so the `&mut this.graph.*` reborrows @@ -4453,7 +4453,7 @@ pub mod bv2_impl { // watched files and dirs to their respective dependants. let fd = if bun_watcher::REQUIRES_FILE_DESCRIPTORS { let mut buf = bun_paths::path_buffer_pool::get(); - // PORT NOTE: Zig used `std.posix.toPosixPath` (copy + NUL- + // Zig used `std.posix.toPosixPath` (copy + NUL- // terminate); on kqueue platforms paths are already // posix-separated so `z()` alone suffices. match bun_sys::open( @@ -4560,7 +4560,7 @@ pub mod bv2_impl { core::mem::discriminant(&resolve.value) ); - // PORT NOTE: `helpCatchMemoryIssues` was a mimalloc TLH probe; bumpalo has no equivalent. + // `helpCatchMemoryIssues` was a mimalloc TLH probe; bumpalo has no equivalent. let _ = FeatureFlags::HELP_CATCH_MEMORY_ISSUES; match resolve.value.consume() { @@ -4691,7 +4691,7 @@ pub mod bv2_impl { resolve.import_record.original_target, ) .expect("oom"); - // PORT NOTE: `GetOrPutResult` has no `key_ptr` — `get_or_put` already + // `GetOrPutResult` has no `key_ptr` — `get_or_put` already // duped the key into the map (see PathToSourceIndexMap.rs). // We need to parse this @@ -4709,7 +4709,7 @@ pub mod bv2_impl { .input_files .append(crate::Graph::InputFile { source: bun_ast::Source { - // PORT NOTE: Zig assigned `path` (Fs.Path) directly; + // Zig assigned `path` (Fs.Path) directly; // shim to the field-identical `bun_paths::fs::Path<'static>`. path: path_as_static(&path), contents: std::borrow::Cow::Borrowed(&b""[..]), @@ -4775,7 +4775,7 @@ pub mod bv2_impl { } else { // SAFETY: map slot from `get_or_put` above; map not mutated since. out_source_index = Some(Index::init(unsafe { *value_ptr })); - // PORT NOTE: Zig freed result.{namespace,path} here; Rust drops below. + // Zig freed result.{namespace,path} here; Rust drops below. drop(result.namespace); drop(result.path); } @@ -5084,7 +5084,7 @@ pub mod bv2_impl { ) -> Result<(), Error> { if !outdir.is_empty() { // Open the output directory and write the metafile relative to it. - // PORT NOTE: Zig used `bun.FD.cwd().makeOpenPath()` + + // Zig used `bun.FD.cwd().makeOpenPath()` + // `NodeFS.writeFileWithPathBuffer`. Route through `bun_sys::File`. let mut buf = bun_paths::path_buffer_pool::get(); let joined = bun_paths::resolve_path::join_string_buf::< @@ -5169,8 +5169,8 @@ pub mod bv2_impl { Ok(ctx) } - // TODO(b0-genuine): body has deep DevServer field access (current_bundle.start_data, - // css_entry_points, etc.). After tier-6 collapse this fn should be HOISTED into + // The body has deep DevServer field access (current_bundle.start_data, + // css_entry_points, etc.). After tier-6 collapse this fn should be hoisted into // bun_runtime::bake (which can name DevServer concretely) and call back into BundleV2 // helpers. Until then the entry-point fields are reached through the vtable. pub fn finish_from_bake_dev_server( @@ -5205,7 +5205,7 @@ pub mod bv2_impl { let asts = self.graph.ast.slice(); let css_asts = asts.items_css(); - // PORT NOTE: SoA columns are physically disjoint slabs but rustc cannot + // SoA columns are physically disjoint slabs but rustc cannot // see that through `&Slice`. Route the two columns we mutate (`parts`, // `import_records`) through `split_raw()` (root-provenance `*mut [T]`, // no `&mut` intermediate) so the per-index `&mut` does not conflict @@ -5220,11 +5220,12 @@ pub mod bv2_impl { let input_files = self.graph.input_files.slice(); let loaders = input_files.items_loader(); let sources = input_files.items_source(); - // TODO(port): multi-zip iteration over MultiArrayList slices [1..] + // (Zig multi-zipped the MultiArrayList slices [1..]; index-based + // iteration over the raw columns is the Rust equivalent here.) for index in 1..self.graph.ast.len() { - // SAFETY: `index < ast.len()`; see PORT NOTE above for column aliasing. + // SAFETY: `index < ast.len()`; see note above for column aliasing. let part_list = unsafe { &mut *parts_col.add(index) }; - // SAFETY: `index < ast.len()`; see PORT NOTE above for column aliasing. + // SAFETY: `index < ast.len()`; see note above for column aliasing. let import_records = unsafe { &mut *import_records_col.add(index) }; let maybe_css = &css_asts[index]; let target = asts.items_target()[index]; @@ -5243,7 +5244,7 @@ pub mod bv2_impl { if LinkerContext::scan_css_imports( u32::try_from(index).expect("int cast"), import_records.as_slice(), - // PORT NOTE: `scan_css_imports` takes the column as a raw + // `scan_css_imports` takes the column as a raw // `*const` slice (the scanImportsAndExports caller holds raw // SoA pointers); it only reads via `is_none()`. Zig spec // (`LinkerContext.zig:496`) types this `[]const ?*...`. @@ -5344,7 +5345,6 @@ pub mod bv2_impl { } } - // TODO(port): leak js_files into arena — Zig returned .items // SAFETY: `alloc_slice_copy` returns into the bundler arena which outlives // this function. Erase the `&self` lifetime via `*const` so the borrow on // `self.arena()` does not extend across the `&mut self` calls below @@ -5411,7 +5411,6 @@ pub mod bv2_impl { } } self.linker.compute_data_for_source_map(js_reachable_files); - // TODO(port): errdefer { bun.outOfMemory() } — caller cannot recover /* arena: help_catch_memory_issues — no-op (mimalloc TLH check) */ @@ -5431,7 +5430,7 @@ pub mod bv2_impl { }; } - // PORT NOTE: `Chunk: !Default` (Vec fields). Allocate via Vec then + // `Chunk: !Default` (Vec fields). Allocate via Vec then // leak into the arena. let mut chunks: Vec = Vec::with_capacity(1 + start.css_entry_points.count() + html_files.count()); @@ -5504,7 +5503,6 @@ pub mod bv2_impl { chunks, ) .map_err(|_| AllocError)?; - // TODO(port): errdefer { bun.outOfMemory() } — caller cannot recover /* arena: help_catch_memory_issues — no-op (mimalloc TLH check) */ @@ -5529,7 +5527,7 @@ pub mod bv2_impl { original_target: options::Target, ) -> bool { if let Some(plugins) = self.plugins_ref() { - // PORT NOTE: `ImportRecord.path` is `bun_paths::fs::Path`; `has_any_matches` + // `ImportRecord.path` is `bun_paths::fs::Path`; `has_any_matches` // takes the structurally-identical `bun_resolver::fs::Path`. Rebuild the // resolver-crate variant from the same backing slices (Zig has a single // `Fs.Path` type — the FFI side only reads `.text` / `.namespace`). @@ -5711,7 +5709,7 @@ pub mod bv2_impl { self.graph.ast.ensure_unused_capacity(2)?; self.graph.input_files.ensure_unused_capacity(2)?; - // PORT NOTE: Zig copied `bake.server_virtual_source` by value. The Rust + // Zig copied `bake.server_virtual_source` by value. The Rust // statics are `LazyLock` and `Source` is not `Clone`, so rebuild // an owned `Source` from the static's clonable fields (`path`, `index`). let server_source = bun_ast::Source { @@ -5759,7 +5757,7 @@ pub mod bv2_impl { } // See barrel_imports.rs for barrel optimization implementation. - // PORT NOTE: Zig `pub usingnamespace`-style method aliases. `pub use` is not + // Zig `pub usingnamespace`-style method aliases. `pub use` is not // permitted in `impl` blocks; the underlying fns live in `barrel_imports` and // take `&mut BundleV2` directly — callers reach them as free functions. // (was: pub use barrel_imports::{apply_barrel_optimization, schedule_barrel_deferred_imports}) @@ -6012,7 +6010,7 @@ pub mod bv2_impl { continue; } - // PORT NOTE: borrowck — `transpiler_for_target` returns `&mut Transpiler` + // borrowck — `transpiler_for_target` returns `&mut Transpiler` // tied to `&mut self`, but the underlying storage is raw `*mut Transpiler` // backrefs valid for `'a` (see `init`). Compute the raw ptr first, then // deref once, so the `&mut self` borrow doesn't span the rest of the loop @@ -6063,7 +6061,7 @@ pub mod bv2_impl { ctx.target, ) }; - // SAFETY: see PORT NOTE above — raw `*mut Transpiler` lives for `'a`. + // SAFETY: see note above — raw `*mut Transpiler` lives for `'a`. let transpiler: &mut Transpiler<'a> = unsafe { &mut *transpiler_ptr }; // Check the FileMap first for in-memory files @@ -6146,7 +6144,7 @@ pub mod bv2_impl { ) { Ok(r) => break r, Err(err) => { - // PORT NOTE: borrowck — `log_for_resolution_failures` returns + // borrowck — `log_for_resolution_failures` returns // `&mut Log` tied to `&mut self`, but it's always a raw-ptr // deref (DevServer vtable or `transpiler.log`). Detach via // `*mut` so later `self.*` reads don't conflict. @@ -6313,13 +6311,13 @@ pub mod bv2_impl { continue; } - // PORT NOTE: borrowck — Zig `Result.path()` returns `?*Path` (raw), + // borrowck — Zig `Result.path()` returns `?*Path` (raw), // letting the loop body keep reading other `resolve_result` fields // (`.flags`, `.path_pair`, `.primary_side_effects_data`, `.jsx`). // The Rust port returns `Option<&mut Path>`, which would lock the // whole struct. Detach via raw ptr to mirror the Zig aliasing. let path: &mut Fs::Path = match resolve_result.path() { - // SAFETY: `resolve_result` outlives this borrow; see PORT NOTE above. + // SAFETY: `resolve_result` outlives this borrow; see note above. Some(p) => unsafe { bun_ptr::detach_lifetime_mut::(p) }, None => { import_record.path.is_disabled = true; @@ -6531,7 +6529,7 @@ pub mod bv2_impl { importer_source_index: IndexInt, ) -> i32 { let mut diff: i32 = 0; - // PORT NOTE: reshaped for borrowck — Zig freely aliased `graph` and the + // reshaped for borrowck — Zig freely aliased `graph` and the // path map across the loop body. Here we (a) capture a raw self ptr for // ParseTask.ctx, (b) hoist dev_server check, and (c) scope the map // borrow to the get_or_put so later `self.graph.*` writes don't overlap. @@ -6693,7 +6691,7 @@ pub mod bv2_impl { import_records: &mut import_record::List, ctx: PatchImportRecordsCtx, ) { - // PORT NOTE: Zig aliased `const graph = &this.graph;`. Borrowck rejects + // Zig aliased `const graph = &this.graph;`. Borrowck rejects // holding that across the `&mut self.graph.build_graphs[...]` borrow // below, so address the disjoint `self.graph.*` fields directly instead. let input_file_loaders = self.graph.input_files.items_loader(); @@ -6750,7 +6748,7 @@ pub mod bv2_impl { // 1. Create the ast right here // 2. Create a separate "virutal" module that becomes the manifest later on. // 3. Add it to the graph - // PORT NOTE: Zig aliased `graph = &this.graph;` — re-borrow `self.graph` + // Zig aliased `graph = &this.graph;` — re-borrow `self.graph` // at each use so the `self.*` method calls below don't conflict. let heap = self.graph.heap; let empty_html_file_source: &mut bun_ast::Source = self.arena_create(bun_ast::Source { @@ -6850,7 +6848,7 @@ pub mod bv2_impl { pub fn on_parse_task_complete(parse_result: &mut parse_task::Result, this: &mut BundleV2) { let _trace = crate::perf::trace("Bundler.onParseTaskComplete"); - // PORT NOTE: Zig aliased `const graph = &this.graph;`. Borrowck rejects + // Zig aliased `const graph = &this.graph;`. Borrowck rejects // holding that across the `this.*` method calls below (each takes // `&mut BundleV2`), so re-borrow `this.graph` at each use site instead. if parse_result.external.function.is_some() { @@ -6860,7 +6858,7 @@ pub mod bv2_impl { parse_task::ResultValue::Success(val) => val.source.index.0, }; let loader: Loader = this.graph.input_files.items_loader()[source as usize]; - // PORT NOTE: `InputFile.arena` column dropped in the Rust port; + // `InputFile.arena` column dropped in the Rust port; // stash the finalizer regardless so plugin-owned bytes are freed. let _ = loader; this.finalizers @@ -6868,10 +6866,9 @@ pub mod bv2_impl { } // defer bun.default_allocator.destroy(parse_result) — caller owns Box and drops at end - // TODO(port): parse_result is heap-allocated by worker; reconstruct heap::take at scope exit let mut diff: i32 = -1; - // PORT NOTE: Zig used `defer { graph.pending_items += diff; … }` — + // Zig used `defer { graph.pending_items += diff; … }` — // hoisted to tail position (see end of fn) so the closure doesn't // double-borrow `graph`/`this`. @@ -6894,14 +6891,14 @@ pub mod bv2_impl { parse_task::ResultValue::Err(data) => data.source_index.get(), parse_task::ResultValue::Success(val) => val.source.index.0, }; - // PORT NOTE: borrowck — read source path/loader before + // borrowck — read source path/loader before // `should_add_watcher(&self)` so the column borrow is released. let source_path = this.graph.input_files.items_source()[source_index as usize] .path .text; let loader = this.graph.input_files.items_loader()[source_index as usize]; if this.should_add_watcher(source_path) { - // PORT NOTE: const generic `CLONE_FILE_PATH = isWindows` + // const generic `CLONE_FILE_PATH = isWindows` // matches `cfg!(windows)` at compile time. let _ = this .bun_watcher_mut() @@ -6965,7 +6962,7 @@ pub mod bv2_impl { &mut result.source.contents, ); } - // PORT NOTE: Zig kept `source` as a stable pointer into the SoA. + // Zig kept `source` as a stable pointer into the SoA. // Borrowck forbids holding `&input_files.source[i]` while writing // other `input_files` columns through the MultiArrayList accessor // methods (each takes `&mut input_files`), so copy out the @@ -7052,7 +7049,7 @@ pub mod bv2_impl { // Set is_export_star_target for barrel optimization. // In dev server mode, source_index is not saved on JS import // records, so fall back to resolving via the path map. - // PORT NOTE: split-borrow `Graph` fields directly so the + // split-borrow `Graph` fields directly so the // `&build_graphs[target]` lookup doesn't lock out // `input_files.items_flags_mut()` (disjoint columns). let result_ast_target = result.ast.target; @@ -7074,7 +7071,7 @@ pub mod bv2_impl { } result.ast.import_records = import_records; - // PORT NOTE: Zig reads `result.ast.named_exports` / + // Zig reads `result.ast.named_exports` / // `result.source` *after* `graph.ast.set(…)` (Zig structs are // value types so the `set` is a shallow copy). The Rust port // moves `result.ast` into `graph.ast` and swapped `result.source` @@ -7134,7 +7131,7 @@ pub mod bv2_impl { .unwrap() .separate_ssr_graph; - // PORT NOTE: `result.source` was swapped into + // `result.source` was swapped into // `graph.input_files` earlier; re-borrow it from the SoA. // `.clone()` materializes the value-copy Zig got for free. let source_loader: Loader = @@ -7160,7 +7157,7 @@ pub mod bv2_impl { let mut ssr_source = this.graph.input_files.items_source()[result_source_index].clone(); - // PORT NOTE: `path_with_pretty_initialized` takes/returns + // `path_with_pretty_initialized` takes/returns // `Fs::Path` (`bun_resolver::fs::Path`); bridge through // `fs_path_from_logger`/`fs_path_to_logger` until the // three `Path` mirrors unify. @@ -7250,7 +7247,7 @@ pub mod bv2_impl { err.log .clone_to_with_recycled(this.transpiler.log_mut(), true); } else { - // PORT NOTE: Zig used `@tagName(err.step)`. + // Zig used `@tagName(err.step)`. let step_name = match err.step { crate::parse_task::Step::Pending => "pending", crate::parse_task::Step::ReadFile => "read_file", @@ -7753,7 +7750,7 @@ pub mod bv2_impl { } impl ExternalFreeFunctionAllocator { - // TODO(refactor): could implement `bun_alloc::Allocator` instead of the manual vtable. + // (Could implement `bun_alloc::Allocator` instead of the manual vtable.) fn free(ext_free_function: *mut c_void, _: &mut [u8], _: bun_alloc::Alignment, _: usize) { // SAFETY: ptr was created by ExternalFreeFunctionAllocator::create diff --git a/src/bundler/bundled_ast.rs b/src/bundler/bundled_ast.rs index 02a103c1daf1..fefb1b3e745a 100644 --- a/src/bundler/bundled_ast.rs +++ b/src/bundler/bundled_ast.rs @@ -39,7 +39,7 @@ pub(crate) type NamedExports = bun_ast::ast_result::NamedExports; pub(crate) type NamedImports = bun_ast::ast_result::NamedImports; pub type TopLevelSymbolToParts = bun_ast::ast_result::TopLevelSymbolToParts; -// PORT NOTE: Zig stores `MultiArrayList(BundledAst)` on `Graph.ast` / +// Zig stores `MultiArrayList(BundledAst)` on `Graph.ast` / // `LinkerGraph.ast` and the bundler indexes columns via `.items(.field)` // (see `linker_context/scanImportsAndExports.zig`, `LinkerContext.zig`). // `` generates the `BundledAstField` enum + @@ -59,7 +59,7 @@ pub struct BundledAst<'arena> { /// they can be manipulated efficiently without a full AST traversal pub import_records: import_record::List<'arena>, - // PORT NOTE: Ast.hashbang is `StoreStr`; mirror it here so init/to_ast can + // Ast.hashbang is `StoreStr`; mirror it here so init/to_ast can // round-trip. pub hashbang: StoreStr, pub parts: part::List<'arena>, @@ -69,7 +69,8 @@ pub struct BundledAst<'arena> { pub url_for_css: &'arena [u8], pub symbols: symbol::List<'arena>, pub module_scope: Scope, - // TODO(port): Zig used `= undefined`; only valid when flags.HAS_CHAR_FREQ is set. + // Zig used `= undefined`; only meaningful when flags.HAS_CHAR_FREQ is set + // (the Rust port zero-initializes instead of leaving it undefined). pub char_freq: CharFreq, pub exports_ref: Ref, pub module_ref: Ref, @@ -189,7 +190,7 @@ impl<'arena> BundledAst<'arena> { } } - // PORT NOTE: Zig's `*const BundledAst` bitwise-copies every field; the Rust + // Zig's `*const BundledAst` bitwise-copies every field; the Rust // collection types aren't Copy, so consume `self` to move them (toAST is a // one-shot conversion back to the fat Ast). pub fn to_ast(self) -> Ast<'arena> { @@ -370,7 +371,7 @@ impl<'arena> BundledAst<'arena> { let total_buffer_len = data_url_prefix_len + encode_len; // PERF(port): was arena alloc via `arena.alloc(u8, n)`; using bumpalo here. let encoded: &mut [u8] = bump.alloc_slice_fill_copy(total_buffer_len, 0u8); - // PORT NOTE: Zig's std.fmt.bufPrint with `{s}` writes raw bytes; BStr's Display + // Zig's std.fmt.bufPrint with `{s}` writes raw bytes; BStr's Display // would emit 3-byte U+FFFD for non-UTF-8 input and overflow the fixed prefix slice. encoded[..5].copy_from_slice(b"data:"); encoded[5..5 + mime_type.len()].copy_from_slice(mime_type); diff --git a/src/bundler/cache.rs b/src/bundler/cache.rs index a75ff3c8dd0a..c642fa18a8ef 100644 --- a/src/bundler/cache.rs +++ b/src/bundler/cache.rs @@ -62,7 +62,7 @@ impl RuntimeTranspilerCacheExt for RuntimeTranspilerCache { /// Mirrors `RuntimeTranspilerCache.Encoding` (RuntimeTranspilerCache.zig:405). /// -/// PORT NOTE: this is the on-disk wire enum for `Metadata.output_encoding` — +/// Note (port): this is the on-disk wire enum for `Metadata.output_encoding` — /// NOT `js_parser::ExportsKind` (an unrelated `#[repr(u8)]` enum that happens /// to start at 0). The bundler-side cache loader maps `Latin1`/`Utf16` blobs /// into a `bun.String` (RuntimeTranspilerCache.zig:310-318) and only feeds @@ -79,7 +79,7 @@ pub enum CacheEncoding { /// Mirrors `RuntimeTranspilerCache.ModuleType` (RuntimeTranspilerCache.zig:399). /// -/// PORT NOTE: NOT `options::ModuleType` — the on-disk wire enum has `Esm`/`Cjs` +/// Note (port): NOT `options::ModuleType` — the on-disk wire enum has `Esm`/`Cjs` /// **swapped** relative to the in-memory parser/options enum (`Unknown=0, /// Cjs=1, Esm=2`). Comparing `metadata.module_type` against /// `options::ModuleType::Cjs as u8` would test for `.esm`. @@ -156,7 +156,7 @@ pub struct Set { } impl Set { - /// Port of `Set.init` (cache.zig:6). PORT NOTE: `arena` is unused — + /// Port of `Set.init` (cache.zig:6). Note (port): `arena` is unused — /// `MutableString::init`/`JavaScript::init` source from the global heap in /// the Rust port; param kept so callers match the Zig signature /// (`crate::cache::Set::init(alloc)`). @@ -241,7 +241,7 @@ impl Fs { } } - // TODO(port): Zig `Fs.deinit` references `c.entries` which is not a field on `Fs` — + // Note (port): Zig `Fs.deinit` references `c.entries` which is not a field on `Fs` — // dead code (Zig lazy compilation never instantiated it). No Drop impl needed beyond // the auto-drop of `shared_buffer` / `macro_shared_buffer`. } @@ -325,7 +325,7 @@ impl Fs { /// Port of `Fs.readFileWithAllocator` (cache.zig:146). /// - /// PORT NOTE: `comptime use_shared_buffer` is taken at runtime — the live + /// Note (port): `comptime use_shared_buffer` is taken at runtime — the live /// callers (`ParseTask::get_code_for_parse_task_without_plugins`, /// `Transpiler::parse`) pass a value computed from runtime state, and the /// resolver's `FsCache` forward-decl already pinned this shape. @@ -352,7 +352,7 @@ impl Fs { ) -> Result { let rfs = &_fs.fs; - // PORT NOTE: reshaped — Zig declared `file_handle = undefined` then assigned on each + // Note (port): reshaped — Zig declared `file_handle = undefined` then assigned on each // branch; restructured into a single let-expression to avoid `mem::zeroed()` on a // type that may have niche (NonZero) fields. let mut _owned: Option = None; @@ -405,7 +405,7 @@ impl Fs { fd ); - // PORT NOTE: reshaped for borrowck — capture `stream` scalar before borrowing + // Note (port): reshaped for borrowck — capture `stream` scalar before borrowing // the shared buffer. let stream = self.stream; @@ -525,14 +525,15 @@ impl JavaScript { let result = match parser.parse() { Ok(r) => r, Err(err) => { - // PORT NOTE: `Parser::parse` consumes `self` (Zig took `*Parser` + // Note (port): `Parser::parse` consumes `self` (Zig took `*Parser` // — by-ref — but the Rust port owns the inner `P` by value), so // `parser` is gone in this arm. The `&'a mut temp_log` it held // is released, so read `temp_log.errors` directly. The lexer // range is lost; fall back to `Range::None` (Zig used // `parser.lexer.range()`). - // TODO(port): thread the failing token range through the - // `Err` payload once `_parse` returns a `(Error, Range)` pair. + // TODO(port): thread the failing token range through the `Err` + // payload (make `_parse` return a `(Error, Range)` pair) so the + // diagnostic points at the failing token like the Zig reference. if temp_log.errors == 0 { log.add_range_error(Some(source), bun_ast::Range::None, err.name().as_bytes()); } @@ -559,7 +560,7 @@ impl JavaScript { } let mut temp_log = bun_ast::Log::init(); - // PORT NOTE: reshaped for borrowck — Zig `defer temp_log.appendToMaybeRecycled(log, source)`; + // Note (port): reshaped for borrowck — Zig `defer temp_log.appendToMaybeRecycled(log, source)`; // scopeguard cannot capture &mut temp_log while it's used below. Explicit calls at each exit. let mut parser = match js_parser::Parser::init(opts, &mut temp_log, source, defines, bump) { diff --git a/src/bundler/defines.rs b/src/bundler/defines.rs index 125ec1ae1152..7a2d29c63aa3 100644 --- a/src/bundler/defines.rs +++ b/src/bundler/defines.rs @@ -130,7 +130,7 @@ pub fn copy_env_for_define( } } - // PORT NOTE: Zig pre-counted `key_buf_len`/`e_strings_to_allocate` to size two bump + // Zig pre-counted `key_buf_len`/`e_strings_to_allocate` to size two bump // allocations, then `iter.reset()` and re-walked. With per-entry copies the pre-sizing // pass is dead — emit directly. PERF(port): was single-buffer key arena; now per-entry Vec reuse. if behavior != DotEnvBehavior::Disable && behavior != DotEnvBehavior::LoadAllWithoutInlining { @@ -138,7 +138,7 @@ pub fn copy_env_for_define( debug_assert!(!prefix.is_empty()); } - // PORT NOTE: Zig's `if (key_buf_len > 0)` gate (env_loader.zig:455) is behavioral, + // Zig's `if (key_buf_len > 0)` gate (env_loader.zig:455) is behavioral, // not just a sizing optimization — when `behavior == .prefix` and NO env key starts // with `prefix`, the entire second walk (including the framework-hash `else` arm) // is skipped. Mirror that by pre-scanning for a prefix match before emitting. @@ -154,7 +154,7 @@ pub fn copy_env_for_define( if any_prefix_match { let mut key_buf: Vec = Vec::new(); - // PORT NOTE: borrowck — iterate parallel slices instead of `iterator()` so the + // borrowck — iterate parallel slices instead of `iterator()` so the // map borrow stays shared while we write into the define stores. let keys = env.map.map.keys(); let values = env.map.map.values(); @@ -232,7 +232,7 @@ impl DefineExt for Define { ) -> Result<(), bun_alloc::AllocError> { let key = global[global.len() - 1]; let parts: Vec> = global.iter().map(|p| Box::<[u8]>::from(*p)).collect(); - // PORT NOTE: reshaped for borrowck — getOrPut split into entry-style match. + // reshaped for borrowck — getOrPut split into entry-style match. if let Some(existing) = self.dots.get_mut(key) { let mut list: Vec = Vec::with_capacity(existing.len() + 1); // PERF(port): was appendSliceAssumeCapacity — profile if hot. @@ -413,7 +413,6 @@ impl DefineDataExt for DefineData { log: &mut bun_ast::Log, bump: &bun_alloc::Arena, ) -> Result { - // TODO(port): narrow error set let mut key_splitter = key.split(|b| *b == b'.'); while let Some(part) = key_splitter.next() { if !js_lexer::is_identifier(part) { @@ -465,7 +464,7 @@ impl DefineDataExt for DefineData { return Ok(DefineData { value, - // PORT NOTE: upstream `DefineData` now owns `original_name: + // upstream `DefineData` now owns `original_name: // Option>` (js_parser/lib.rs:1369) instead of the // borrowed `ptr`/`len` pair (Zig's 48→40-byte packing). Dupe // the value bytes — these are tiny startup-time copies. diff --git a/src/bundler/entry_points.rs b/src/bundler/entry_points.rs index d345e0d9d676..17c7d7af533f 100644 --- a/src/bundler/entry_points.rs +++ b/src/bundler/entry_points.rs @@ -10,7 +10,7 @@ use bun_wyhash::{self, Wyhash11}; use crate::Transpiler; use bun_js_parser as js_ast; -// PORT NOTE: `Path`/`PathName` come from the lower-tier `bun_paths::fs` shim +// `Path`/`PathName` come from the lower-tier `bun_paths::fs` shim // (lifetime-erased `'static` slices) so `bun_ast::Source` field types line up; // `FileSystem` is the real `bun_resolver::fs` singleton now that // `bun_resolver` is in this crate's dep set. @@ -39,17 +39,12 @@ impl Default for FallbackEntryPoint { } impl FallbackEntryPoint { - // TODO(port): crate::options::Framework / ClientCssInJs — `options` - // module is still gated; body also touched `bun_resolver::fs` (see - // PORTING.md §Forbidden) before un-gating. pub fn generate( entry: &mut FallbackEntryPoint, input_path: &[u8], transpiler: &mut TranspilerType, ) -> Result<(), bun_core::Error> - // TODO(port): narrow error set where - // TODO(port): TranspilerType trait bound — body reads `.options.framework` and `.arena`. TranspilerType: TranspilerLike, { // This is *extremely* naive. @@ -70,13 +65,13 @@ impl FallbackEntryPoint { .client_css_in_js != ClientCssInJs::AutoOnImportCss; - // PORT NOTE: self-referential — when the rendered code fits in + // self-referential — when the rendered code fits in // `entry.code_buffer` the Source borrows it (disjoint-field write to // `entry.source` while `entry.code_buffer` is shared-borrowed). On // overflow the Source owns the bytes via `Cow::Owned` (Zig allocated // from `transpiler.arena`; here the Source owns it directly so Drop // frees it). - // PORT NOTE: assemble bytes directly (not `write!`+`BStr`) so a + // assemble bytes directly (not `write!`+`BStr`) so a // non-UTF-8 byte in `input_path` is emitted verbatim like Zig `{s}`, // not lossily replaced with U+FFFD by `BStr as Display`. macro_rules! render_into_entry { @@ -138,7 +133,7 @@ impl ClientEntryPoint { strings::starts_with(b"entry.", extname) } - // PORT NOTE: takes the lifetime-generic `bun_paths::fs::PathName<'_>` (not the + // takes the lifetime-generic `bun_paths::fs::PathName<'_>` (not the // `'static`-field `bun_paths::fs::PathName<'static>`) so callers with a borrowed path // (e.g. `bun_runtime::filesystem_router::get_script_src_string`) needn't forge // `'static`. The body only copies `dir`/`base`/`ext` into `outbuffer`. @@ -151,7 +146,7 @@ impl ClientEntryPoint { let mut generated_path = Fs::FileSystem::get().abs_buf(&joined_base_and_dir_parts, outbuffer); - // PORT NOTE: reshaped for borrowck — capture len, drop borrow, re-borrow outbuffer. + // reshaped for borrowck — capture len, drop borrow, re-borrow outbuffer. let mut len = generated_path.len(); outbuffer[len..len + b".entry".len()].copy_from_slice(b".entry"); len += b".entry".len(); @@ -185,9 +180,7 @@ impl ClientEntryPoint { original_path: &Fs::PathName, client: &[u8], ) -> Result<(), bun_core::Error> - // TODO(port): narrow error set where - // TODO(port): TranspilerType trait bound — body reads `.options.framework`. TranspilerType: TranspilerLike, { let entry = self; @@ -210,8 +203,10 @@ impl ClientEntryPoint { .client_css_in_js != ClientCssInJs::AutoOnImportCss; - // TODO(port): self-referential — `code` borrows `entry.code_buffer` and is stored into - // `entry.source`. See note in FallbackEntryPoint::generate. + // INVARIANT: self-referential — `code` borrows `entry.code_buffer` and is + // stored into `entry.source` (lifetime erased), so `entry` must not move + // or drop while `entry.source` is in use. See note in + // FallbackEntryPoint::generate. let code: &[u8] = if disable_css_imports { let mut cursor = std::io::Cursor::new(&mut entry.code_buffer[..]); write!( @@ -279,7 +274,6 @@ impl ServerEntryPoint { is_hot_reload_enabled: bool, path_to_use: &[u8], ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set // Use the global arena so this buffer's lifetime is decoupled // from whichever arena the caller's VM happens to be using; the // slice is read later from `getHardcodedModule` which outlives any @@ -395,7 +389,7 @@ impl MacroEntryPoint { let hash = hasher.final_(); let fmt = bun_fmt::hex_int_lower::<16>(hash); - // PORT NOTE: reshaped for borrowck — capture cursor position, drop &mut + // reshaped for borrowck — capture cursor position, drop &mut // borrow, then re-borrow `buf` immutably. let n = { let mut cursor = std::io::Cursor::new(&mut buf[..]); @@ -419,8 +413,6 @@ impl MacroEntryPoint { (bun_wyhash::hash(specifier) as u32) as i32 } - // TODO(port): bun_ast::Macro + bun_resolver::fs::PathName — - // see `generate_id`. pub fn generate( entry: &mut MacroEntryPoint, _: &mut Transpiler, @@ -429,13 +421,12 @@ impl MacroEntryPoint { macro_id: i32, macro_label_: &[u8], ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let dir_to_use: &[u8] = if import_path.dir.is_empty() { b"" } else { import_path.dir_with_trailing_slash() }; - // PORT NOTE: reshaped for borrowck — capture the label length, write the + // reshaped for borrowck — capture the label length, write the // body via a scoped &mut borrow, then re-borrow `code_buffer` immutably // for the (label, code) slices passed to `init_path_string`. let label_len = macro_label_.len(); @@ -521,9 +512,10 @@ impl MacroEntryPoint { cursor.position() as usize }; - // TODO(port): self-referential — `macro_label`/`code` borrow `entry.code_buffer` - // and are stored into `entry.source` (lifetime erased via `IntoStr`); restructure - // so Source owns its bytes (or use a raw-ptr slice). + // INVARIANT: self-referential — `macro_label`/`code` borrow + // `entry.code_buffer` and are stored into `entry.source` (lifetime erased + // via `IntoStr`), so `entry` must not move or drop while `entry.source` + // is in use. let macro_label: &[u8] = &entry.code_buffer[..label_len]; let code: &[u8] = &entry.code_buffer[label_len..label_len + code_len]; entry.source = bun_ast::Source::init_path_string(macro_label, code); @@ -533,9 +525,9 @@ impl MacroEntryPoint { } } -// TODO(port): `TranspilerLike` is a placeholder for the duck-typed -// `comptime TranspilerType: type` param used by FallbackEntryPoint/ClientEntryPoint. -// Replace with the concrete `Transpiler` type or a real trait. +// Trait stand-in for the duck-typed `comptime TranspilerType: type` param Zig +// used for FallbackEntryPoint/ClientEntryPoint (Rust has no comptime duck +// typing). pub trait TranspilerLike { fn options(&self) -> &crate::options::Options<'_>; } diff --git a/src/bundler/lib.rs b/src/bundler/lib.rs index 49775a49070a..fe71ab7ea445 100644 --- a/src/bundler/lib.rs +++ b/src/bundler/lib.rs @@ -340,7 +340,7 @@ pub use bundle_v2::dispatch; // ── link-interfaces (must be at crate root so `$crate::__alias` resolves) ── // Re-exported through `bundle_v2::dispatch` for existing call sites. -// Erased handle to `bake::DevServer`. PORT NOTE: Zig takes +// Erased handle to `bake::DevServer`. The Zig original takes // `*const DevServerOutput` but mutates through the `chunks: []Chunk` slice it // holds; in Rust the struct stores `&'a mut [Chunk]`, hence `*mut`. bun_dispatch::link_interface! { diff --git a/src/bundler/linker.rs b/src/bundler/linker.rs index 20c38df88bfc..003a621abe7b 100644 --- a/src/bundler/linker.rs +++ b/src/bundler/linker.rs @@ -8,7 +8,7 @@ use bun_ast::Log; use bun_ast::{ImportKind, ImportRecord, ImportRecordFlags, ImportRecordTag}; use bun_collections::HashMap; use bun_paths::{self, SEP}; -// PORT NOTE: two `fs` shapes are in play here. `bun_resolver::fs` (`Fs`) holds +// two `fs` shapes are in play here. `bun_resolver::fs` (`Fs`) holds // the singleton `FileSystem` / `DirnameStore`; `bun_paths::fs` (`PFs`) defines // the `Path`/`PathName` value types that `ImportRecord.path` is typed against. // Both port `src/resolver/fs.zig`; B-3 collapses them. Until then, construct @@ -35,7 +35,7 @@ bun_core::named_error_set!(CSSResolveError); type HashedFileNameMap = HashMap; -// PORT NOTE: `_transpiler.Transpiler.isCacheEnabled` is gated in the draft body +// `_transpiler.Transpiler.isCacheEnabled` is gated in the draft body // (`transpiler.rs:1111`). The Zig value is a hard `false` (`const isCacheEnabled // = false;`); inline it here so `get_hashed_filename` compiles without depending // on the gated `Transpiler` impl. @@ -43,7 +43,7 @@ const IS_CACHE_ENABLED: bool = false; pub struct Linker { // arena field dropped — global mimalloc (callers pass `bun.default_allocator`) - // PORT NOTE: Zig stored borrowed `*BundleOptions` / `*Log` / `*Resolver` / + // Zig stored borrowed `*BundleOptions` / `*Log` / `*Resolver` / // `*ResolveQueue` / `*ResolveResults` / `*FileSystem`. The un-gated // `Transpiler` struct owns those values directly and also owns `linker: // crate::Linker` by value, so storing Rust references here would alias @@ -82,7 +82,7 @@ pub struct TaggedResolution { // // `bun_alloc::BSSStringList` encodes the Zig generics as // `COUNT = _COUNT * 2`, `ITEM_LENGTH = _ITEM_LENGTH + 1` (see `bun_alloc/lib.rs`). -// PORT NOTE: `bss_string_list!` would be the canonical declare-site macro but +// `bss_string_list!` would be the canonical declare-site macro but // expands to `core::cell::SyncUnsafeCell`, and `bun_bundler` does not (yet) // enable `#![feature(sync_unsafe_cell)]`. Use the heap-allocating `init()` // fallback under a `LazyLock` instead — same lifetime semantics @@ -325,7 +325,7 @@ impl Linker { } // ── getModKey / getHashedFilename ──────────────────────────────────── - // PORT NOTE: Zig's `Fs.FileSystem.RealFS.ModKey` is a nested decl; the + // Zig's `Fs.FileSystem.RealFS.ModKey` is a nested decl; the // Rust port hoists `ModKey` to module scope (`bun_resolver::fs::ModKey`) // alongside `RealFS`. `file_path` is typed `PFs::Path` (not `Fs::Path`) // so `get_hashed_filename` — whose callers all build `PFs::Path` — can @@ -352,7 +352,7 @@ impl Linker { }; let file = bun_sys::File::borrow(&raw_fd); Fs::FileSystem::set_max_fd(file.handle().native()); - // PORT NOTE: spec called `Fs.FileSystem.RealFS.ModKey.generate(&this.fs.fs, + // spec called `Fs.FileSystem.RealFS.ModKey.generate(&this.fs.fs, // path, file)`; both leading args are unread (fs.rs:1386). The inline // `bun_resolver::fs::RealFS` (which `self.fs.fs` is) and the full-port // `fs_full::RealFS` are distinct types, so route through the @@ -374,15 +374,25 @@ impl Linker { } let modkey = self.get_mod_key(file_path, fd)?; - // PORT NOTE: `ModKey::hash_name` writes into a 1 KiB threadlocal and - // returns a `'static` slice into it (matches Zig's `hash_name_buf` - // threadlocal). Spec passes `file_path.text` even though the param is - // named `basename`; preserved verbatim. - let hash_name = modkey.hash_name(file_path.text)?; + // `ModKey::hash_name` writes into a caller-supplied buffer (1 KiB, + // matching the capacity of the threadlocal Zig's `hash_name_buf` + // used) and returns a borrow of it; `dupe` copies the bytes into the + // process-lifetime interner to satisfy this fn's `'static` return. + // Note: `IS_CACHE_ENABLED` is a hard `const false` (see above), so + // the `hashed_filenames` cache never dedups — every call interns a + // fresh copy for the life of the process. Accepted: the `'static` + // return contract forces a copy anyway, and the alternative (the old + // threadlocal slice return) was unsound. `dupe` also aborts on OOM + // where the old path propagated `?` — consistent with the + // `bun.handleOom` idiom for interner allocations. + // Spec passes `file_path.text` even though the param is named + // `basename`; preserved verbatim. + let mut hash_name_buf = [0u8; 1024]; + let hash_name = dupe(modkey.hash_name(file_path.text, &mut hash_name_buf)?); if IS_CACHE_ENABLED { let hashed = bun_wyhash::hash(file_path.text); - self.hashed_filenames.insert(hashed, dupe(hash_name)); + self.hashed_filenames.insert(hashed, hash_name); } Ok(hash_name) @@ -391,7 +401,7 @@ impl Linker { /// This modifies the Ast in-place! It resolves import records and /// generates paths. /// - /// PORT NOTE: `comptime import_path_format` demoted to a runtime arg — + /// `comptime import_path_format` demoted to a runtime arg — /// `options::ImportPathFormat` doesn't derive `ConstParamTy`, and the /// crate doesn't enable `adt_const_params`. All callers pass a literal, /// and the inner `generate_import_path` body is a single `match` either @@ -423,7 +433,7 @@ impl Linker { | options::Loader::Js | options::Loader::Ts | options::Loader::Tsx => { - // PORT NOTE: reshaped for borrowck — Zig iterated + // reshaped for borrowck — Zig iterated // `result.ast.import_records.slice()` while also reading other // `result.*` fields and (in the not-found branch) borrowing // `&result.source`. Iterate by index, take field-disjoint @@ -573,7 +583,7 @@ impl Linker { Ok(()) } - // PORT NOTE: reshaped for borrowck — Zig passed `&mut self` + `&mut + // reshaped for borrowck — Zig passed `&mut self` + `&mut // ImportRecord` (a sub-borrow of `result.ast`) + `&mut ParseResult`. In // Rust those overlap; pass the disjoint pieces explicitly. fn when_module_not_found( @@ -660,7 +670,7 @@ impl Linker { } if namespace == b"bun" || namespace == b"file" || namespace.is_empty() { - // PORT NOTE: `linker.fs.relative` is a thin wrapper over + // `linker.fs.relative` is a thin wrapper over // `bun.path.relative`; the inline `bun_resolver::fs` // module doesn't expose it yet, so call the path layer // directly. The threadlocal-buffer result must be @@ -729,7 +739,7 @@ impl Linker { } } - // PORT NOTE: `fs.relativeTo(source_path)` == + // `fs.relativeTo(source_path)` == // `relative(fs.top_level_dir, source_path)` in Zig. let top_level_dir = self.fs().top_level_dir; let mut base: &[u8] = @@ -780,7 +790,7 @@ impl Linker { ) -> Result { let hash_key = self.resolve_result_hash_key(&resolve_result); - // PORT NOTE: Zig `getOrPut` → `HashMap::entry`; `found_existing` is + // Zig `getOrPut` → `HashMap::entry`; `found_existing` is // whether the key was already present. Matches Zig // `linker.resolve_results.getOrPut` / `linker.resolve_queue.writeItem` // (linker.zig:387-390). diff --git a/src/bundler/linker_context/MetafileBuilder.rs b/src/bundler/linker_context/MetafileBuilder.rs index f7ecff60a64d..29b34c9186dd 100644 --- a/src/bundler/linker_context/MetafileBuilder.rs +++ b/src/bundler/linker_context/MetafileBuilder.rs @@ -396,7 +396,7 @@ pub fn generate(c: &mut LinkerContext, chunks: &mut [Chunk]) -> Result first_output = false; j.push_static(b"\n "); - // PORT NOTE: Zig pushes a borrowed slice; push_static borrows for the + // `push_static` borrows for the // lifetime of the joiner (`chunk.metafile_chunk_json: Box<[u8]>` outlives `j`). j.push_static(&chunk.metafile_chunk_json); } @@ -422,7 +422,7 @@ pub fn generate(c: &mut LinkerContext, chunks: &mut [Chunk]) -> Result )?; // Get final output with all chunk references resolved. - // PORT NOTE: Zig passes `&chunks[0]` as the dummy chunk and `chunks` as the + // The Zig original passes `&chunks[0]` as the dummy chunk and `chunks` as the // full slice (aliased). `code()` takes both as `&` now, so pass `&chunks[0]` // directly — overlapping shared borrows are fine. let code_result = intermediate.code( @@ -451,7 +451,7 @@ fn write_json_string(writer: &mut impl Write, str: &[u8]) -> std::io::Result<()> // ────────────────────────────────────────────────────────────────────────── // Minimal `std.json.Value`-shaped tree for `generate_markdown`. // -// PORT NOTE: Zig's `generateMarkdown` re-parses the metafile JSON via +// Zig's `generateMarkdown` re-parses the metafile JSON via // `std.json.parseFromSlice(std.json.Value, …)` — a generic dynamic-tree parse. // The Rust crates available here (`bun_parsers::json`) only expose an // AST-expr parser, so a small self-contained Value/parser is provided below @@ -712,9 +712,7 @@ impl<'a> JsonParser<'a> { // ────────────────────────────────────────────────────────────────────────── // generate_markdown helper structs (local to the function in Zig; hoisted here) -// PORT NOTE: lifetime <'a> ties borrowed slices to the parsed JSON value's -// lifetime. The Zig originals were anonymous structs holding []const u8 that -// borrowed from the std.json parse arena. +// Lifetime <'a> ties borrowed slices to the parsed JSON value's lifetime. // ────────────────────────────────────────────────────────────────────────── struct InputFileInfo<'a> { diff --git a/src/bundler/linker_context/OutputFileListBuilder.rs b/src/bundler/linker_context/OutputFileListBuilder.rs index 6632f0ebfb55..7124d90ed630 100644 --- a/src/bundler/linker_context/OutputFileListBuilder.rs +++ b/src/bundler/linker_context/OutputFileListBuilder.rs @@ -195,8 +195,8 @@ impl OutputFileList { self.index_for_sourcemaps_and_bytecode.unwrap_or(0), self.additional_output_files_start, ); - // PORT NOTE: Zig did bitwise memcpy (ownership move). `OutputFile` is not - // `Clone`, so drain by value into the target window. + // Ownership move: `OutputFile` is not `Clone`, so drain by value into + // the target window. let len = additional_output_files.len(); let dest = self.get_mutable_additional_output_files(); for (i, of) in additional_output_files.drain(..).enumerate() { diff --git a/src/bundler/linker_context/StaticRouteVisitor.rs b/src/bundler/linker_context/StaticRouteVisitor.rs index d5a63f4238b0..2d4acb213ae6 100644 --- a/src/bundler/linker_context/StaticRouteVisitor.rs +++ b/src/bundler/linker_context/StaticRouteVisitor.rs @@ -16,10 +16,6 @@ pub struct StaticRouteVisitor<'a> { pub visited: AutoBitSet, } -// PORT NOTE: Zig `deinit` only freed `cache` and `visited` with the default -// arena. Both are now owned types with `Drop`, so no explicit `impl Drop` -// is needed. - impl<'a> StaticRouteVisitor<'a> { /// This the quickest, simplest, dumbest way I can think of doing this. /// Investigate performance. It can have false negatives (it doesn't properly @@ -33,7 +29,7 @@ impl<'a> StaticRouteVisitor<'a> { return false; } - // PORT NOTE: `self.c` is `&'a LinkerContext` (Copy), so these slice + // `self.c` is `&'a LinkerContext` (Copy), so these slice // borrows are tied to `'a`, not to `&self`, and do not conflict with // the `&mut self` call below. `parse_graph()` is the safe backref // accessor (one centralized `unsafe`, see `LinkerContext::parse_graph`). diff --git a/src/bundler/linker_context/computeChunks.rs b/src/bundler/linker_context/computeChunks.rs index 2079e74be18b..9bccb895aa29 100644 --- a/src/bundler/linker_context/computeChunks.rs +++ b/src/bundler/linker_context/computeChunks.rs @@ -31,7 +31,6 @@ fn make_flags(has_html_chunk: bool, is_browser_chunk_from_server_build: bool) -> f } -// TODO(port): narrow error set #[inline(never)] pub fn compute_chunks( this: &mut LinkerContext, @@ -46,7 +45,8 @@ pub fn compute_chunks( let arena = Arena::new(); let temp = &arena; - // TODO(port): StringArrayHashMap keyed by arena-allocated &[u8]; using ArrayHashMap<&[u8], Chunk> here. + // Keys borrow from `temp` (Zig used a StringArrayHashMap with arena-duped + // keys); the map and the arena are both dropped at end of fn. let mut js_chunks: ArrayHashMap<&[u8], Chunk> = ArrayHashMap::new(); js_chunks.reserve(this.graph.entry_points.len()); @@ -65,15 +65,15 @@ pub fn compute_chunks( let parse_graph = unsafe { &*this.parse_graph }; // `bump` is a `BackRef` into `BundleV2.graph.arena`, valid for the link step. // Hoisted so the loop can hold disjoint &mut borrows into `this.graph`. - // PORT NOTE: `BundlerStyleSheet::empty()` no longer takes an arena in Rust; kept for + // `BundlerStyleSheet::empty()` no longer takes an arena in Rust; kept for // when arena threading lands. let _arena: &Arena = this.graph.arena(); - // PORT NOTE: borrowck escape hatch — the SoA column slices below hold disjoint + // borrowck escape hatch — the SoA column slices below hold disjoint // immutable borrows into `this.graph` while several helpers (and the BundleV2 // back-pointer recovery) still want `&mut LinkerContext`. The Zig original - // freely aliases; TODO(refactor): thread split borrows through `LinkerGraph` - // instead of laundering through a raw pointer. + // freely aliases; split borrows could eventually be threaded through + // `LinkerGraph` instead of laundering through a raw pointer. let this_ptr: *mut LinkerContext = this; let entry_source_indices = this.graph.entry_points.items_source_index(); @@ -91,7 +91,7 @@ pub fn compute_chunks( for (entry_id_, &source_index) in entry_source_indices.iter().enumerate() { let entry_bit = entry_id_ as chunk::EntryPointId; // @truncate - // PORT NOTE: reshaped for borrowck — set the bit through a scoped &mut, then keep an + // reshaped for borrowck — set the bit through a scoped &mut, then keep an // owned clone so the `this.graph.files` borrow does not span the helper calls below // that need `&LinkerContext` / `&mut LinkerContext`. let entry_bits: AutoBitSet = { @@ -116,7 +116,7 @@ pub fn compute_chunks( .alloc_slice_copy(entry_point_chunk_bits.bytes(this.graph.entry_points.len())); } else { // Force HTML chunks to always be generated, even if there's an identical JS file. - // PORT NOTE: Zig used a Formatter struct; build the byte key directly since + // Zig used a Formatter struct; build the byte key directly since // entry_bits is arbitrary bytes (not UTF-8) and cannot go through fmt::Display. let mut v = bun_alloc::ArenaVec::new_in(temp); v.push((!has_html_chunk) as u8); @@ -145,7 +145,7 @@ pub fn compute_chunks( } if css_asts[source_index as usize].is_some() { - // SAFETY: see `this_ptr` PORT NOTE above — the helper only reads from + // SAFETY: see `this_ptr` note above — the helper only reads from // `this.graph` columns disjoint from the slices we hold here. let order = find_imported_files_in_css_order( unsafe { &mut *this_ptr }, @@ -221,7 +221,7 @@ pub fn compute_chunks( let css_source_indices = find_imported_css_files_in_js_order(this, temp, Index::init(source_index)); if css_source_indices.len() > 0 { - // SAFETY: see `this_ptr` PORT NOTE above. + // SAFETY: see `this_ptr` note above. let order = find_imported_files_in_css_order( unsafe { &mut *this_ptr }, temp, @@ -284,7 +284,7 @@ pub fn compute_chunks( } } } - // PORT NOTE: reshaped for borrowck — re-borrow file_entry_bits after the loop above mutated it + // reshaped for borrowck — re-borrow file_entry_bits after the loop above mutated it let file_entry_bits: &mut [AutoBitSet] = this.graph.files.items_entry_bits_mut(); let css_reprs = this.graph.ast.items_css(); @@ -350,7 +350,7 @@ pub fn compute_chunks( *entry.value_ptr = AtomicUsize::new(0); // Initialize byte count to 0 } } else { - // PORT NOTE: Zig used a local `Handler` struct passed to entry_bits.forEach; + // Zig used a local `Handler` struct passed to entry_bits.forEach; // in Rust we pass a context struct + fn pointer. struct Handler<'a> { chunks: &'a mut [Chunk], @@ -449,7 +449,7 @@ pub fn compute_chunks( } // PERF(port): was assume_capacity - // PORT NOTE: `Chunk` is not `Clone` (Zig is move-by-value); take by index. + // `Chunk` is not `Clone` (Zig is move-by-value); take by index. let idx = js_chunks.get_index(&key).expect("unreachable"); let owned = core::mem::take(&mut js_chunks.values_mut()[idx]); let has_html = owned.flags.contains(chunk::Flags::HAS_HTML_CHUNK); @@ -503,8 +503,6 @@ pub fn compute_chunks( } break 'sort_chunks sorted_chunks; - // TODO(port): return type — Zig returns []Chunk allocated by this.arena(); here we return Box<[Chunk]>. - // Confirm ownership of `chunks` slice (sorted_chunks Vec backing storage). }; let chunks: &mut [Chunk] = sorted_chunks.slice_mut(); @@ -540,7 +538,7 @@ pub fn compute_chunks( let unique_key_item_len = chunk::UNIQUE_KEY_LEN; let mut unique_key_builder = bun_core::StringBuilder::init_capacity(unique_key_item_len * chunks.len()); - // PORT NOTE: in Zig `unique_key_buf` aliases the builder's backing buffer and + // in Zig `unique_key_buf` aliases the builder's backing buffer and // every `chunk.unique_key` is a slice into it. Mirror that: the builder never // reallocates after `init_capacity`, so each `fmt()` returns a stable subslice // that we detach to `&'static [u8]` (BACKREF) and transfer ownership of the @@ -553,7 +551,7 @@ pub fn compute_chunks( let bv2: &mut BundleV2 = unsafe { &mut *LinkerContext::bundle_v2_ptr(this_ptr) }; let kinds = this.graph.files.items_entry_point_kind(); let output_paths = this.graph.entry_points.items_output_path(); - // PORT NOTE: re-borrow after `find_all_imported_parts_in_js_order` released `&mut this`. + // re-borrow after `find_all_imported_parts_in_js_order` released `&mut this`. let ast_targets = this.graph.ast.items_target(); for (chunk_id, chunk) in chunks.iter_mut().enumerate() { // Assign a unique key to each chunk. This key encodes the index directly so @@ -697,9 +695,9 @@ pub fn compute_chunks( // is only assigned here on success, so no rollback guard is needed.) this.unique_key_buf = unique_key_builder.move_to_slice(); + // Zig returned `[]Chunk` allocated by `this.arena()`; the caller-owned + // `Box<[Chunk]>` here transfers the `sorted_chunks` Vec backing storage. Ok(sorted_chunks.to_owned_slice()) - // TODO(port): return type — Zig returns []Chunk allocated by this.arena(); here we return Box<[Chunk]>. - // Confirm ownership of `chunks` slice (sorted_chunks Vec backing storage). } pub use crate::DeferredBatchTask; diff --git a/src/bundler/linker_context/computeCrossChunkDependencies.rs b/src/bundler/linker_context/computeCrossChunkDependencies.rs index 5139e28de46e..d6cd74135fd9 100644 --- a/src/bundler/linker_context/computeCrossChunkDependencies.rs +++ b/src/bundler/linker_context/computeCrossChunkDependencies.rs @@ -31,7 +31,7 @@ pub fn compute_cross_chunk_dependencies( // defer { meta.*.deinit(); free(chunk_metas) } — handled by Drop { - // PORT NOTE: Zig heap-allocated this via c.arena().create() and destroyed it at + // Zig heap-allocated this via c.arena().create() and destroyed it at // scope end; in Rust we construct on the stack and let it drop. // // `ctx` / `symbols` / `chunks` are stored as raw pointers so the struct does not @@ -47,7 +47,7 @@ pub fn compute_cross_chunk_dependencies( // Lifetime-erase the `LinkerContext<'_>` so the struct's `'a` (which // ties only the local SoA-column borrows) is not forced to equal the // LinkerContext's invariant `'_`. `NonNull::from(&mut *c)` preserves - // `c`'s Unique provenance (see PORT NOTE above). + // `c`'s Unique provenance (see note above). let ctx_ref = bun_ptr::BackRef::from( core::ptr::NonNull::from(&mut *c).cast::>(), ); @@ -86,7 +86,7 @@ pub fn compute_cross_chunk_dependencies( pub(crate) struct CrossChunkDependencies<'a, 'bump> { chunk_meta: &'a mut [ChunkMeta], - // PORT NOTE: `BackRef` — the same `[Chunk]` slice is also iterated mutably by + // `BackRef` — the same `[Chunk]` slice is also iterated mutably by // the caller's sequential `walk` loop; `walk` only reads `chunks[other].unique_key` // (disjoint from the per-iteration `&mut Chunk`). The slice outlives the struct // (caller stack frame). @@ -100,7 +100,7 @@ pub(crate) struct CrossChunkDependencies<'a, 'bump> { exports_refs: &'a [Ref], sorted_and_filtered_export_aliases: &'a [js_meta::SortedAndFilteredExportAliases], resolved_exports: &'a [ResolvedExports], - // PORT NOTE: `BackRef` — Zig stores `*LinkerContext` / `*Symbol.Map` and freely + // `BackRef` — Zig stores `*LinkerContext` / `*Symbol.Map` and freely // aliases `c.graph` columns alongside; borrowck cannot express that split, so // opt out here via `BackRef` (safe `Deref` at each use site in `walk`). Lifetime // erased (`'static`) so the outer `CrossChunkDependencies<'_>` borrow is not tied @@ -128,7 +128,7 @@ impl<'a, 'bump> CrossChunkDependencies<'a, 'bump> { pub(crate) fn walk(&mut self, chunk: &mut Chunk, chunk_index: usize) { let deps = self; // `ctx` / `chunks` are `BackRef`s into `LinkerContext` / the caller's chunk - // slice, valid for the link pass (see PORT NOTE on the struct fields). + // slice, valid for the link pass (see note on the struct fields). // `chunks` aliases the slice the caller iterates mutably but is only read here. let ctx: &LinkerContext<'_> = deps.ctx.get(); // `BackRef` into `LinkerContext.graph.symbols`, valid for the link @@ -137,7 +137,7 @@ impl<'a, 'bump> CrossChunkDependencies<'a, 'bump> { let symbols: &bun_ast::symbol::Map = deps.symbols.get(); let _chunks: &[Chunk] = deps.chunks.get(); let chunk_meta = &mut deps.chunk_meta[chunk_index]; - // PORT NOTE: reshaped for borrowck — Zig held `&chunk_meta` and `&chunk_meta.imports` + // reshaped for borrowck — Zig held `&chunk_meta` and `&chunk_meta.imports` // simultaneously; here we go through `chunk_meta.imports` / `chunk_meta.dynamic_imports`. let entry_point_chunk_indices = deps.entry_point_chunk_indices; @@ -172,9 +172,11 @@ impl<'a, 'bump> CrossChunkDependencies<'a, 'bump> { { let other_chunk_index = entry_point_chunk_indices[import_record.source_index.get() as usize]; + // Slice copy (fat pointer) — same as Zig's by-pointer + // assignment: `path.text` borrows the chunk's + // `unique_key` backing buffer (`LinkerContext.unique_key_buf`), + // which outlives the link pass. import_record.path.text = _chunks[other_chunk_index as usize].unique_key; - // TODO(port): Zig assigns the slice by pointer (no copy); decide - // ownership of `path.text` vs `unique_key`. import_record.source_index = Index::INVALID; // Track this cross-chunk dynamic import so we make sure to @@ -326,7 +328,7 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( chunk_metas: &mut [ChunkMeta], ) -> Result<(), bun_alloc::AllocError> { // Mark imported symbols as exported in the chunk from which they are declared - // PORT NOTE: reshaped for borrowck — Zig zips (chunks, chunk_metas, 0..) and also indexes + // reshaped for borrowck — Zig zips (chunks, chunk_metas, 0..) and also indexes // chunk_metas[other_chunk_index] / chunks[other_chunk_index] inside the loop body. We // iterate by index and re-borrow per access. debug_assert_eq!(chunks.len(), chunk_metas.len()); @@ -336,7 +338,7 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( } // Find all uses in this chunk of symbols from other chunks - // PORT NOTE: reshaped for borrowck — collect keys first to avoid holding a borrow on + // reshaped for borrowck — collect keys first to avoid holding a borrow on // chunk_metas[chunk_index] while mutating chunk_metas[other_chunk_index]. let import_refs: Vec = chunk_metas[chunk_index].imports.keys().to_vec(); // PERF(port): was direct iteration over .keys() without copy — profile if it shows up on a hot path @@ -507,7 +509,7 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( original_name: bun_ast::StoreStr::new(b"" as &[u8]), }); - // `alias` points into the link-pass arena (see PORT NOTE above), + // `alias` points into the link-pass arena (see note above), // which outlives `exports_to_other_chunks`; `.slice()` re-borrows // under the StoreStr arena contract. let _ = repr.exports_to_other_chunks.put(ref_, alias.slice()); // OOM-only Result (Zig: catch unreachable) @@ -544,7 +546,7 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( debug!("Generating cross-chunk imports"); let mut list: Vec = Vec::new(); // defer list.deinit() — handled by Drop - // PORT NOTE: reshaped for borrowck — Zig's `for (chunks) |*chunk|` aliases the same + // reshaped for borrowck — Zig's `for (chunks) |*chunk|` aliases the same // slice it passes to `sortedCrossChunkImports`. We move the per-chunk fields we // mutate (`imports_from_other_chunks`, `cross_chunk_imports`) out via `take`, drop // the `chunk` borrow, hand the whole `chunks` slice to `sorted_cross_chunk_imports` @@ -562,7 +564,7 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( ); let mut cross_chunk_imports = core::mem::take(&mut chunks[chunk_index].cross_chunk_imports); - // PORT NOTE: reshaped for borrowck — Zig copies the Vec by value, mutates, + // reshaped for borrowck — Zig copies the Vec by value, mutates, // then writes back; we `take` to express the same move-out/move-in. let mut cross_chunk_prefix_stmts = Vec::::default(); diff --git a/src/bundler/linker_context/convertStmtsForChunk.rs b/src/bundler/linker_context/convertStmtsForChunk.rs index 7b7dbf328bbe..182a257f28ab 100644 --- a/src/bundler/linker_context/convertStmtsForChunk.rs +++ b/src/bundler/linker_context/convertStmtsForChunk.rs @@ -196,7 +196,11 @@ pub fn convert_stmts_for_chunk( )); if let Some(mod_) = module_exports_for_export { - // TODO(port): Zig writes args[3] which is out-of-bounds (len is 3); preserved as args[2] — verify intent. + // The Zig source wrote `args[3]`, out of bounds + // for the len-3 slice it allocated; the intent — + // per the "__reExport(exports, ns, module.exports)" + // call shape above — is the third argument, so + // append at index 2. args.push(mod_); } diff --git a/src/bundler/linker_context/convertStmtsForChunkForDevServer.rs b/src/bundler/linker_context/convertStmtsForChunkForDevServer.rs index ae25173e4900..e9f5e505cf83 100644 --- a/src/bundler/linker_context/convertStmtsForChunkForDevServer.rs +++ b/src/bundler/linker_context/convertStmtsForChunkForDevServer.rs @@ -276,7 +276,7 @@ pub fn convert_stmts_for_chunk_for_dev_server<'bump>( Loc::EMPTY, ))?; // hmr.onUpdate = [ ... ]; - // PORT NOTE: reshaped for borrowck — capture len before moving esm_callbacks + // Capture len before moving `esm_callbacks` (borrowck). let callbacks_len = esm_callbacks.len(); stmts .inside_wrapper_prefix diff --git a/src/bundler/linker_context/doStep5.rs b/src/bundler/linker_context/doStep5.rs index 1742f67874c0..31b8a3af874b 100644 --- a/src/bundler/linker_context/doStep5.rs +++ b/src/bundler/linker_context/doStep5.rs @@ -1,6 +1,6 @@ //! Port of `src/bundler/linker_context/doStep5.zig`. //! -//! PORT NOTE: like `scanImportsAndExports.rs`, the Zig body holds many +//! Like `scanImportsAndExports.rs`, the Zig body holds many //! overlapping `&mut` column slices out of `LinkerGraph.{ast,meta}` while also //! calling `&self` methods. The SoA columns are physically disjoint and never //! reallocate during this step, so we cache raw column pointers and deref at @@ -203,7 +203,7 @@ impl LinkerContext<'_> { // Each part tracks the other parts it depends on within this file let mut local_dependencies: HashMap = HashMap::default(); - // PORT NOTE: reshaped for borrowck — multiple `&mut` into graph SoA; + // Reshaped from the Zig for borrowck — multiple `&mut` into graph SoA; // raw per-row pointers via `split_raw()` so concurrent tasks never // hold overlapping `&mut [T]`. let parts_slice: *mut [Part] = row_mut!(ast.parts, bun_ast::PartList, id).as_mut_slice(); @@ -249,7 +249,7 @@ impl LinkerContext<'_> { // Now that all files have been parsed, determine which property // accesses off of imported symbols are inlined enum values and // which ones aren't - // PORT NOTE: reshaped for borrowck — Zig iterates keys()/values() while + // Reshaped from the Zig for borrowck — Zig iterates keys()/values() while // holding a mutable getPtr into part.symbol_uses; collect refs first. // PERF(port): the property-use map is empty for the overwhelming // majority of parts (it only fills for `import * as ns`/enum @@ -404,7 +404,7 @@ impl LinkerContext<'_> { /// WARNING: This method is run in parallel over all files. Do not mutate data /// for other files within this method or you will create a data race. /// - /// PORT NOTE: takes `&self` (read-only) plus the three SoA row cells it + /// Takes `&self` (read-only) plus the three SoA row cells it /// mutates as explicit `&mut` params, so the parallel `do_step5` dispatch /// never forms a concurrent `&mut LinkerContext` / whole-column `&mut [T]`. #[allow(clippy::too_many_arguments)] @@ -420,10 +420,17 @@ impl LinkerContext<'_> { ast_flags: &mut AstFlags, ast_parts: &mut bun_ast::PartList, ) { - // PORT NOTE: Zig toggled `Stmt.Disabler`/`Expr.Disabler` (debug-only - // re-entrancy guards around the global Store). `Disabler::scope()` - // calls `disable()` and re-`enable()`s on drop — currently no-op stubs - // until the thread-local toggle lands (`js_parser/ast/mod.rs`). + // Zig toggled `Stmt.Disabler`/`Expr.Disabler` (debug-only guards + // around the global thread-local block store). `Disabler::scope()` + // calls `disable()` and re-`enable()`s on drop. In debug builds the + // disabler only fires when `Store::append` falls through to that + // global slab — i.e. when no `ASTMemoryAllocator` scope is installed. + // Bundler workers always install one (`Worker::get` pushes + // `ast_memory_store`), so appends in this step — including + // `Stmt::assign` below — route to the worker allocator and bypass the + // check entirely, exactly as in Zig (expr.zig:3189-3195). The guard + // exists to catch accidental global-slab use if this code ever runs + // without that allocator installed. let _stmt_guard = bun_ast::stmt::Disabler::scope(); let _expr_guard = bun_ast::expr::Disabler::scope(); @@ -451,7 +458,7 @@ impl LinkerContext<'_> { // + 1 if we need to do module.exports = __toCommonJS(exports) force_include_exports_for_entry_point as usize; - // PORT NOTE: Zig used `Stmt.Batcher` (preallocated arena slice + + // Zig used `Stmt.Batcher` (preallocated arena slice + // cursor). `Batcher::::init` requires `T: Default` which `Stmt` // doesn't satisfy, so we hand-roll the same shape: one arena slab of // `stmts_count` `MaybeUninit`, sliced front-to-back. `eat1` @@ -562,7 +569,7 @@ impl LinkerContext<'_> { let all_export_stmts_len = needs_exports_variable as usize + (!properties.is_empty()) as usize + force_include_exports_for_entry_point as usize; - // PORT NOTE: the trailing `all_export_stmts_len` slots of `stmts_slab` + // The trailing `all_export_stmts_len` slots of `stmts_slab` // (after the per-export `eat1`s above) are filled below in the order // {var exports={}, __export(...), module.exports=__toCommonJS(...)}. let all_export_stmts_base = stmts_head; @@ -602,7 +609,7 @@ impl LinkerContext<'_> { let mut export_ref = Ref::NONE; if !properties.is_empty() { export_ref = self.runtime_function(b"__export"); - // PORT NOTE: `bumpalo::Vec` → `Vec` via the global heap; + // `bumpalo::Vec` → `Vec` via the global heap; // `G::PropertyList` is `Vec` and currently has no // arena-backed `move_from_list`, so re-own. PERF(port). let mut owned_props: Vec = Vec::with_capacity(properties.len()); diff --git a/src/bundler/linker_context/findAllImportedPartsInJSOrder.rs b/src/bundler/linker_context/findAllImportedPartsInJSOrder.rs index 37e2a9747444..716f13df56ae 100644 --- a/src/bundler/linker_context/findAllImportedPartsInJSOrder.rs +++ b/src/bundler/linker_context/findAllImportedPartsInJSOrder.rs @@ -66,11 +66,11 @@ pub fn find_imported_parts_in_js_order( part_ranges_shared.clear(); parts_prefix_shared.clear(); - // PORT NOTE: reshaped for borrowck — capture before constructing visitor + // Capture before constructing the visitor (borrowck). let with_code_splitting = this.graph.code_splitting; let with_scb = this.graph.is_scb_bitset.bit_length > 0; - // PORT NOTE: the Zig visitor holds a *LinkerContext alongside SoA column slices + // The Zig visitor holds a *LinkerContext alongside SoA column slices // borrowed from it, and mutates one column (`entry_point_chunk_index`). Rust // borrowck forbids the latter through a shared `&LinkerContext`, so cache that // single mutable column as a raw `*mut [u32]` (provenance via the @@ -121,7 +121,6 @@ pub fn find_imported_parts_in_js_order( (visitor.files, parts_in_chunk_order) }; - // PORT NOTE: `chunk.content.javascript` union field access → enum match. match &mut chunk.content { chunk::Content::Javascript(js) => { js.files_in_chunk_order = files_in_chunk_order.into_boxed_slice(); @@ -158,7 +157,7 @@ pub struct FindImportedPartsVisitor<'a, 'ctx> { pub entry_point: EntryPoint, pub chunk_index: u32, /// Raw column pointer into `c.graph.files` for the single mutable write in - /// `visit` (see PORT NOTE above). + /// `visit` (see the raw-pointer note above). entry_point_chunk_indices: *mut [u32], } diff --git a/src/bundler/linker_context/findImportedFilesInCSSOrder.rs b/src/bundler/linker_context/findImportedFilesInCSSOrder.rs index f196118950b0..6c09ccb9bcbf 100644 --- a/src/bundler/linker_context/findImportedFilesInCSSOrder.rs +++ b/src/bundler/linker_context/findImportedFilesInCSSOrder.rs @@ -14,7 +14,7 @@ use crate::linker_context_mod::debug; use crate::{Index, LinkerContext}; use bun_ast::Index as AstIndex; -// PORT NOTE: Zig `entry.*` / `@memcpy` are bitwise copies of arena-backed +// Zig's `entry.*` / `@memcpy` are bitwise copies of arena-backed // `CssImportOrder` values (the inner `Vec`s point into bump arenas and are // never individually freed). Rust's `Vec` has `Drop`, so a literal `*entry` // is not `Copy`. We replicate the Zig bitwise-copy semantics here. @@ -28,7 +28,7 @@ use bun_ast::Index as AstIndex; unsafe fn bitwise_copy(src: &T) -> T { // SAFETY: `src` is a valid aligned `&T`; the `unsafe fn` contract requires // the caller to ensure the duplicated value's `Drop` is suppressed (arena - // ownership, see PORT NOTE above) so the aliased buffer is never freed twice. + // ownership, see the bitwise-copy note above) so the aliased buffer is never freed twice. unsafe { core::ptr::read(src) } } @@ -38,7 +38,7 @@ unsafe fn bitwise_copy(src: &T) -> T { #[inline] fn memcpy_and_reset(order: &mut Vec, wip: &mut Vec) { debug_assert!(order.capacity() >= wip.len()); - // PORT NOTE: do not Drop `order`'s prior entries — they were already + // Do not Drop `order`'s prior entries — they were already // bitwise-copied into `wip` (see `bitwise_copy` callers above), so dropping // here would double-free their `conditions` buffers. // SAFETY: `set_len(0)` is unconditionally sound (0 ≤ capacity; shrinking @@ -85,7 +85,7 @@ pub fn find_imported_files_in_css_order<'a>( css_asts: &'a [crate::bundled_ast::CssCol], all_import_records: &'a [bun_ast::import_record::List<'a>], - // PORT NOTE: Zig's `graph: *LinkerGraph` is never read in `visit()`; + // Zig's `graph: *LinkerGraph` is never read in `visit()`; // dropped here to avoid an aliasing `&mut this.graph` borrow against // `arena`/`css_asts` (which already borrow `this.graph`). // `BackRef` (not `&'a Graph`) so the visitor's `'a` borrow stays @@ -156,7 +156,7 @@ pub fn find_imported_files_in_css_order<'a>( // }) // } - // PORT NOTE: `defer { _ = visitor.visited.pop(); }` — explicit pop at end. + // Zig: `defer { _ = visitor.visited.pop(); }` — explicit pop at end. // The defer is registered AFTER the `orelse return` above, so skipping the // pop on that early-return path matches the original semantics. @@ -237,9 +237,9 @@ pub fn find_imported_files_in_css_order<'a>( } else { self.order.push(CssImportOrder { kind: CssImportOrderKind::ExternalPath(record.path), - // PORT NOTE: Zig `wrapping_conditions.*` is a bitwise struct copy. + // Zig's `wrapping_conditions.*` is a bitwise struct copy. // SAFETY: arena-backed `Vec` header; the pushed - // `CssImportOrder` never drops it (see PORT NOTE at + // `CssImportOrder` never drops it (see the note at // `bitwise_copy`), so the aliased buffer is freed once // with the arena. conditions: unsafe { bitwise_copy(wrapping_conditions) }, @@ -284,25 +284,25 @@ pub fn find_imported_files_in_css_order<'a>( } // Accumulate imports in depth-first postorder self.order.push(CssImportOrder { - // PORT NOTE: `crate::Index` (= `bun_ast::Index`) and the + // `crate::Index` (= `bun_ast::Index`) and the // `bun_ast::Index` carried by `CssImportOrderKind::SourceIndex` // are TYPE_ONLY mirrors of the same Zig `bun.ast.Index`; // both are `#[repr(transparent)]` over `u32`. kind: CssImportOrderKind::SourceIndex(AstIndex(source_index.get())), - // PORT NOTE: Zig `wrapping_conditions.*` is a bitwise struct copy. + // Zig's `wrapping_conditions.*` is a bitwise struct copy. // SAFETY: arena-backed `Vec` header; `CssImportOrder` suppresses - // `Drop` on it (see PORT NOTE at `bitwise_copy`), so the aliased + // `Drop` on it (see the note at `bitwise_copy`), so the aliased // buffer is freed once with the arena. conditions: unsafe { bitwise_copy(wrapping_conditions) }, condition_import_records: Vec::new(), }); - // PORT NOTE: explicit pop replacing `defer { _ = visitor.visited.pop(); }` + // Explicit pop replacing Zig's `defer { _ = visitor.visited.pop(); }`. let _ = self.visited.pop(); } } - // PORT NOTE: reshaped for borrowck — read MultiArrayList columns before constructing visitor. + // Read MultiArrayList columns before constructing the visitor (borrowck). let css_asts_slice: &[crate::bundled_ast::CssCol] = this.graph.ast.items_css(); let all_import_records_slice = this.graph.ast.items_import_records(); let arena = this.graph.arena(); @@ -387,7 +387,7 @@ pub fn find_imported_files_in_css_order<'a>( let mut external_path_duplicates: StringArrayHashMap> = StringArrayHashMap::new(); let mut i: u32 = order.len() as u32; - // PORT NOTE: reshaped for borrowck — `order.at(i)` and `order.mut_(i)` + // Borrowck: `order.at(i)` and `order.mut_(i)` // cannot overlap, and `is_conditional_import_redundant` needs to read // both `entry.conditions` and `order.at(j).conditions`. Hold raw // pointers into the Vec buffer (Zig used the same pattern via @@ -413,7 +413,7 @@ pub fn find_imported_files_in_css_order<'a>( // So we should keep the @layer rules so that the cascade ordering of layers // is preserved // - // PORT NOTE: `crate::bun_css::LayerName` (lifetime-erased + // `crate::bun_css::LayerName` (lifetime-erased // shadow) and `::bun_css::LayerName` are distinct nominal // types until the ungate shadow is removed; cast through // `NonNull` to satisfy `Layers::borrow`. @@ -458,7 +458,7 @@ pub fn find_imported_files_in_css_order<'a>( // copy instead of the last copy like other things in CSS. { struct DuplicateEntry { - // PORT NOTE: lifetime-erased slice header — borrows either + // Lifetime-erased slice header — borrows either // `css_asts[..].layer_names` (real `::bun_css::LayerName`) or // `Layers::inner()` (shadow `LayerName`). Both nominal types should // be reconciled; until then we compare via @@ -564,7 +564,7 @@ pub fn find_imported_files_in_css_order<'a>( // layers_post_import let layers_key: *const [LayerName] = match &entry.kind { CssImportOrderKind::SourceIndex(idx) => { - // PORT NOTE: see LayerName nominal-type note above. + // See the LayerName nominal-type note above. std::ptr::from_ref::<[_]>( css_asts[idx.get() as usize] .as_deref() @@ -579,7 +579,7 @@ pub fn find_imported_files_in_css_order<'a>( // SAFETY: every match arm yields a pointer to a live slice (`css_asts` // arena, `entry.kind`'s `Layers`, or a static empty); the source-index // arm is a `*const [_]`-level cast between layout-identical `LayerName` - // shadows (see PORT NOTE above). Valid for this loop iteration. + // shadows (see the note above). Valid for this loop iteration. let layers_key: &[LayerName] = unsafe { &*layers_key }; let mut index: usize = 0; while index < layer_duplicates.len() as usize { @@ -778,7 +778,7 @@ fn deep_clone_conditions(list: &Vec, arena: &Arena) -> Vec) -> Vec { let mut out = Vec::::init_capacity(list.len() as usize); for r in list.slice_const() { - // PORT NOTE: `ImportRecord` is plain-old-data in Zig (no destructor); + // `ImportRecord` is plain-old-data in Zig (no destructor); // `Path<'static>` slices borrow resolver storage. Bitwise copy matches // the Zig `clone(arena)` semantics. // SAFETY: `ImportRecord` is POD (borrowed slices, no owning `Drop`); a @@ -922,8 +922,6 @@ fn debug_css_order(this: &LinkerContext, order: &Vec, step: CssO // PERF(port): `step` was a comptime enum param; debug-only so demoted to runtime. #[cfg(debug_assertions)] { - // PORT NOTE: comptime `"BUN_DEBUG_CSS_ORDER_" ++ @tagName(step)` — - // runtime concat is fine here (debug-only). let tag = step.tag_name(); let env_var = format!("BUN_DEBUG_CSS_ORDER_{}\0", tag); let enable_all = bun_core::env_var::BUN_DEBUG_CSS_ORDER diff --git a/src/bundler/linker_context/generateChunksInParallel.rs b/src/bundler/linker_context/generateChunksInParallel.rs index 6a5954e67de4..dde65f8f2d44 100644 --- a/src/bundler/linker_context/generateChunksInParallel.rs +++ b/src/bundler/linker_context/generateChunksInParallel.rs @@ -5,7 +5,7 @@ use std::io::Write as _; use bun_collections::AutoBitSet; use bun_collections::StringArrayHashMap; use bun_collections::StringHashMap; -// PORT NOTE: Zig `bun.threading.ThreadPool` is the *module*; `Batch`/`Task` +// Zig `bun.threading.ThreadPool` is the *module*; `Batch`/`Task` // are free types in that module, not associated types on the struct. use bun_core::String as BunString; use bun_core::strings; @@ -41,15 +41,14 @@ const BYTECODE_EXTENSION: &str = ".jsc"; bun_core::declare_scope!(PartRanges, hidden); -// PORT NOTE: `Chunk.final_rel_path` / `metafile_chunk_json` are owned +// `Chunk.final_rel_path` / `metafile_chunk_json` are owned // `Box<[u8]>` (Zig stored them as linker-arena `[]const u8`); assignments // below move the boxed buffer directly — no lifetime promotion needed. use crate::linker_context_mod::debug; -// TODO(port): Zig's return type is `!if (is_dev_server) void else ArrayList(OutputFile)`. +// Zig's return type is `!if (is_dev_server) void else ArrayList(OutputFile)`. // Rust const generics cannot vary the return type, so we always return -// `Vec` and the IS_DEV_SERVER path returns an empty Vec. Could be -// split into two monomorphized wrappers if the unused Vec matters. +// `Vec` and the IS_DEV_SERVER path returns an empty Vec. pub fn generate_chunks_in_parallel( c: &mut LinkerContext, chunks: &mut [Chunk], @@ -66,7 +65,7 @@ pub fn generate_chunks_in_parallel( { // TODO: instead of running a renamer per chunk, run it per file debug!(" START {} renamers", chunks.len()); - // PORT NOTE: Zig `defer debug(...)` is moved to end-of-scope explicitly below. + // Zig `defer debug(...)` is moved to end-of-scope explicitly below. let ctx = GenerateChunkCtx { chunk: bun_ptr::BackRef::new_mut(&mut chunks[0]), // SAFETY: `c` is the live `&mut LinkerContext` for the link step; @@ -84,7 +83,7 @@ pub fn generate_chunks_in_parallel( if c.source_maps.line_offset_tasks.len() > 0 { debug!(" START {} source maps (line offset)", chunks.len()); c.source_maps.line_offset_wait_group.wait(); - // PORT NOTE: `c.arena().free(...)` + `.len = 0` → Vec drop semantics. + // `c.arena().free(...)` + `.len = 0` → Vec drop semantics. c.source_maps.line_offset_tasks = Box::default(); debug!(" DONE {} source maps (line offset)", chunks.len()); } @@ -117,7 +116,7 @@ pub fn generate_chunks_in_parallel( callback: prepare_css_asts_for_chunk, }, chunk: std::ptr::from_mut::(chunk), - // PORT NOTE: `PrepareCssAstTask.linker` is `*mut LinkerContext<'static>` + // `PrepareCssAstTask.linker` is `*mut LinkerContext<'static>` // (raw ptr is invariant); `.cast()` erases the inner `'a` to satisfy it. linker: std::ptr::from_mut::(c).cast(), }); @@ -147,7 +146,7 @@ pub fn generate_chunks_in_parallel( { let mut total_count: usize = 0; - // PORT NOTE: `GenerateChunkCtx` fields are raw pointers; capture them + // `GenerateChunkCtx` fields are raw pointers; capture them // before the `iter_mut()` borrow so the same `*mut [Chunk]` can be // stored in every ctx (Zig stores `[]Chunk` by value). // SAFETY: `c` is the live `&mut LinkerContext` for the link step. @@ -353,7 +352,7 @@ pub fn generate_chunks_in_parallel( // Compute the final hashes of each chunk, then use those to create the final // paths of each chunk. This can technically be done in parallel but it // probably doesn't matter so much because we're not hashing that much data. - // PORT NOTE: reshaped for borrowck — index loop so `chunks` can be passed + // Reshaped for borrowck — index loop so `chunks` can be passed // whole to `append_isolated_hashes_for_imported_chunks` and then indexed. for index in 0..chunks.len() { let mut hash = ContentHasher::default(); @@ -368,7 +367,7 @@ pub fn generate_chunks_in_parallel( chunk.template.placeholder.hash = Some(hash.digest()); let mut rel_path: Vec = Vec::new(); - // PORT NOTE: use the byte-writer (`PathTemplate::print`) directly — + // Use the byte-writer (`PathTemplate::print`) directly — // routing through `Display`/`write!` goes via `from_utf8_lossy`, // which would replace non-UTF-8 dir bytes with U+FFFD and corrupt // the output path. Zig's `std.fmt.allocPrint` writes raw bytes. @@ -447,7 +446,7 @@ pub fn generate_chunks_in_parallel( c.log_mut().add_error(None, bun_ast::Loc::EMPTY, msg); - // PORT NOTE: Zig `inline for` over a homogeneous tuple → const array + plain for. + // Zig `inline for` over a homogeneous tuple → const array + plain for. for (name, template) in [ ("entry", entry_naming), ("chunk", chunk_naming), @@ -557,14 +556,14 @@ pub fn generate_chunks_in_parallel( // Free the ModuleInfo now that it's been serialized to bytes. // It was allocated with bun.default_allocator (not the arena), // so it must be explicitly destroyed. - // PORT NOTE: in Rust, dropping the Option> frees it. + // In Rust, dropping the Option> frees it. js.module_info = None; } } // Generate metafile JSON fragments for each chunk (after paths are resolved) if c.options.metafile { - // PORT NOTE: reshaped for borrowck — `generate_chunk_json` reads all chunks + // Reshaped for borrowck — `generate_chunk_json` reads all chunks // immutably while we write one chunk's `metafile_chunk_json`; index split. for i in 0..chunks.len() { let json = @@ -618,7 +617,7 @@ pub fn generate_chunks_in_parallel( // Closing tag escaping (>>` frees via `Drop` (global mimalloc); if `allocatorForSize` // returns a distinct arena for large buffers, matched-arena dealloc must be // restored here. @@ -627,7 +626,7 @@ pub fn generate_chunks_in_parallel( if is_standalone { let mut scc: Vec>> = vec![None; chunks.len()]; - // PORT NOTE: `IntermediateOutput.code_standalone` reads `&Chunk` / + // `IntermediateOutput.code_standalone` reads `&Chunk` / // `&[Chunk]` (chunk is `&chunks[ci]`). Take `intermediate_output` out // by value so the only `&mut` is disjoint from those shared borrows. for ci in 0..chunks.len() { @@ -675,7 +674,7 @@ pub fn generate_chunks_in_parallel( )?; } else { // In-memory build (also used for standalone mode) - // PORT NOTE: `code()` / `code_standalone()` read `chunk` (= `&chunks[i]`) + // `code()` / `code_standalone()` read `chunk` (= `&chunks[i]`) // and the full `&[Chunk]` slice simultaneously. Iterate by index so both // can be safe shared reborrows of `chunks`; the only per-chunk mutation // is the `intermediate_output` take/restore, done via `chunks[i]`. @@ -810,7 +809,6 @@ pub fn generate_chunks_in_parallel( + a.len() + b.len() + b"\n".len(); - // TODO(port): Zig uses Chunk.IntermediateOutput.allocatorForSize(total_len) let mut buf: Vec = Vec::with_capacity(total_len); // PERF(port): was appendSliceAssumeCapacity buf.extend_from_slice(&code_result.buffer); @@ -849,7 +847,6 @@ pub fn generate_chunks_in_parallel( let source_map_start = b"//# sourceMappingURL=data:application/json;base64,"; let total_len = code_result.buffer.len() + source_map_start.len() + encode_len + 1; - // TODO(port): Zig uses Chunk.IntermediateOutput.allocatorForSize(total_len) let mut buf: Vec = Vec::with_capacity(total_len); // PERF(port): was appendSliceAssumeCapacity @@ -1164,12 +1161,12 @@ pub fn generate_chunks_in_parallel( // Deinit dropped items to free their heap allocations (paths, buffers). let mut result = output_files.take(); let mut write_idx: usize = 0; - // PORT NOTE: reshaped for borrowck — Zig iterates by pointer and assigns into items[write_idx]. + // Reshaped for borrowck — Zig iterates by pointer and assigns into items[write_idx]. let len = result.len(); for i in 0..len { if result[i].loader == Loader::Html { result.swap(write_idx, i); - // PORT NOTE: Zig copies item then leaves original; using swap keeps semantics + // Zig copies item then leaves original; using swap keeps semantics // (the slot at `i` will be truncated/dropped below). write_idx += 1; } @@ -1184,7 +1181,6 @@ pub fn generate_chunks_in_parallel( pub use crate::ThreadPool; -// TODO(port): narrow error set use crate::EntryPoint; use crate::options::SourceMapOption; use crate::output_file::BakeExtra; diff --git a/src/bundler/linker_context/generateCodeForFileInChunkJS.rs b/src/bundler/linker_context/generateCodeForFileInChunkJS.rs index 72dcd1714bbe..fad1011fac8c 100644 --- a/src/bundler/linker_context/generateCodeForFileInChunkJS.rs +++ b/src/bundler/linker_context/generateCodeForFileInChunkJS.rs @@ -20,9 +20,6 @@ use bun_js_parser::lexer as js_lexer; use super::convert_stmts_for_chunk::convert_stmts_for_chunk; use super::convert_stmts_for_chunk_for_dev_server::convert_stmts_for_chunk_for_dev_server; -// PORT NOTE: MultiArrayList column access — Zig `list.items(.field)` is mapped here as -// `list.items_field()` method calls (codegen'd accessors on the SoA wrappers). - #[allow(clippy::too_many_arguments)] pub fn generate_code_for_file_in_chunk_js<'r, 'src>( c: &mut LinkerContext, @@ -40,7 +37,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( ) -> js_printer::PrintResult { let source_index = part_range.source_index.get() as usize; - // PORT NOTE: reshaped for borrowck — grab raw pointers to the SoA columns up front so + // Grab raw pointers to the SoA columns up front so // subsequent `&mut c` borrows (convert_stmts_for_chunk, print_code_for_file_in_chunk_js) // don't conflict. Matches Zig which slices once at the top. // SAFETY: the underlying MultiArrayList storage is not resized for the duration of this @@ -61,7 +58,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( // referencing everything by array makes the code a lot more annoying :( // - // PORT NOTE: `MultiArrayList::get` returns `ManuallyDrop` — the + // `MultiArrayList::get` returns `ManuallyDrop` — the // storage retains ownership of every Drop field (`parts`, `symbols`, // `named_imports`, …). The local `flags` mutation below is intentional and // stays scoped to this read view. @@ -104,7 +101,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( .all_stmts .extend_from_slice(stmts.inside_wrapper_suffix.as_slice()); - // PORT NOTE: reshaped for borrowck — capture pointer/len now, re-slice after pushes. + // Capture pointer/len now, re-slice after pushes (borrowck). // SAFETY: `inner` aliases the first `main_stmts_len` elements of `all_stmts`; // subsequent pushes only append past this range and capacity was reserved above // so no reallocation occurs. Matches Zig which slices then continues appending. @@ -187,7 +184,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( // TODO: there is a weird edge case where the pretty path is not computed // it does not reproduce when debugging. let source_ref = c.get_source(source_index as u32); - // PORT NOTE: reshaped for borrowck — Zig copies the `Source` by value, + // The Zig original copies the `Source` by value, // mutates `.path`, and passes `&source`. `bun_ast::Source` is not `Clone` // (its `Cow` fields would deep-copy `Owned` data); instead, build a // borrowed-field shadow only when the path needs fixing. @@ -309,7 +306,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( match flags.wrap { WrapKind::Esm => { - // PORT NOTE: reshaped for borrowck — append_slice borrows `stmts` mutably while + // Borrowck: `append_slice` borrows `stmts` mutably while // also reading from a sibling field. let suffix = core::mem::take(&mut stmts.inside_wrapper_suffix); stmts.append_slice(StmtListWhich::OutsideWrapperPrefix, suffix.as_slice()); @@ -399,7 +396,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( // Be careful: the top-level value in a JSON file is not necessarily an object if let ExprData::EObject(e_object) = default_expr.data { - // PORT NOTE: Zig `properties.clone(temp_arena)` is a memcpy into the + // Zig's `properties.clone(temp_arena)` is a memcpy into the // temp arena. `G::Property` is not `Clone` (it embeds a `Vec`), so // mirror the Zig bitwise copy directly. JSON object properties carry no // owned heap data (`ts_decorators` is always empty, `class_static_block` @@ -692,7 +689,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( // Hoist all top-level "var" and "function" declarations out of the closure { let mut end: usize = 0; - // PORT NOTE: reshaped for borrowck — iterate by index since we mutate + // Iterate by index since we mutate // `inner_stmts[end]` and call `stmts.append(...)` inside the loop. 'hoist: for i in 0..stmts.all_stmts.len() { let stmt = stmts.all_stmts[i]; @@ -751,7 +748,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( continue 'hoist; } StmtData::SClass(mut class) => 'stmt: { - // PORT NOTE: `class` is `StoreRef` — an arena-owned pointer. + // `class` is `StoreRef` — an arena-owned pointer. // `&mut class.class` (via DerefMut) yields a `&mut G::Class` into arena // memory, so wrapping it in a StoreRef for `EClass` is sound and matches // Zig's `&class.class`. @@ -1073,7 +1070,7 @@ fn merge_adjacent_local_stmts(stmts: &mut Vec, _arena: &Bump) { let mut did_merge_with_previous_local = false; let mut end: usize = 1; - // PORT NOTE: reshaped for borrowck — iterate by index because we read `stmts[i]` + // Iterate by index because we read `stmts[i]` // and write `stmts[end - 1]` / `stmts[end]` in the same loop body. for i in 1..stmts.len() { let stmt = stmts[i]; diff --git a/src/bundler/linker_context/generateCodeForLazyExport.rs b/src/bundler/linker_context/generateCodeForLazyExport.rs index cb3d546178d4..4412218a7351 100644 --- a/src/bundler/linker_context/generateCodeForLazyExport.rs +++ b/src/bundler/linker_context/generateCodeForLazyExport.rs @@ -37,7 +37,7 @@ pub fn generate_code_for_lazy_export( source_index: IndexInt, ) -> Result<(), AllocError> { let exports_kind = this.graph.ast.items_exports_kind()[source_index as usize]; - // PORT NOTE: reshaped for borrowck — take `parts` as a raw pointer *before* the + // Take `parts` as a raw pointer *before* the // long-lived immutable `items_css()` borrow below; re-borrowed again later as needed. let parts: *mut [Part] = this.graph.ast.items_parts_mut()[source_index as usize].as_mut_slice(); // SAFETY: parse_graph backref; raw deref because `all_sources` is held @@ -170,7 +170,6 @@ pub fn generate_code_for_lazy_export( .unwrap() .loc; - // PORT NOTE: was `catch |err| bun.handleOom(err)` — crash on OOM. self.log.add_range_error_fmt_with_note( Some(&self.all_sources[idx as usize]), bun_ast::Range { loc: compose_loc, ..Default::default() }, @@ -306,7 +305,7 @@ pub fn generate_code_for_lazy_export( } } - // PORT NOTE: Zig left `parts: undefined` and rebound per-iteration; Rust + // Zig left `parts: undefined` and rebound per-iteration; Rust // forbids uninit refs, so the Visitor is constructed inside the loop with // a fresh `parts` borrow each time (reshaped for borrowck). let all_symbols = this.graph.ast.items_symbols(); @@ -355,7 +354,7 @@ pub fn generate_code_for_lazy_export( tail_loc: stmt.loc, tail: E::TemplateContents::Cooked(E::String::init(b"")), }); - // PORT NOTE: Zig used an arena-backed ArrayList and moved `.items` + // Zig used an arena-backed ArrayList and moved `.items` // into `E.Template`; mirror that by moving into the linker arena // (freed when the linker arena drops). let parts_slice = @@ -430,13 +429,12 @@ pub fn generate_code_for_lazy_export( _ => { // Otherwise, generate ES6 export statements. These are added as additional // parts so they can be tree shaken individually. - // PORT NOTE: Zig `part.stmts.len = 0` truncates the slice. part.stmts = bun_ast::StoreSlice::EMPTY; if let ExprData::EObject(e_object) = &expr.data { for property in e_object.properties.slice() { let _: &G::Property = property; - // PORT NOTE: `Expr`/`ExprData`/`StoreRef<_>` are `Copy`. Copy `key` out so + // `Expr`/`ExprData`/`StoreRef<_>` are `Copy`. Copy `key` out so // `key_str: StoreRef` is a mutable local — `slice()` resolves // the rope in-place via `DerefMut` into the arena slot (matches Zig's // `property.key.?.data.e_string.slice(...)` which takes `*String`). @@ -493,7 +491,7 @@ pub fn generate_code_for_lazy_export( }, key.loc, ))); - // PORT NOTE: `parts.ptr[generated[1]]` — re-borrow `parts` here for borrowck. + // Re-borrow `parts` here for borrowck. let parts = this.graph.ast.items_parts_mut()[source_index as usize].as_mut_slice(); parts[generated.1 as usize].stmts = bun_ast::StoreSlice::new_mut(new_stmts); diff --git a/src/bundler/linker_context/generateCompileResultForCssChunk.rs b/src/bundler/linker_context/generateCompileResultForCssChunk.rs index aa6ae59cf0d7..d03ad06be366 100644 --- a/src/bundler/linker_context/generateCompileResultForCssChunk.rs +++ b/src/bundler/linker_context/generateCompileResultForCssChunk.rs @@ -84,7 +84,6 @@ fn generate_compile_result_for_css_chunk_impl( a.reset(); } }); - // TODO(port): worker.arena threading — css crate is an AST crate and may want &'bump Bump let mut allocating_writer: Vec = Vec::new(); let Content::Css(css_content) = &chunk.content else { diff --git a/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs b/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs index e8fb88000af7..c0ff1cb64467 100644 --- a/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs +++ b/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs @@ -401,10 +401,12 @@ fn generate_compile_result_for_html_chunk_impl<'a>( let import_records = c.graph.ast.items_import_records(); let source_index = chunk.entry_point.source_index(); - // HTML bundles for dev server must be allocated to it, as it must outlive - // the bundle task. See `DevServer.RouteBundle.HTML.bundled_html_text` - // TODO(port): Zig used `dev.arena()` vs `worker.arena` to control output ownership. - // In Rust with global mimalloc this distinction collapses; verify DevServer ownership. + // HTML bundles for the dev server must outlive the bundle task (see + // `DevServer.RouteBundle.HTML.bundled_html_text`). Zig picked `dev.arena()` + // vs `worker.arena` for the output buffer; here the output is built in a + // plain `Vec` and returned as an owned `Box<[u8]>` inside + // `CompileResult::Html`, so it lives as long as whoever holds the + // `CompileResult` — no arena-lifetime distinction needed. // `c.log` is now `*mut Log` (raw backref); copy directly. The HTMLLoader.log // field is currently dead_code, so no write actually occurs through this diff --git a/src/bundler/linker_context/generateCompileResultForJSChunk.rs b/src/bundler/linker_context/generateCompileResultForJSChunk.rs index f33d4844344a..849dd3d5f11c 100644 --- a/src/bundler/linker_context/generateCompileResultForJSChunk.rs +++ b/src/bundler/linker_context/generateCompileResultForJSChunk.rs @@ -34,7 +34,10 @@ pub unsafe fn generate_compile_result_for_js_chunk(task: *mut ThreadPoolLib::Tas let (part_range, c_ptr, chunk_ptr, mut worker) = unsafe { crate::linker_context_mod::pending_part_range_prologue(task) }; - // TODO(port): Environment.show_crash_trace — exact cfg key TBD; using feature = "show_crash_trace" + // Mirrors Zig `Environment.show_crash_trace`; wired as a cargo feature + // through bun_runtime → bun_bundler → bun_crash_handler. Unlike Zig + // (auto-on in debug/test/asan), no build profile enables the feature by + // default — it must be opted into explicitly. #[cfg(feature = "show_crash_trace")] let _crash_guard = { // `part_range.ctx.{c,chunk}` are `ParentRef`/`BackRef` — safe shared @@ -62,7 +65,8 @@ pub unsafe fn generate_compile_result_for_js_chunk(task: *mut ThreadPoolLib::Tas // borrows below are scoped to the impl call so they do not overlap the // raw slot write that follows. (Peer tasks still hold their own `&mut` // views into the same `LinkerContext`/`Chunk` for read-only printer use — - // see TODO(ub-audit) on `unsafe impl Sync for Chunk`.) + // see the renamer caveat / SAFETY note on `unsafe impl Sync for + // Chunk` in Chunk.rs.) let c_mut: &mut LinkerContext = unsafe { &mut *c_ptr }; // SAFETY: same mutable-provenance / disjoint-write contract as `c_ptr` above. let chunk_mut: &mut Chunk = unsafe { &mut *chunk_ptr }; @@ -89,13 +93,12 @@ fn generate_compile_result_for_js_chunk_impl( let _trace = bun_core::perf::trace("Bundler.generateCodeForFileInChunkJS"); // `defer trace.end()` → handled by Drop on _trace - // Client and server bundles for Bake must be globally allocated, as they - // must outlive the bundle task. - // TODO(port): runtime arena selection (dev_server vs default) — - // `DevServerHandle` does not yet expose an arena handle, and - // `BufferWriter::init()` / `DeclCollector.decls` use the global arena - // in the Rust port. Once `dispatch::DevServerHandle::arena()` exists, - // thread it here so dev-server bundles outlive the worker arena. + // Client and server bundles for Bake must outlive the bundle task. Zig + // selected the dev-server arena here when `c.dev_server` was set; the Rust + // port allocates `BufferWriter::init()` output from the global heap and + // `DeclCollector.decls` from the worker heap (`worker.arena`, alive until + // bundle teardown) — both outlive the task's CompileResult consumption, + // so a per-dev-server arena would only be a perf optimization. let _ = c.dev_server; // temporary_arena / stmt_list are initialized in Worker::create before any task runs. @@ -151,9 +154,9 @@ fn generate_compile_result_for_js_chunk_impl( let collect_decls = c.options.generate_bytecode_cache && c.options.output_format == OutputFormat::Esm && c.options.compile; - // PORT NOTE: Zig threaded `arena` (dev_server or default) into - // DeclCollector; the Rust DeclCollector wants `*const Arena`. Use the - // worker heap for now (see TODO above re: dev_server arena). + // Zig threaded `arena` (dev_server or default) into DeclCollector; the + // Rust DeclCollector wants `*const Arena` and uses the worker heap (see + // the dev-server allocation note above). let mut dc = DeclCollector { arena: worker.arena.as_ptr(), ..Default::default() diff --git a/src/bundler/linker_context/postProcessCSSChunk.rs b/src/bundler/linker_context/postProcessCSSChunk.rs index 39867549c377..53caa9219c99 100644 --- a/src/bundler/linker_context/postProcessCSSChunk.rs +++ b/src/bundler/linker_context/postProcessCSSChunk.rs @@ -14,10 +14,8 @@ pub fn post_process_css_chunk( worker: &mut thread_pool::Worker, chunk: &mut Chunk, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let c = ctx.c(); - // TODO(port): worker.arena is a per-worker arena — thread `&'bump Bump`. - // PORT NOTE: avoid FRU `..Default::default()` — StringJoiner impls Drop (E0509). + // Avoid FRU `..Default::default()` — StringJoiner impls Drop (E0509). let mut j = StringJoiner::default(); j.watcher = Watcher { input: chunk.unique_key, @@ -99,7 +97,7 @@ pub fn post_process_css_chunk( } // Save the offset to the start of the stored JavaScript - // PORT NOTE: Zig `j.push(.., bun.default_allocator)` — code() borrows from + // Zig `j.push(.., bun.default_allocator)` — code() borrows from // compile_results which outlives the joiner; treat as static (no copy/free). j.push_static(compile_result.code()); diff --git a/src/bundler/linker_context/postProcessHTMLChunk.rs b/src/bundler/linker_context/postProcessHTMLChunk.rs index 8841bcf5742a..1d77567bfba0 100644 --- a/src/bundler/linker_context/postProcessHTMLChunk.rs +++ b/src/bundler/linker_context/postProcessHTMLChunk.rs @@ -9,7 +9,8 @@ pub fn post_process_html_chunk( worker: &mut thread_pool::Worker, chunk: &mut Chunk, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set — Zig `!void` but body has zero `try` sites (inferred-empty) + // The body has no fallible sites; the Result signature matches the other + // `post_process_*_chunk` callees dispatched from `generate_chunk`. // This is where we split output into pieces let c = ctx.c(); // E0509: StringJoiner has Drop, so FRU `..Default::default()` is illegal — assign field instead. @@ -22,7 +23,7 @@ pub fn post_process_html_chunk( let compile_results = &chunk.compile_results_for_chunk; for compile_result in compile_results.iter() { - // PORT NOTE: Zig `j.push(.., bun.default_allocator)` — code() borrows from + // Zig `j.push(.., bun.default_allocator)` — code() borrows from // chunk.compile_results_for_chunk which outlives `j.done()`; arena arg dropped. j.push_static(compile_result.code()); } @@ -43,7 +44,7 @@ pub fn post_process_html_chunk( ctx.chunks.len() as u32, // @truncate )); // Zig: `catch |err| bun.handleOom(err)` - // PORT NOTE: reshaped for borrowck (compute hash before assigning into chunk) + // Reshaped for borrowck (compute hash before assigning into chunk) let isolated_hash = c.generate_isolated_hash(chunk, alloc); chunk.isolated_hash = isolated_hash; diff --git a/src/bundler/linker_context/postProcessJSChunk.rs b/src/bundler/linker_context/postProcessJSChunk.rs index d4d278f6e3fa..ed7a3b4f4620 100644 --- a/src/bundler/linker_context/postProcessJSChunk.rs +++ b/src/bundler/linker_context/postProcessJSChunk.rs @@ -46,7 +46,6 @@ pub fn post_process_js_chunk( chunk: &mut Chunk, chunk_index: usize, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let _trace = perf::trace("Bundler.postProcessJSChunk"); let _ = chunk_index; @@ -63,9 +62,11 @@ pub fn post_process_js_chunk( // embedded `Vec`/`Vec` buffers leak from the global heap. let _ast_alloc_heap = js_ast::StoreAstAllocHeap::new(); - // TODO(port): `defer chunk.renamer.deinit(bun.default_allocator)` — Zig explicitly - // tears down the renamer at end of scope. In Rust this should be handled by Drop on - // the renamer field, or an explicit `chunk.renamer.take()` at fn exit. Verify. + // Zig `defer chunk.renamer.deinit(bun.default_allocator)`: the renamer is + // not used after this function, so it is reset to `None` before returning + // (success path below). `ChunkRenamer`'s variants free their owned storage + // via Drop; the `symbols` view inside is `ManuallyDrop` (non-owning), so + // graph storage is never freed — matching Zig's deinit semantics. // PERF(port): was arena bulk-free — profile if hot. let arena = Arena::new(); @@ -115,7 +116,7 @@ pub fn post_process_js_chunk( let worker_arena: &Arena = worker.arena(); { - // PORT NOTE: Zig builds one `print_options` and passes it by-value twice. + // Zig builds one `print_options` and passes it by-value twice. // Rust `Options` is not `Copy` (holds `&mut ModuleInfo`), and a closure // taking `&mut ModuleInfo` can't express "output lifetime = input // lifetime" — so build a base with `module_info: None` and override it @@ -160,7 +161,7 @@ pub fn post_process_js_chunk( }); } - // PORT NOTE: `MultiArrayList::get` returns `ManuallyDrop` — + // `MultiArrayList::get` returns `ManuallyDrop` — // the storage retains ownership of every Drop field (`named_imports`, // `parts`, `top_level_symbols_to_parts`, …), so the gathered struct // must NOT run Drop. `to_ast` consumes by value, so unwrap, convert, @@ -602,7 +603,7 @@ pub fn post_process_js_chunk( } { - // PORT NOTE: Zig `j.push(code, worker.allocator)` transferred ownership; + // Zig `j.push(code, worker.allocator)` transferred ownership; // `cross_chunk_prefix` is a local that drops at fn exit, but the joiner // may be stashed on `chunk.intermediate_output` and consumed later // (`IntermediateOutput::Joiner` path). Move the Box into the joiner. @@ -753,7 +754,7 @@ pub fn post_process_js_chunk( } { - // PORT NOTE: `entry_point_tail` is a local `CompileResult` whose `code` + // `entry_point_tail` is a local `CompileResult` whose `code` // is a `Box<[u8]>`; Zig `j.push(tail_code, worker.allocator)` handed // ownership to the joiner. Move it so the deferred-joiner path doesn't // read freed memory after this fn returns. @@ -768,7 +769,7 @@ pub fn post_process_js_chunk( // Put the cross-chunk suffix inside the IIFE { - // PORT NOTE: see cross_chunk_prefix above — move ownership into joiner. + // See cross_chunk_prefix above — move ownership into joiner. let code = print_result_take_code(&mut cross_chunk_suffix); if !code.is_empty() { if newline_before_comment { @@ -880,6 +881,11 @@ pub fn post_process_js_chunk( )?; } + // Free the renamer now that printing is done (Zig: `defer + // chunk.renamer.deinit(bun.default_allocator)`); nothing reads it after + // post-processing. + chunk.renamer = Default::default(); + Ok(()) } @@ -914,7 +920,7 @@ fn add_binding_vars_to_module_info( } } -// PORT NOTE: `js_printer::print` ties bump/Options/import_records/renamer to a +// `js_printer::print` ties bump/Options/import_records/renamer to a // single `'a`, and `Renamer<'r, 'src>` is invariant in `'src` — so the caller's // renamer lifetime fixes `'a`. All by-ref params that flow into `print` must // share that lifetime. @@ -923,8 +929,6 @@ pub fn generate_entry_point_tail_js<'a>( to_common_js_ref: Ref, to_esm_ref: Ref, source_index: IndexInt, - // bundler is an AST crate: std.mem.Allocator param → &'bump Bump (Arena) - // TODO(port): thread &'bump Bump from worker.arena end-to-end. arena: &'a Arena, temp_arena: &Arena, mut r: js_printer::renamer::Renamer<'a, 'a>, @@ -933,7 +937,7 @@ pub fn generate_entry_point_tail_js<'a>( let flags: crate::js_meta::Flags = c.graph.meta.items_flags()[source_index as usize]; // PERF(port): was arena-backed ArrayList(Stmt) — profile if hot. let mut stmts: Vec = Vec::new(); - // PORT NOTE: `MultiArrayList::get` returns `ManuallyDrop`; the + // `MultiArrayList::get` returns `ManuallyDrop`; the // storage retains ownership of every Drop field, so neither this // `BundledAst` nor the `ast_view: Ast` derived from it below may run Drop. let ast = c.graph.ast.get(source_index as usize); @@ -1032,7 +1036,7 @@ pub fn generate_entry_point_tail_js<'a>( let mut had_default_export = false; for (i, alias) in sorted_and_filtered_export_aliases.iter().enumerate() { - // PORT NOTE: Zig `resolved_exports.get(alias).?` returns a by-value + // Zig `resolved_exports.get(alias).?` returns a by-value // copy of `ExportData`; only `.data` (an `ImportTracker`, `Copy`) is // read/mutated below, so copy that field instead of the whole struct. let mut resolved_export_data = @@ -1165,7 +1169,7 @@ pub fn generate_entry_point_tail_js<'a>( } } - // PORT NOTE: arena-owned `*mut [ClauseItem]` — move the + // arena-owned `*mut [ClauseItem]` — move the // collected Vec into the linker arena (Zig used // `c.arena().alloc`). The arena slice is also iterated // below for the synthetic-default-export path. @@ -1183,7 +1187,6 @@ pub fn generate_entry_point_tail_js<'a>( // PERF(port): was initCapacity catch unreachable let getter_fn_body: &mut [Stmt] = arena.alloc_slice_fill_default(items.len()); - // TODO(port): arena.alloc(Stmt, n) — needs arena slice alloc API let mut remain_getter_fn_body = &mut getter_fn_body[..]; for export_item in items.iter() { let (fn_body, rest) = remain_getter_fn_body.split_at_mut(1); @@ -1325,7 +1328,7 @@ pub fn generate_entry_point_tail_js<'a>( // Add generated local declarations from entry point tail to module_info. // This captures vars like `var export_foo = cjs.foo` for CJS export copies. - // PORT NOTE: reshaped for borrowck — reborrow via as_deref_mut so module_info + // Reshaped for borrowck — reborrow via as_deref_mut so module_info // remains usable for print_options below. if let Some(mi) = module_info.as_mut() { let mi: &mut ModuleInfo = &mut **mi; diff --git a/src/bundler/linker_context/prepareCssAstsForChunk.rs b/src/bundler/linker_context/prepareCssAstsForChunk.rs index c473a298841e..fdff8befc886 100644 --- a/src/bundler/linker_context/prepareCssAstsForChunk.rs +++ b/src/bundler/linker_context/prepareCssAstsForChunk.rs @@ -20,7 +20,7 @@ use bun_resolver::DataURL; use crate::chunk::{Content, CssImportOrderKind}; -// PORT NOTE: Zig stores `*Chunk` / `*LinkerContext` (freely-aliasing mutable +// Zig stores `*Chunk` / `*LinkerContext` (freely-aliasing mutable // pointers). We mirror that with raw pointers rather than `&mut` / `&` so that // (a) the container_of `container_of` recovery of `*mut BundleV2` from // `linker` retains write provenance over the whole bundle, and (b) multiple @@ -91,7 +91,7 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & // Remove duplicate rules across files. This must be done in serial, not // in parallel, and must be done from the last rule to the first rule. { - // PORT NOTE: Zig accesses `chunk.content.css.{imports_in_chunk_in_order,asts}` + // Zig accesses `chunk.content.css.{imports_in_chunk_in_order,asts}` // through the union field at each use site while also holding `entry` as a raw // pointer into `imports_in_chunk_in_order`. In Rust, every `chunk.content.css.*` // re-enters the `Content` enum and re-borrows `chunk.content` as a whole, which @@ -105,14 +105,14 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & while i != 0 { i -= 1; let entry = css_chunk.imports_in_chunk_in_order.mut_(i); - // PORT NOTE: reshaped for borrowck — match on entry.kind while also touching + // Reshaped for borrowck — match on entry.kind while also touching // entry.conditions / entry.condition_import_records relies on disjoint field borrows. match &mut entry.kind { CssImportOrderKind::Layers(layers) => { let inner = layers.inner(); let len = inner.len(); let rules = if len > 0 { - // PORT NOTE: Zig `SmallList(LayerName,1).fromBabyListNoDeinit(layers.inner().*)` + // Zig `SmallList(LayerName,1).fromBabyListNoDeinit(layers.inner().*)` // is a bitwise Vec→SmallList header transfer. In Rust the // `Chunk::Layers` payload is the lifetime-erased shadow // `bun_css::LayerName { v: Vec> }`, @@ -133,7 +133,7 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & shadow.v.slice().iter().map(|seg| { // `seg` borrows arena-owned bytes that outlive this // stylesheet; route through `StoreStr` for the lifetime - // erasure (see layer.rs TODO(port)). + // erasure (see the corresponding note in layer.rs). bun_ast::StoreStr::new(seg).slice() }), ), @@ -162,7 +162,7 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & css_chunk.asts[i] = ast; } CssImportOrderKind::ExternalPath(p) => { - // PORT NOTE: Zig keeps `conditions: ?*ImportConditions` as a raw + // Zig keeps `conditions: ?*ImportConditions` as a raw // pointer to index 0 while the `while j != 1` loop reads // `entry.conditions.len` / `.at(j)`. Taking `&mut` at index 0 here // would exclusively borrow the whole `entry.conditions` Vec for @@ -195,7 +195,7 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & while j != 1 { j -= 1; - // PORT NOTE: Zig has no destructors, so when `ast_import` falls + // Zig has no destructors, so when `ast_import` falls // out of scope the bitwise-duplicated `ImportConditions` inside // it (see `ptr::read` below) is simply abandoned. In Rust, // dropping `ast_import` would run `Drop` on that aliased @@ -283,7 +283,7 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & b"text/css", strings::trim(print_result.code.as_slice(), b" \n\r\t"), ); - // PORT NOTE: Zig allocated into the worker arena (`arena`). + // Zig allocated into the worker arena (`arena`). // `encode_string_as_shortest_data_url` returns a heap `Vec`; // copy it into the worker bump so ownership matches Zig (freed // at bundle teardown via arena reset). SAFETY: arena outlives diff --git a/src/bundler/linker_context/renameSymbolsInChunk.rs b/src/bundler/linker_context/renameSymbolsInChunk.rs index 1d14bde68bcb..43946ee509a4 100644 --- a/src/bundler/linker_context/renameSymbolsInChunk.rs +++ b/src/bundler/linker_context/renameSymbolsInChunk.rs @@ -15,11 +15,6 @@ use crate::{Chunk, LinkerContext, StableRef, WrapKind}; /// TODO: investigate if we need to parallelize this function /// esbuild does parallelize it. -// TODO(port): narrow error set -// TODO(port): bundler is an AST crate (PORTING.md §Allocators) — verify whether caller passes -// an arena vs default_allocator for the dropped `arena: std.mem.Allocator` param; if arena, -// thread `bump: &'bump Bump` and switch working Vecs to bun_alloc::ArenaVec<'bump, T>. -// // CONCURRENCY: called from `LinkerContext::generate_js_renamer` (`each_ptr` // callback) — runs on worker threads, one task per chunk. Writes go to // `chunk.renamer` (per-chunk disjoint) plus per-`source_index` rows of @@ -105,7 +100,7 @@ pub unsafe fn rename_symbols_in_chunk( ) }; - // PORT NOTE: `symbol::Map` is not `Clone`/`Copy`; Zig passed the struct + // `symbol::Map` is not `Clone`/`Copy`; Zig passed the struct // (slice header) by value. Build a non-owning shallow view via // `from_bump_slice` so the renamer's `Map` does not free graph storage on // drop. @@ -260,7 +255,7 @@ pub unsafe fn rename_symbols_in_chunk( top_level_symbols.clear(); for stable_ref in &sorted_imports_from_other_chunks { - // PORT NOTE: `StableRef` is `repr(packed)`; copy the field to avoid an unaligned ref. + // `StableRef` is `repr(packed)`; copy the field to avoid an unaligned ref. let ref_ = { stable_ref.r#ref }; minify_renamer.accumulate_symbol_use_count( &mut top_level_symbols, @@ -281,16 +276,16 @@ pub unsafe fn rename_symbols_in_chunk( let mut r = NumberRenamer::init(make_symbols_view(symbols), &reserved_names)?; for stable_ref in &sorted_imports_from_other_chunks { - // PORT NOTE: `StableRef` is `repr(packed)`; copy the field to avoid an unaligned ref. + // `StableRef` is `repr(packed)`; copy the field to avoid an unaligned ref. r.add_top_level_symbol(stable_ref.r#ref); } - // PORT NOTE: Zig used `r.temp_arena` for this list; arena param dropped + // Zig used `r.temp_arena` for this list; arena param dropped let mut sorted: Vec = Vec::new(); for &source_index in files_in_order { let wrap = all_flags[source_index as usize].wrap; - // PORT NOTE: need `&mut [Part]` for `add_top_level_declared_symbols`. + // Need `&mut [Part]` for `add_top_level_declared_symbols`. let parts: &mut [Part] = all_parts[source_index as usize].as_mut_slice(); match wrap { @@ -366,7 +361,7 @@ pub unsafe fn rename_symbols_in_chunk( } } } - // PORT NOTE: reshaped for borrowck — `&mut r.root` while `r` is the + // Reshaped for borrowck — `&mut r.root` while `r` is the // `&mut self` receiver. Take a raw pointer; `assign_names_*` does // not touch `self.root` through `self`. let root: *mut renamer::NumberScope = core::ptr::addr_of_mut!(r.root); diff --git a/src/bundler/linker_context/scanImportsAndExports.rs b/src/bundler/linker_context/scanImportsAndExports.rs index ab95027391f8..5205df726d7f 100644 --- a/src/bundler/linker_context/scanImportsAndExports.rs +++ b/src/bundler/linker_context/scanImportsAndExports.rs @@ -1,6 +1,6 @@ //! Port of `src/bundler/linker_context/scanImportsAndExports.zig`. // -// PORT NOTE: the Zig body takes ~20 simultaneous mutable column slices +// The Zig body takes ~20 simultaneous mutable column slices // (`this.graph.ast.items(.field)`) and freely interleaves them with // `&mut LinkerContext` method calls. Rust's borrowck forbids both holding // overlapping `&mut [T]` columns from the same `MultiArrayList` and holding @@ -84,7 +84,7 @@ pub fn scan_imports_and_exports( let _outer_trace = perf::trace("Bundler.scanImportsAndExports"); let output_format = this.options.output_format; - // PORT NOTE: `reachable_files` is borrowed out of `this.graph` while the + // `reachable_files` is borrowed out of `this.graph` while the // body also calls `&mut this.graph` methods. Snapshot the indices. // PERF(port): was zero-copy slice; profile. let mut reachable: Vec = this.graph.reachable_files.slice().to_vec(); @@ -131,7 +131,7 @@ pub fn scan_imports_and_exports( let cjs_export_copies: *mut [js_meta::CjsExportCopies] = meta.cjs_export_copies; let entry_point_part_indices: *mut [Index] = meta.entry_point_part_index; - // PORT NOTE: Zig copies `symbols` to a local and `defer`-writes it back. + // Zig copies `symbols` to a local and `defer`-writes it back. // In Rust `this.graph.symbols` is the same storage, so no copy-back needed. { @@ -317,8 +317,6 @@ pub fn scan_imports_and_exports( output_format, } }; - // PORT NOTE: `defer dependency_wrapper.export_star_map.deinit()` → Drop handles it. - for source_index_ in &reachable { let source_index = source_index_.get(); let id = source_index as usize; @@ -363,9 +361,6 @@ pub fn scan_imports_and_exports( { let mut export_star_ctx: Option = None; let _trace = perf::trace("Bundler.ResolveExportStarStatements"); - // PORT NOTE: `defer { if (export_star_ctx) |*export_ctx| export_ctx.source_index_stack.deinit(); }` - // → Drop on `export_star_ctx` handles freeing `source_index_stack: Vec`. - for source_index_ in &reachable { let source_index = source_index_.get(); let id = source_index as usize; @@ -491,7 +486,7 @@ pub fn scan_imports_and_exports( // imported using an import star statement. // Note: `do` will wait for all to finish before moving forward // - // PORT NOTE: Zig dispatched via `worker_pool.each(arena, this, + // Zig dispatched via `worker_pool.each(arena, this, // doStep5, reachable_files)` (parallel fan-out, blocks until done). // `do_step5` only touches distinct SoA rows per `source_index` (the // columns are pre-sized and never reallocate during this step), @@ -614,7 +609,7 @@ pub fn scan_imports_and_exports( .graph .arena() .alloc_slice_fill_default::(string_buffer_len); - // PORT NOTE: `StringBuilder::drop` reconstructs a `Box<[u8]>` from + // `StringBuilder::drop` reconstructs a `Box<[u8]>` from // `ptr`/`cap` and frees it via the global arena. Here the // backing buffer is arena-owned (bumpalo), so dropping would hand // mimalloc a pointer it never allocated. Wrap in `ManuallyDrop` — @@ -701,8 +696,8 @@ pub fn scan_imports_and_exports( } } - // PORT NOTE: Zig `defer bun.assert(builder.len == builder.cap)` — - // moved to end-of-scope assert (no early returns inside this block). + // End-of-scope assert (Zig used `defer`); relies on there being no + // early returns inside this block. debug_assert!(builder.len == builder.cap); // Include the "__export" symbol from the runtime if it was used in the @@ -727,7 +722,7 @@ pub fn scan_imports_and_exports( { let imports_to_bind = &col_ref!(imports_to_bind_list)[id]; debug_assert_eq!(imports_to_bind.keys().len(), imports_to_bind.values().len()); - // PORT NOTE: reshaped for borrowck — iterate by index so we can + // Iterate by index so we can // re-borrow `parts` after each `top_level_symbol_to_parts` call. for itb_i in 0..imports_to_bind.keys().len() { let r#ref: Ref = col_ref!(imports_to_bind_list)[id].keys()[itb_i]; @@ -822,7 +817,7 @@ pub fn scan_imports_and_exports( for part_index in top_to_parts { // PERF(port): was appendAssumeCapacity dependencies.push(Dependency { - // PORT NOTE: `crate::Index` ↔ `bun_ast::Index` are both + // `crate::Index` ↔ `bun_ast::Index` are both // `#[repr(transparent)] u32` newtypes ported from the // same Zig `ast.Index`; bridge by `.value` until B-3 // collapses them to a single re-export. @@ -861,8 +856,6 @@ pub fn scan_imports_and_exports( ..Default::default() }, )?; - // PORT NOTE: `catch |err| bun.handleOom(err)` dropped — `?` propagates OOM. - col!(entry_point_part_indices)[id] = Index::part(entry_point_part_index); // Pull in the "__toCommonJS" symbol if we need it due to being an entry point @@ -892,7 +885,7 @@ pub fn scan_imports_and_exports( let mut runtime_require_uses: u32 = 0; // Imports of wrapped files must depend on the wrapper - // PORT NOTE: iterate by index so each iteration re-borrows + // Iterate by index so each iteration re-borrows // `import_records` (the body calls `&mut this.graph` methods). let import_record_indices_len = col_ref!(parts_list)[id].as_slice()[part_index] .import_record_indices @@ -909,7 +902,7 @@ pub fn scan_imports_and_exports( let other_id = rec_source_index.value() as usize; // Don't follow external imports (this includes import() expressions) - // PORT NOTE: short-circuit — `is_external_dynamic_import` indexes by + // Short-circuit: `is_external_dynamic_import` indexes by // `record.source_index`, so it must only run when that index is valid. let is_external_dyn = rec_source_index.is_valid() && { let record = &col_ref!(import_records_list)[id].as_slice() @@ -1320,7 +1313,7 @@ impl<'a> ExportStarContext<'a> { continue; } - // PORT NOTE: reshaped for borrowck — collect (alias, name) pairs so the + // Collect (alias, name) pairs so the // loop body can mutably borrow `resolved_exports` / `imports_to_bind`. // PERF(port): was zero-copy `iter()` over StringArrayHashMap; profile. let exports_len = col_ref!(self.named_exports)[other_id].keys().len(); @@ -1375,7 +1368,6 @@ impl<'a> ExportStarContext<'a> { }, ) .expect("oom"); - // PORT NOTE: `catch |err| bun.handleOom(err)` dropped — aborts on OOM. } else if gop.value_ptr.data.source_index.get() != other_source_index { // Two different re-exports colliding makes it potentially ambiguous gop.value_ptr @@ -1388,7 +1380,6 @@ impl<'a> ExportStarContext<'a> { }, ..Default::default() }); - // PORT NOTE: `catch |err| bun.handleOom(err)` dropped — aborts on OOM. } } @@ -1396,7 +1387,7 @@ impl<'a> ExportStarContext<'a> { self.add_exports(resolved_exports, target_id, other_source_index); } - // PORT NOTE: Zig `defer this.source_index_stack.shrinkRetainingCapacity(stack_end_pos - 1)` + // Zig: `defer this.source_index_stack.shrinkRetainingCapacity(stack_end_pos - 1)` // — inlined at scope end (no early returns after the push). self.source_index_stack.truncate(stack_end_pos - 1); } @@ -1539,8 +1530,6 @@ mod __css_validation { log: &'a mut Log, } - // PORT NOTE: `pub fn deinit` → Drop on `visited` / `properties` handles cleanup. - impl<'a, 'bump> Visitor<'a, 'bump> { fn add_property_or_warn( &mut self, @@ -1619,8 +1608,6 @@ mod __css_validation { ), ..Default::default() }); - // PORT NOTE: nested `catch |err| bun.handleOom(err)` chain dropped — aborts on OOM. - // Don't warn more than once entry.value_ptr.source_index = Index::INVALID.get(); } diff --git a/src/bundler/linker_context/writeOutputFilesToDisk.rs b/src/bundler/linker_context/writeOutputFilesToDisk.rs index 75aefd5033fe..ad44c32e0629 100644 --- a/src/bundler/linker_context/writeOutputFilesToDisk.rs +++ b/src/bundler/linker_context/writeOutputFilesToDisk.rs @@ -79,7 +79,7 @@ pub fn write_output_files_to_disk( let bv2: &mut BundleV2 = unsafe { &mut *LinkerContext::bundle_v2_ptr(std::ptr::from_mut::(c)) }; - // PORT NOTE: Zig passes `chunk` (an element of `chunks`) and `chunks` + // Zig passes `chunk` (an element of `chunks`) and `chunks` // together into `code()`/`code_standalone()`. The callee now takes // `&Chunk` / `&[Chunk]` (read-only), so iterate by index and reborrow // shared; the only per-chunk mutation is the `intermediate_output` @@ -151,7 +151,7 @@ pub fn write_output_files_to_disk( &resolver_opts.public_path }; - // PORT NOTE: take `intermediate_output` by value so its `&mut self` is + // Take `intermediate_output` by value so its `&mut self` is // disjoint from the `&chunks[i]` / `&[Chunk]` reads below. let mut intermediate_output = core::mem::take(&mut chunks[chunk_index_in_chunks_list].intermediate_output); @@ -502,11 +502,13 @@ pub fn write_output_files_to_disk( } }), entry_point_index: if output_kind == options::OutputKind::EntryPoint { - // TODO(port): `bake_types::Framework` is missing - // `server_components`; once it lands, restore the - // `if c.framework.is_some_and(|fw| fw.server_components.is_some()) { 3 } else { 1 }` - // branch. - let offset: u32 = 1; + // Server-components builds insert 2 extra synthetic sources + // before user entry points, so the source-index offset is 3. + let offset: u32 = if c.framework.is_some_and(|fw| fw.server_components.is_some()) { + 3 + } else { + 1 + }; Some(chunk.entry_point.source_index() - offset) } else { None @@ -545,7 +547,7 @@ pub fn write_output_files_to_disk( } { - // PORT NOTE: reshaped for borrowck — compute len before mut borrow, + // Reshaped for borrowck — compute len before mut borrow, // bump `total_insertions`, then take the slice. let additional_start = output_files.additional_output_files_start as usize; let additional_len = output_files.output_files.len() - additional_start; diff --git a/src/bundler/options.rs b/src/bundler/options.rs index fa53adadea3e..698be0518de7 100644 --- a/src/bundler/options.rs +++ b/src/bundler/options.rs @@ -11,24 +11,8 @@ use bun_resolver::fs as Fs; use bun_resolver::fs::PathResolverExt as _; use bun_resolver::package_json::{MacroMap as MacroRemap, PackageJSON}; use std::borrow::Cow; -// TODO(b2-blocked): bun_analytics — Cargo.toml does not yet list the dep -// (adding it triggers upstream rebuilds with in-progress breakage). The -// `analytics::features::*` counters are pure telemetry side effects; the -// increment call-sites below are ``-gated until the dep is wired -// so the no-op is explicit (PORTING.md §Forbidden patterns: silent no-ops). - -mod analytics { - #[allow(non_upper_case_globals)] - pub(super) mod features { - use core::sync::atomic::AtomicUsize; - // Zig: `analytics.Features.{define,loaders,macros,external} += n`. - // Real statics live in `bun_analytics::features::*` (AtomicUsize). - pub(crate) static define: AtomicUsize = AtomicUsize::new(0); - pub(crate) static loaders: AtomicUsize = AtomicUsize::new(0); - pub(crate) static macros: AtomicUsize = AtomicUsize::new(0); - pub(crate) static external: AtomicUsize = AtomicUsize::new(0); - } -} +// Zig: `analytics.Features.{define,loaders,macros,external} += n`. +use bun_analytics as analytics; use enum_map::EnumMap; pub use crate::defines; @@ -39,10 +23,8 @@ pub use defines::Define; use crate::defines::{DefineDataExt as _, DefineExt as _}; pub use bun_options_types::global_cache::GlobalCache; -// ── B-2 type aliases for incomplete lower-tier surfaces ── -// TODO(b2-blocked): bun_resolver::package_json::ESModule::ConditionsMap — module -// path doesn't expose this yet; local alias matches Zig `StringArrayHashMap(void)`. -pub type ConditionsMap = StringArrayHashMap<()>; +// Zig `ESModule.ConditionsMap` — canonical alias lives in the resolver. +pub use bun_resolver::package_json::ConditionsMap; // TODO(b2-blocked): bun_sys::Dir — directory handle. Mapped to Fd for now // (matches `bun.FD.fromStdDir` pattern). pub type Dir = bun_sys::Fd; @@ -89,7 +71,7 @@ pub fn validate_path( return Box::default(); } // TODO: switch to getFdPath()-based implementation - // PORT NOTE: Zig used `std.fs.path.resolve(arena, &.{cwd, rel_path})`; + // Zig used `std.fs.path.resolve(arena, &.{cwd, rel_path})`; // `join_abs_string` resolves `.`/`..` against `cwd` into a threadlocal // buffer which is then boxed (matches the arena.dupe in the Zig path). let _ = path_kind; @@ -110,9 +92,6 @@ pub fn validate_path( Box::from(out) } -// PORT NOTE: options.zig `stringHashMapFromArrays` — inline the construction -// (see definesFromTransformOptions / loadersFromTransformOptions below). - // `AllowUnresolved` is defined canonically in // `bun_js_parser::options` (lower tier) because the parser is the consumer // (`P::should_allow_unresolved_dynamic_specifier`). Re-export here so @@ -310,8 +289,8 @@ const DEFAULT_MAIN_FIELDS_BUN: &[&[u8]] = &[ /// `bun_bundler::options` so `use bun_bundler::options::TargetExt;` makes /// `.bake_graph()` etc. available on the single canonical type. pub trait TargetExt: Copy { - // pub const fromJS — deleted: see PORTING.md "*_jsc alias" rule. - // TODO(port): move to *_jsc — bun_bundler_jsc::options_jsc::target_from_js + // `fromJS` lives in `bun_bundler_jsc::options_jsc::target_from_js` + // (PORTING.md "*_jsc alias" rule). fn bake_graph(self) -> crate::bake_types::Graph; fn out_extensions(self) -> StringHashMap<&'static [u8]>; @@ -344,7 +323,6 @@ pub trait TargetExt: Copy { impl TargetExt for Target { fn bake_graph(self) -> crate::bake_types::Graph { - // TODO(b0): bake::Graph arrives from move-in (TYPE_ONLY → bundler) match self { Target::Browser => crate::bake_types::Graph::Client, Target::BakeServerComponentsSsr => crate::bake_types::Graph::Ssr, @@ -399,11 +377,10 @@ pub trait LoaderExt: Copy { fn to_mime_type(self, paths: &[&[u8]]) -> bun_http_types::MimeType::MimeType; fn from_mime_type(mime_type: bun_http::MimeType) -> Loader; - // PORT NOTE: `pub type Map` hoisted to module-level `LoaderEnumMap`. + // `pub type Map` hoisted to module-level `LoaderEnumMap`. fn stdin_name_map() -> LoaderEnumMap { - let mut map: LoaderEnumMap = EnumMap::from_array([b"" as &[u8]; 21]); - // TODO(port): EnumMap::from_array length must match variant count. + let mut map: LoaderEnumMap = EnumMap::from_fn(|_| b"" as &[u8]); map[Loader::Jsx] = b"input.jsx"; map[Loader::Js] = b"input.js"; map[Loader::Ts] = b"input.ts"; @@ -423,10 +400,10 @@ pub trait LoaderExt: Copy { map } - // pub const fromJS — deleted: see PORTING.md "*_jsc alias" rule. - // TODO(port): move to *_jsc — bun_bundler_jsc::options_jsc::loader_from_js + // `fromJS` lives in `bun_bundler_jsc::options_jsc::loader_from_js` + // (PORTING.md "*_jsc alias" rule). - // PORT NOTE: `is_type_script` / `is_java_script_like*` spelling-aliases + // `is_type_script` / `is_java_script_like*` spelling-aliases // moved to inherent `impl Loader` in `bun_options_types::bundle_enums` so // cross-crate callers (bun_jsc / bun_runtime) resolve them without a trait // import. @@ -558,8 +535,6 @@ pub struct LoaderResult<'a> { pub package_json: Option<&'a PackageJSON>, } -// TODO(b2-blocked): bun_paths::path_literal! + Fs::Path::loader + strings::eql_long -// arity — body touches VmLoaderCtx vtable which is real but the helper APIs are not. pub fn get_loader_and_virtual_source<'a>( specifier_str: &'a [u8], jsc_vm: &'a VmLoaderCtx, @@ -863,8 +838,7 @@ impl ESMConditions { }) } - pub fn clone(&self) -> Result { - // TODO(port): narrow error set + pub fn clone(&self) -> Result { let default = self.default.clone()?; let import = self.import.clone()?; let require = self.require.clone()?; @@ -948,7 +922,7 @@ pub fn defines_from_transform_options( None => (&[], &[]), }; - // PORT NOTE: Zig stringHashMapFromArrays — inlined as concrete RawDefines build (over-reserves +4). + // Zig stringHashMapFromArrays — inlined as concrete RawDefines build (over-reserves +4). let mut user_defines: defines::RawDefines = defines::RawDefines::default(); user_defines.reserve(input_keys.len() + 4); for (i, key) in input_keys.iter().enumerate() { @@ -979,7 +953,7 @@ pub fn defines_from_transform_options( break 'load_env; } - // PORT NOTE: flatten `api::StringMap` into parallel borrowed slices. + // flatten `api::StringMap` into parallel borrowed slices. // `api::DotEnvBehavior` is the same type as `DotEnv::DotEnvBehavior` // (re-export), so no conversion needed. let api_defaults = framework.to_api().defaults; @@ -1178,7 +1152,7 @@ pub fn loaders_from_transform_options( loaders.insert(ext, loader_values[i]); } - // PORT NOTE: Zig `getOrPutValue` → contains+insert; `Loader` is not `Default` + // Zig `getOrPutValue` → contains+insert; `Loader` is not `Default` // so the `V: Default`-gated `StringArrayHashMap::get_or_put_value` does not // apply. Semantics are identical (insert only when absent). for ext in DEFAULT_LOADER_EXT { @@ -1206,7 +1180,6 @@ pub fn loaders_from_transform_options( Ok(loaders) } -// PORT NOTE: `Dir` alias hoisted to top of file (= bun_sys::Fd). #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum SourceMapOption { @@ -1241,7 +1214,7 @@ impl SourceMapOption { } } -// PORT NOTE: hoisted from `impl SourceMapOption` — Rust forbids `static` in inherent impls. +// hoisted from `impl SourceMapOption` — Rust forbids `static` in inherent impls. pub static SOURCE_MAP_OPTION_MAP: phf::Map<&'static [u8], SourceMapOption> = phf::phf_map! { b"none" => SourceMapOption::None, b"inline" => SourceMapOption::Inline, @@ -1271,7 +1244,7 @@ impl PackagesOption { } } -// PORT NOTE: hoisted from `impl PackagesOption` — Rust forbids `static` in inherent impls. +// hoisted from `impl PackagesOption` — Rust forbids `static` in inherent impls. pub static PACKAGES_OPTION_MAP: phf::Map<&'static [u8], PackagesOption> = phf::phf_map! { b"external" => PackagesOption::External, b"bundle" => PackagesOption::Bundle, @@ -1306,9 +1279,9 @@ pub struct BundleOptions<'a> { pub hot_module_reloading: bool, pub react_fast_refresh: bool, pub inject: Option]>>, - // TODO(port): lifetime — `bun_url::URL<'a>` borrows its input string. Zig - // stored it borrowing `transform_options.origin` (sibling field). Using the - // owned variant so the struct is self-contained. + // `bun_url::URL<'a>` borrows its input string; Zig stored it borrowing + // `transform_options.origin` (sibling field). The owned variant keeps the + // struct self-contained. pub origin: bun_url::OwnedURL, pub output_dir_handle: Option, @@ -1331,7 +1304,7 @@ pub struct BundleOptions<'a> { pub target: Target, pub main_fields: Box<[Box<[u8]>]>, /// TODO: remove this in favor accessing bundler.log - /// PORT NOTE: raw `*mut` (not `&'a mut`) — Zig aliases the same `*Log` + /// raw `*mut` (not `&'a mut`) — Zig aliases the same `*Log` /// into `Transpiler.log` / `Resolver.log` / `Linker.log`. A stored /// `&'a mut` here would assert uniqueness for `'a` and make every access /// through those sibling raw pointers UB under stacked borrows. @@ -1422,7 +1395,6 @@ pub struct BundleOptions<'a> { // directly — all access goes through crate::dispatch::DevServerVTable. pub dev_server: *const (), /// Set when Bake is bundling. Affects module resolution. - // TODO(b0): bake::Framework arrives from move-in (TYPE_ONLY → bundler) pub framework: Option<&'a crate::bake_types::Framework>, pub serve_plugins: Option]>>, @@ -1690,15 +1662,12 @@ impl<'a> BundleOptions<'a> { !self.defines_loaded } - // TODO(b2-blocked): defines_from_transform_options (see above) + - // api::TransformOptions.define field (peechy `TransformOptions` body still - // opaque). pub fn load_defines( &mut self, arena: &bun_alloc::Arena, loader_: Option<&mut DotEnv::Loader>, ) -> Result<(), bun_core::Error> { - // PORT NOTE: spec `loadDefines(..., env: ?*const options.Env)` had its + // spec `loadDefines(..., env: ?*const options.Env)` had its // sole caller pass `&this.options.env` (transpiler.zig:334). Forwarding // that as `Option<&Env>` forced the caller into an aliased-`&mut` UB // raw-pointer dance under Stacked Borrows. Dropped the param and read @@ -1734,7 +1703,7 @@ impl<'a> BundleOptions<'a> { Some(Cow::Borrowed(b"\"development\"".as_slice())) }; - // PORT NOTE: reshaped for borrowck — node_env computed before passing self.log + // reshaped for borrowck — node_env computed before passing self.log self.define = defines_from_transform_options( // No other `&mut Log` is live across this call (see `log_mut` // caller contract). @@ -1744,7 +1713,8 @@ impl<'a> BundleOptions<'a> { loader_, Some(&self.env), node_env.as_deref(), - // TODO(port): &self.drop is Box<[Box<[u8]>]>, callee wants &[&[u8]] + // `&self.drop` is `Box<[Box<[u8]>]>`; the callee wants `&[&[u8]]`, + // so re-borrow per call (cold path: once per options build). &self.drop.iter().map(|s| s.as_ref()).collect::>(), self.dead_code_elimination && self.minify_syntax, arena, @@ -1780,13 +1750,11 @@ impl<'a> BundleOptions<'a> { .collect::>(), ); - // TODO(port): many fields below have Zig defaults via `= ...`; in Rust we initialize - // each explicitly. Could add a `Default`-ish builder. let mut opts = BundleOptions { footer: Cow::Borrowed(b""), banner: Cow::Borrowed(b""), log, - // PORT NOTE: `define` is `undefined` in Zig and filled by `loadDefines` later; + // `define` is `undefined` in Zig and filled by `loadDefines` later; // initialize empty so the struct is well-formed before `load_defines` runs. define: Box::new(defines::Define { identifiers: Default::default(), @@ -1891,7 +1859,6 @@ impl<'a> BundleOptions<'a> { optimize_imports: None, }; - // TODO(b2-blocked): bun_analytics dep not yet wired in bundler/Cargo.toml { analytics::features::define .fetch_add(usize::from(transform.define.is_some()), Ordering::Relaxed); @@ -1912,7 +1879,7 @@ impl<'a> BundleOptions<'a> { opts.env.disable_default_env_files = transform.disable_default_env_files; if let Some(origin) = &transform.origin { - // PORT NOTE: ownership — `URL<'_>` borrows its input. The Zig + // ownership — `URL<'_>` borrows its input. The Zig // `URL.parse` borrowed `transform.origin` (a sibling of // `opts.transform_options`); here `OwnedURL` owns the href and // callers borrow via `.url()`. @@ -1991,7 +1958,7 @@ impl<'a> BundleOptions<'a> { .collect(); } - // PORT NOTE: Zig passed `log` directly; reborrow the raw `*mut Log` + // Zig passed `log` directly; reborrow the raw `*mut Log` // for the duration of this call only. opts.external = init_external_modules( &mut fs.fs, @@ -2024,7 +1991,7 @@ impl<'a> BundleOptions<'a> { if opts.write && !opts.output_dir.is_empty() { let handle = open_output_dir(&opts.output_dir)?; opts.output_dir_handle = Some(handle); - // PORT NOTE: Zig `fs.getFdPath(.fromStdDir(handle))` interns into + // Zig `fs.getFdPath(.fromStdDir(handle))` interns into // `dirname_store`; the inline `bun_resolver::fs::FileSystem` does // not yet expose `get_fd_path`, so resolve via `bun_sys` and box. let mut buf = bun_paths::PathBuffer::uninit(); @@ -2038,7 +2005,6 @@ impl<'a> BundleOptions<'a> { opts.tsconfig_override = Some(tsconfig.clone()); } - // TODO(b2-blocked): bun_analytics dep not yet wired in bundler/Cargo.toml { analytics::features::macros.fetch_add( usize::from(opts.target == Target::BunMacro), @@ -2099,7 +2065,7 @@ pub mod bundle_options_defaults { } pub fn open_output_dir(output_dir: &[u8]) -> Result { - // PORT NOTE: Zig used `std.fs.cwd().openDir/.makeDir`; routed through + // Zig used `std.fs.cwd().openDir/.makeDir`; routed through // `bun_sys` per CLAUDE.md (never `std::fs`). match bun_sys::open_dir_at(bun_sys::Fd::cwd(), output_dir) { Ok(d) => Ok(d), @@ -2139,7 +2105,7 @@ pub fn open_output_dir(output_dir: &[u8]) -> Result { } /// Port of `fs.zig` `Fs.File` (path + contents pair). `bun_resolver::fs` -/// does not surface this type yet (TODO(b2-blocked)); local mirror keeps +/// does not surface this type; local mirror keeps /// `TransformOptions.entry_point` self-contained. pub struct EntryPointFile { pub path: bun_paths::fs::Path<'static>, @@ -2178,7 +2144,7 @@ impl TransformOptions { }; let mut _cwd: Box<[u8]> = Box::from(b"/".as_slice()); - // TODO(port): Environment.isWasi + // Zig gated on `Environment.isWasi or Environment.isWindows`. #[cfg(any(target_os = "wasi", windows))] { // `getcwd_alloc` returns a NUL-terminated `ZBox`; strip the NUL @@ -2207,7 +2173,8 @@ impl TransformOptions { loader, resolve_dir: Box::from(entry_point_name.dir), entry_point, - // TODO(port): resolve_dir borrows from entry_point in Zig; cloned here + // resolve_dir borrows from entry_point in Zig; cloned here so + // `TransformOptions` stays lifetime-free. main_fields: Target::default_main_fields_map()[Target::Browser], jsx: if loader.is_jsx() { Some(jsx::Pragma::default()) @@ -2274,7 +2241,7 @@ pub struct EnvEntry { type EnvList = MultiArrayList; -// PORT NOTE: `Debug` derive dropped — `MultiArrayList` is not `Debug`. +// `Debug` derive dropped — `MultiArrayList` is not `Debug`. pub struct Env { pub behavior: api::DotEnvBehavior, pub prefix: Box<[u8]>, @@ -2406,7 +2373,7 @@ impl Env { } } -// PORT NOTE: `Debug` derive dropped — `Env` is not `Debug` (MultiArrayList). +// `Debug` derive dropped — `Env` is not `Debug` (MultiArrayList). #[derive(Default)] pub struct EntryPoint { pub path: Box<[u8]>, @@ -2542,7 +2509,7 @@ pub(crate) fn path_template_needs(data: &[u8], field: PlaceholderField) -> bool } // Shared body for PathTemplate::print / PathTemplateConst::print (D064). -// PORT NOTE: Zig `format(self, comptime _, _, writer: anytype)` writes raw path bytes via +// Zig `format(self, comptime _, _, writer: anytype)` writes raw path bytes via // writer.writeAll; mapped to a byte-writer free fn (not `core::fmt::Display`) per // PORTING.md "(comptime X: type, arg: X) writer → &mut impl bun_io::Write (bytes)". pub(crate) fn path_template_print( @@ -2717,7 +2684,7 @@ pub struct Placeholder { pub target: Box<[u8]>, } -// PORT NOTE: hoisted from `impl Placeholder` — Rust forbids `static` in inherent impls. +// hoisted from `impl Placeholder` — Rust forbids `static` in inherent impls. pub static PLACEHOLDER_MAP: phf::Map<&'static [u8], PlaceholderField> = phf::phf_map! { b"dir" => PlaceholderField::Dir, b"name" => PlaceholderField::Name, diff --git a/src/bundler/transpiler.rs b/src/bundler/transpiler.rs index 219d05f478a2..e09cc7052c8d 100644 --- a/src/bundler/transpiler.rs +++ b/src/bundler/transpiler.rs @@ -19,7 +19,7 @@ use crate::options; /// Port of `transpiler.zig:ResolveResults` — keyed by source path hash. pub(crate) type ResolveResults = HashMap; /// Port of `transpiler.zig:ResolveQueue` — `std.fifo.LinearFifo(resolver.Result, .Dynamic)`. -// PORT NOTE: `bun_collections::LinearFifo>` would be exact, +// `bun_collections::LinearFifo>` would be exact, // but `DynamicBuffer` isn't re-exported from `bun_collections` yet. `VecDeque` // is structurally equivalent (growable ring buffer); swap once the re-export lands. pub(crate) type ResolveQueue = std::collections::VecDeque; @@ -123,18 +123,16 @@ pub const fn default_macro_js_value() -> MacroJSCtx { /// on every VM so that the options can be used for transpilation. pub struct Transpiler<'a> { pub options: options::BundleOptions<'a>, - // PORT NOTE: raw ptr — Zig aliased the same `*Log` into `linker.log` and + // Raw ptr — Zig aliased the same `*Log` into `linker.log` and // `resolver.log` (see `set_log`). `&'a mut` would forbid that aliasing. - // TODO(port): lifetime — restructure once linker/resolver own their logs. pub log: *mut bun_ast::Log, - // TODO(port): arena — bundler is an AST crate per PORTING.md so we - // thread an arena, but callers usually pass `bun.default_allocator`. - // Confirm whether this should be removed (global mimalloc) or kept. + // Bundler is an AST crate per PORTING.md, so an arena is threaded even + // though callers usually pass `bun.default_allocator`. pub arena: &'a Arena, pub result: options::TransformResult, pub resolver: Resolver<'a>, - // TODO(port): lifetime — Zig used the global `Fs.FileSystem.instance` - // singleton (`&'static mut`). Raw ptr until the singleton accessor lands. + // Raw ptr — Zig used the global `Fs.FileSystem.instance` singleton + // (`&'static mut`); a stored `&'a mut` would assert uniqueness it cannot have. pub fs: *mut Fs::FileSystem, pub output_files: Vec, pub resolve_results: Box, @@ -149,7 +147,7 @@ pub struct Transpiler<'a> { // by `configure_linker` below; `set_log` keeps `linker.log` in sync. pub linker: crate::linker::Linker, pub timer: SystemTimer, - // TODO(port): lifetime — Zig stored `&DotEnv.Loader` (global singleton). + // Raw ptr — Zig stored `&DotEnv.Loader` (the global singleton). pub env: *mut dot_env::Loader<'a>, pub macro_context: Option, @@ -160,7 +158,7 @@ impl<'a> Transpiler<'a> { /// Port of `transpiler.zig:95 setLog`. /// - /// PORT NOTE: takes `*mut Log` (not `&'a mut`) because Zig aliased the same + /// takes `*mut Log` (not `&'a mut`) because Zig aliased the same /// `*Log` into `linker.log` / `resolver.log`; the un-gated struct field is /// already a raw pointer for that reason. pub fn set_log(&mut self, log: *mut bun_ast::Log) { @@ -176,7 +174,7 @@ impl<'a> Transpiler<'a> { // TODO: remove this method. it does not make sense pub fn set_arena(&mut self, arena: &'a Arena) { self.arena = arena; - // PORT NOTE: `crate::Linker` is the unit stub — no `.arena` field. + // `crate::Linker` is the unit stub — no `.arena` field. // `Resolver` dropped its `arena` field (global mimalloc; see // resolver/lib.rs `// arena: dropped`), so nothing left to thread. } @@ -260,7 +258,7 @@ impl<'a> Transpiler<'a> { /// other `self.*` fields as arguments without a borrow-checker conflict; /// callers must not hold two results live at once, nor hold a result /// across a `self.{resolver,linker}` call that itself writes to the - /// aliased `*mut Log` (see field PORT NOTE — same allocation is threaded + /// aliased `*mut Log` (see field comment — same allocation is threaded /// into `linker.log` / `resolver.log`). #[inline] #[allow(clippy::mut_from_ref)] @@ -471,7 +469,7 @@ impl<'a> Transpiler<'a> { let mut prefixed = Vec::with_capacity(2 + entry_point.len()); prefixed.extend_from_slice(b"./"); prefixed.extend_from_slice(entry_point); - // PORT NOTE: spec leaks the prefixed slice (arena-freed in + // spec leaks the prefixed slice (arena-freed in // Zig). `Resolver::resolve` interns the path internally, // so the heap buffer can drop after the call. if let Ok(r) = self.resolver.resolve( @@ -499,7 +497,7 @@ impl<'a> Transpiler<'a> { let mut cache_bust_buf = bun_paths::PathBuffer::uninit(); // Bust directory cache and try again - // PORT NOTE: reshaped for borrowck — Zig's labelled-block + // reshaped for borrowck — Zig's labelled-block // returned a slice that aliases either `entry_point` (via // `dirname`) or `cache_bust_buf`. Rust can't unify the two // disjoint mutable borrows of `cache_bust_buf` across `break`, @@ -578,7 +576,7 @@ impl<'a> Transpiler<'a> { let env_loader = self.env_mut(); let mut is_production = env_loader.is_production(); - // PORT NOTE: spec (`transpiler.zig:314`) eagerly did + // spec (`transpiler.zig:314`) eagerly did // `Expr.Data.Store.create()` / `Stmt.Data.Store.create()` plus a // `defer Store.reset()` here, purely so `defines.zig`'s `parse_env_json` // had a thread-local AST store to build `E::String` nodes in. That work @@ -634,7 +632,7 @@ impl<'a> Transpiler<'a> { #[inline(never)] pub fn dump_environment_variables(&self) { use bun_js_printer::{Encoding, write_json_string}; - // PORT NOTE: spec uses `std.json.Stringify` (`.whitespace = .indent_2`) + // spec uses `std.json.Stringify` (`.whitespace = .indent_2`) // to dump `env.map.*`. The Rust `bun_dotenv::Map` doesn't impl // `serde::Serialize`, so iterate and emit the object by hand. Keys and // values go through `write_json_string` (the same escaper the printer @@ -690,14 +688,14 @@ fn merge_tsconfig_jsx_into(tsconfig: &TSConfigJSON, out: &mut crate::options_imp impl<'a> Transpiler<'a> { /// Port of `transpiler.zig:233 configureLinkerWithAutoJSX`. pub fn configure_linker_with_auto_jsx(&mut self, auto_jsx: bool) { - // PORT NOTE: `Linker::init` dropped its `arena` arg (linker.rs:172 + // `Linker::init` dropped its `arena` arg (linker.rs:172 // — global mimalloc). Zig stored borrowed `*T` into the linker; the // un-gated `crate::linker::Linker` mirrors that with raw pointers so // `&mut self.options` etc. coerce directly. Self-reference is // load-bearing — `linker.link()` reads back through these into the // owning `Transpiler` — hence raw `*mut`, not `&'a mut` (would alias // `&mut self` on every call). - // PORT NOTE: `.cast()` on the `options`/`resolver` pointers erases the + // `.cast()` on the `options`/`resolver` pointers erases the // `<'a>` lifetime parameter — `Linker` stores them as // `*mut BundleOptions` / `*mut Resolver` with an (implicit) distinct // lifetime. Raw-pointer storage is the Zig contract; the linker never @@ -735,7 +733,6 @@ impl<'a> Transpiler<'a> { /// Port of `transpiler.zig:263 runEnvLoader`. pub fn run_env_loader(&mut self, skip_default_env: bool) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set use bun_options_types::schema::api::DotEnvBehavior; // Derived once up front; no other live `&mut` to this `Loader` exists // for the duration of this call (Zig accessed `this.env.*` freely). @@ -789,7 +786,7 @@ impl<'a> Transpiler<'a> { // sole `&mut` for the call. let dir: &mut bun_resolver::fs::DirEntry = unsafe { &mut *dir }; - // PORT NOTE: `Env.files: Box<[Box<[u8]>]>` but `Loader::load` + // `Env.files: Box<[Box<[u8]>]>` but `Loader::load` // wants `&[&[u8]]`. Re-borrow into a small Vec; the explicit // `--env-file` list is bounded (CLI args), not hot-path. // PERF(port): one tiny alloc — Zig passed the slice directly. @@ -873,7 +870,7 @@ impl AlreadyBundled { } /// Port of `transpiler.zig:ParseResult`. -// PORT NOTE: lifetime-free — `runtime_transpiler_cache` is a raw pointer (Zig +// lifetime-free — `runtime_transpiler_cache` is a raw pointer (Zig // `?*RuntimeTranspilerCache`) so `AsyncModule.parse_result` / `JSTranspiler` // can store this by value without threading a borrow lifetime. pub struct ParseResult<'a> { @@ -883,15 +880,15 @@ pub struct ParseResult<'a> { pub already_bundled: AlreadyBundled, pub input_fd: Option, pub empty: bool, - // PORT NOTE: Zig `_resolver.PendingResolution.List` is + // Zig `_resolver.PendingResolution.List` is // `MultiArrayList(PendingResolution)`. `PendingResolution` does not yet // derive `MultiArrayElement` (lives in `bun_resolver`, derive macro is in // `bun_collections_macros` — orphan rules forbid impl-ing it here), so the // SoA `len()`/column accessors aren't reachable. Use AoS `Vec` for now; // `is_pending_import` only scans `import_record_id`, so the layout // difference is observable only as a SoA→AoS perf delta. - // TODO(b3): switch back to `MultiArrayList` once the - // derive lands upstream in `bun_resolver`. + // Once the `MultiArrayElement` derive lands upstream in `bun_resolver`, + // this can switch back to `MultiArrayList`. pub pending_imports: Vec, /// Zig: `?*bun.RuntimeTranspilerCache`. SAFETY: erased — bundler stores it @@ -917,7 +914,7 @@ impl<'a> ParseResult<'a> { pub fn empty(arena: &'a bun_alloc::Arena) -> Self { ParseResult { source: Default::default(), - // PORT NOTE: `options::Loader` has no `Default`; Zig field had no + // `options::Loader` has no `Default`; Zig field had no // initializer either. `File` is the resolver's neutral fallback // (BundleEnums.rs:353), and `Default` here exists only for // `mem::take` in `AsyncModule::resume_loading_module`. @@ -957,7 +954,7 @@ impl<'a> ParseResult<'a> { pub fn is_pending_import(&self, id: u32) -> bool { // Spec transpiler.zig:43-47: scan `pending_imports.items(.import_record_id)` for `id`. - // PORT NOTE: AoS scan (see field comment); SoA column iteration restored + // AoS scan (see field comment); SoA column iteration restored // when `PendingResolution: MultiArrayElement` lands. self.pending_imports .iter() @@ -1044,7 +1041,7 @@ fn to_parser_module_type( /// Spec: `fs.zig:FileSystem.init`. /// -/// PORT NOTE: the inline `bun_resolver::fs` module exposes the `FileSystem` +/// the inline `bun_resolver::fs` module exposes the `FileSystem` /// struct + `INSTANCE`/`INSTANCE_LOADED` statics (resolver/lib.rs:120,129) but /// not the `init` constructor (that lives in the still-gated file-backed /// `resolver/fs.rs`). All fields are `pub` and `EntriesMap`/`Mutex` have @@ -1079,7 +1076,7 @@ fn init_file_system( /// `Box<[Box<[u8]>]>`/`StringSet`/`StringArrayHashMap` so this is a faithful /// value copy rather than a `Default` stub. /// -/// TODO(b3): drop this once `bun_options_types::BundleOptions` exists and both +/// This projection can be dropped once `bun_options_types::BundleOptions` exists and both /// crates re-export it — `Resolver::init1` will then take the canonical type /// directly and Zig's `bundle_options` value can flow through unchanged /// (transpiler.zig:209 passes the same `options` to both struct fields). @@ -1193,7 +1190,7 @@ impl<'a> Transpiler<'a> { /// * [`options::BundleOptions::from_api`] — `bun_bundler::options` /// * [`Resolver::init1`] — `bun_resolver` /// - /// PORT NOTE: `log` / `env_loader_` are raw pointers (not `&'a mut`) to + /// `log` / `env_loader_` are raw pointers (not `&'a mut`) to /// match the un-gated struct field types — Zig aliased the same `*Log` /// into `linker.log` / `resolver.log` (see `set_log`). pub fn init( @@ -1230,7 +1227,6 @@ impl<'a> Transpiler<'a> { // deref sites below go through `NonNull` rather than the raw argument. let log_nn = core::ptr::NonNull::new(log).expect("Transpiler::init_in_place: log is non-null"); - // TODO(port): narrow error set bun_ast::expr::data::Store::create(); bun_ast::stmt::data::Store::create(); // These two `create()`s are eager (not deferred to the first `parse()`) @@ -1253,7 +1249,7 @@ impl<'a> Transpiler<'a> { // `reset()` branches to `enter()` on null ARENA), so per-file ASTs // *do* get the side arena from the first parsed file onward. - // PORT NOTE: `FileSystem::init` wants `&'static [u8]`; Zig passed a + // `FileSystem::init` wants `&'static [u8]`; Zig passed a // borrowed slice (transpiler.zig:179). Intern via `DirnameStore` // (the same path `FileSystem::init` already uses for the // `None`/getcwd case — fs.rs:222) so the cwd lives in the @@ -1280,9 +1276,6 @@ impl<'a> Transpiler<'a> { // Transfer ownership of both allocations into the global // singleton via `heap::alloc` (the AtomicPtr becomes the // owner; matches `MiniEventLoop::init_global`). - // TODO(port): replace with a `OnceLock`-backed - // `bun_dotenv::instance_or_init()` accessor once - // `bun_dotenv` grows one (PORTING.md §Concurrency). let map: *mut dot_env::Map = bun_core::heap::into_raw(Box::new(dot_env::Map::init())); // SAFETY: `map` is a fresh heap allocation with no other @@ -1407,10 +1400,9 @@ impl<'a> Transpiler<'a> { >( &mut self, mut this_parse: ParseOptions<'a, '_>, - // TODO(port): Zig `anytype` + `@hasField(.., "source")` — only ever - // called with `?*EntryPoints.ClientEntryPoint` in this file. If other - // callers pass a different type, introduce a `ClientEntryPointLike` - // trait with `fn source() -> Option<&Source>`. + // Zig took `anytype` + `@hasField(.., "source")`; the only caller + // type is `?*EntryPoints.ClientEntryPoint`, so the param is typed + // concretely. client_entry_point_: Option<&mut EntryPoints::ClientEntryPoint>, ) -> Option> { let arena = this_parse.arena; @@ -1434,7 +1426,7 @@ impl<'a> Transpiler<'a> { // (`Drop` is a no-op). let mut source_backing: resolver::cache::Contents = resolver::cache::Contents::Empty; - // PORT NOTE: Zig `&brk: { ... }` took the address of a temporary; Rust + // Zig `&brk: { ... }` took the address of a temporary; Rust // owns the value and borrows it after the block. let source: &'a bun_ast::Source = arena.alloc('brk: { if let Some(virtual_source) = this_parse.virtual_source { @@ -1533,7 +1525,7 @@ impl<'a> Transpiler<'a> { if let Some(file_fd_ptr) = this_parse.file_fd_ptr { *file_fd_ptr = entry.fd; } - // PORT NOTE: `Source.contents: &'static [u8]` (the AST crate's `Str` + // `Source.contents: &'static [u8]` (the AST crate's `Str` // convention). The bytes live either in the per-thread shared // buffer (`USE_SHARED_BUFFER` → `Contents::SharedBuffer`, no-op // drop) or in `this_parse.arena` (`Contents::Arena`, no-op drop — @@ -1606,7 +1598,7 @@ impl<'a> Transpiler<'a> { jsx.parse = loader.is_jsx(); let _ = &this_parse.macro_remappings; - // PORT NOTE: `ParserOptions::init` is hard-typed + // `ParserOptions::init` is hard-typed // `-> Options<'static>` and `Options<'a>` is *invariant* in // `'a` (it holds `Option<&'a mut MacroContext>`), so an // `Options<'static>` cannot be passed to @@ -1690,8 +1682,8 @@ impl<'a> Transpiler<'a> { // .bundler_feature_flags`. Zig aliased a `*const StringSet`; // `Features.bundler_feature_flags` is currently owned // (`Option>`), so clone by value. - // TODO(refactor): change the parser-side field to - // `Option<&'a StringSet>` to avoid the clone. + // (Changing the parser-side field to `Option<&'a StringSet>` + // would avoid the clone.) // The clone drops with `opts` — no leak. opts.features.bundler_feature_flags = self .options @@ -1748,7 +1740,7 @@ impl<'a> Transpiler<'a> { .map(|m| &mut *core::ptr::from_mut(m)); } - // PORT NOTE: spec calls `transpiler.resolver.caches.js.parse`. + // spec calls `transpiler.resolver.caches.js.parse`. // The resolver-side `cache::JavaScript` is a fieldless // shell with no `parse` body (resolver/lib.rs:1664); // the real `parse` lives on `crate::cache::JavaScript`. Both @@ -1805,7 +1797,7 @@ impl<'a> Transpiler<'a> { if this_parse.virtual_source.is_none() && this_parse.allow_bytecode_cache { - // PORT NOTE: `bun.bytecode_extension` + // `bun.bytecode_extension` // (bun.zig:3502) — no Rust const re-export // in `bun_core` yet, so inline the literal. const BYTECODE_EXT: &[u8] = b".jsc"; @@ -1819,7 +1811,7 @@ impl<'a> Transpiler<'a> { // `path_buf2[total] == 0` already; safe to // borrow as a NUL-terminated ZStr. let zpath = bun_core::ZStr::from_buf(&path_buf2[..], total); - // PORT NOTE: spec calls + // spec calls // `bun.sys.File.toSourceAt(...)` which is // `read_from` + wrap-in-`bun_ast::Source`. // We only need `.contents`, so call @@ -1926,7 +1918,7 @@ fn parse_data_loader<'a>( // => |kind|` — comptime monomorphization per loader; profile if it // shows up on a hot path. // - // PORT NOTE: `bun_parsers::*` parse into the T2 value AST + // `bun_parsers::*` parse into the T2 value AST // (`bun_ast::Expr`); lift into the full T4 // `bun_ast::Expr` via the deep-convert `From` bridge // (Expr.rs:1265) so the StoreRef-backed accessors below work. @@ -1964,7 +1956,7 @@ fn parse_data_loader<'a>( let mut symbols: Vec = Vec::new(); - // PORT NOTE: reshaped — Zig `arena.alloc(Part, 1)` returned + // reshaped — Zig `arena.alloc(Part, 1)` returned // an arena slice, but `Ast::from_parts` takes `Box<[Part]>` // (Vec owns its buffer). The single-part array is built on // the global heap; `stmts` stays arena-backed (`*mut [Stmt]`). @@ -1990,7 +1982,7 @@ fn parse_data_loader<'a>( let properties: &mut [bun_ast::G::Property] = obj.properties.slice_mut(); if !properties.is_empty() { let n = properties.len(); - // PORT NOTE: Zig `expandToCapacity()` / `arena.alloc(Symbol, n)` + // Zig `expandToCapacity()` / `arena.alloc(Symbol, n)` // leave slots uninitialized, which is inert in Zig. // The loop below writes sparsely at index `i` and // `continue`s on `"default"` / duplicate keys, so @@ -2002,7 +1994,7 @@ fn parse_data_loader<'a>( let mut decls: Vec = vec![bun_ast::G::Decl::default(); n]; symbols.resize_with(n, Default::default); - // PORT NOTE: `S::ExportClause.items: *mut [ClauseItem]` + // `S::ExportClause.items: *mut [ClauseItem]` // is arena-owned; `ClauseItem: Default` so // `alloc_slice_fill_default` is fine. let export_clauses = arena.alloc_slice_fill_default::(n); @@ -2010,7 +2002,7 @@ fn parse_data_loader<'a>( bun_collections::StringHashMap::default(); // duplicate_key_checker drops at end of scope (defer .deinit()) let mut count: usize = 0; - // PORT NOTE: reshaped for borrowck — cannot zip 4 + // reshaped for borrowck — cannot zip 4 // slices with one mutable borrow into `decls` and // also random-access `decls[prev]`. for i in 0..n { @@ -2038,7 +2030,7 @@ fn parse_data_loader<'a>( Some(prop.value.expect("infallible: prop has value")); continue; } - // PORT NOTE: spec transpiler.zig:1030-1071 + // spec transpiler.zig:1030-1071 // writes at `i` and shrinks to `count`, leaving // holes when `"default"` / duplicates `continue` // — a latent spec bug. Write densely at `count` @@ -2318,7 +2310,7 @@ fn parse_unsupported_loader(loader: options::Loader, path: &bun_paths::fs::Path< // `ModuleLoader::transpile_source_code` (jsc_hooks.rs spec :525-539); the // dispatch shim that `RuntimeTranspilerStore` / `AsyncModule` link against. // -// PORT NOTE: `comptime format: js_printer.Format` demoted to a runtime arg — +// `comptime format: js_printer.Format` demoted to a runtime arg — // `bun_js_printer::Format` doesn't derive `ConstParamTy` (and can't be added // from this crate). All callers pass a literal anyway; the inner // `print_ast::<_, ASCII_ONLY, ENABLE_SOURCE_MAP>` keeps both real comptime @@ -2327,7 +2319,7 @@ fn parse_unsupported_loader(loader: options::Loader, path: &bun_paths::fs::Path< // ══════════════════════════════════════════════════════════════════════════ use bun_js_printer as js_printer; -// PORT NOTE: `module_info` threads the *printer's* `analyze_transpiled_module::ModuleInfo` +// `module_info` threads the *printer's* `analyze_transpiled_module::ModuleInfo` // (the producer), not `crate::analyze_transpiled_module::ModuleInfo` (the // richer consumer-side mirror). The print // path only ever fills the printer-owned one and hands its serialized bytes to @@ -2337,7 +2329,7 @@ use js_printer::analyze_transpiled_module; /// Map the bundler-local `Target` (options.rs:489) to the lower-tier /// `bun_ast::Target` consumed by `js_printer::Options`. /// The two enums are variant-for-variant identical but nominally distinct; -/// TODO(refactor): collapse them (see lib.rs `pub mod options` shadow note). +/// they could be collapsed (see lib.rs `pub mod options` shadow note). #[inline] fn to_bundle_enums_target(t: crate::options_impl::Target) -> bun_ast::Target { use bun_ast::Target as T; @@ -2378,14 +2370,17 @@ impl<'a> Transpiler<'a> { runtime_transpiler_cache: Option>, module_info: Option<*mut analyze_transpiled_module::ModuleInfo>, ) -> Result { - // TODO(port): narrow error set - // TODO(port): `bun.perf.trace("JSPrinter.printWithSourceMap")` / - // `("JSPrinter.print")` — `bun_perf::trace` now takes a `PerfEvent` - // enum and neither variant is in `generated_perf_trace_events.rs` - // yet. Re-add once `scripts/generate-perf-trace-events.sh` runs - // against the Rust tree. - - // PORT NOTE: Zig built `Symbol.NestedList.fromBorrowedSliceDangerous( + // Zig: `bun.perf.trace("JSPrinter.printWithSourceMap")` / + // `("JSPrinter.print")`. Routed through the T0 ftrace subset like the + // other bundler spans (`Bundler.computeChunks` etc.) — + // `bun_perf::PerfEvent` has no JSPrinter variants yet. + let _trace = bun_core::perf::trace(if ENABLE_SOURCE_MAP { + "JSPrinter.printWithSourceMap" + } else { + "JSPrinter.print" + }); + + // Zig built `Symbol.NestedList.fromBorrowedSliceDangerous( // &.{ast.symbols})` — aliased the stack-one-slice into the map. Rust // can't borrow `ast.symbols` while moving `ast` into `print_ast`, so // take the column out (the printer never reads `tree.symbols`; it @@ -2451,7 +2446,7 @@ impl<'a> Transpiler<'a> { ), js_printer::Format::EsmAscii => { - // PORT NOTE: `switch (target.isBun()) { inline else => |is_bun| ... }` + // `switch (target.isBun()) { inline else => |is_bun| ... }` // — runtime bool → comptime dispatch. Hoisted into the // `print_ast_esm_ascii` helper so the const-generic IS_BUN can // also drive `module_type`. @@ -2608,7 +2603,7 @@ impl<'a> Transpiler<'a> { ) } - // PORT NOTE: hoisted from `inline else => |is_bun|` arm of + // hoisted from `inline else => |is_bun|` arm of // print_with_source_map_maybe to express the comptime bool dispatch as a // const generic. #[allow(clippy::too_many_arguments)] @@ -2718,7 +2713,7 @@ impl<'a> Transpiler<'a> { handler: js_printer::SourceMapHandler<'_>, module_info: Option<*mut analyze_transpiled_module::ModuleInfo>, ) -> Result { - // PORT NOTE: env_var feature_flag getters return `Option` + // env_var feature_flag getters return `Option` // (Some(default) when unset); Zig's `.get()` is plain `bool`. if bun_core::env_var::feature_flag::BUN_FEATURE_FLAG_DISABLE_SOURCE_MAPS .get() @@ -2816,14 +2811,14 @@ impl<'a> Transpiler<'a> { /// Port of `transpiler.zig:1254 enqueueEntryPoints`. /// - /// PORT NOTE: the Zig version writes the resolved entry results into a + /// the Zig version writes the resolved entry results into a /// caller-provided `[]Result` slice; the only caller (`transform`) discards /// that slice immediately, so the Rust port returns only the count and lets /// `linker.enqueue_resolve_result` push directly onto `resolve_queue`. fn enqueue_entry_points(&mut self) -> usize { let mut entry_point_i: usize = 0; - // PORT NOTE: snapshot entry points so the `&mut self` resolver call + // snapshot entry points so the `&mut self` resolver call // does not conflict with the `&self.options` borrow. let entries: Vec> = self.options.entry_points.to_vec(); let top_level_dir = self.fs().top_level_dir; @@ -2988,7 +2983,7 @@ impl<'a> Transpiler<'a> { let Some(file_path_ref) = resolve_result.path_const() else { return Ok(None); }; - // PORT NOTE: `resolver::Result.path_pair` carries `bun_resolver::fs::Path<'_>`; + // `resolver::Result.path_pair` carries `bun_resolver::fs::Path<'_>`; // downstream `linker.link`/`get_hashed_filename` and `OutputFile.src_path` // expect `bun_paths::fs::Path<'_>` / `bun_paths::fs::Path<'static>`. Re-init via // `text` (the only field both shapes share semantically). @@ -3038,15 +3033,17 @@ impl<'a> Transpiler<'a> { | options::Loader::Json5 | options::Loader::Text | options::Loader::Md => { - // PORT NOTE: borrowck — `parse` consumes `&mut self`, so capture + // borrowck — `parse` consumes `&mut self`, so capture // the option fields needed for `ParseOptions` first. let jsx = jsx_pragma_from_resolver(&resolve_result.jsx); let dirname_fd = resolve_result.dirname_fd; let emit_decorator_metadata = resolve_result.flags.emit_decorator_metadata(); let experimental_decorators = resolve_result.flags.experimental_decorators(); - // TODO(port): `MacroRemap` (StringArrayHashMap of StringArrayHashMap) - // has no nested `Clone` impl; the Zig copied it by value. Re-key - // shallowly here matching the build-command conversion. + // `MacroRemap` (StringArrayHashMap of StringArrayHashMap) has + // no nested `Clone` impl (the inner clone is fallible); Zig + // copied it by value. Rebuild the outer map, deep-cloning + // each inner map (fallible), matching the build-command + // conversion. let macro_remappings = { let mut m = MacroRemap::default(); for (k, v) in self.options.macro_remap.iter() { @@ -3215,8 +3212,8 @@ impl<'a> Transpiler<'a> { // the CSS AST it backs is dropped before this fn returns // (only `result.code: Vec` escapes, which is // global-heap). `'static` matches the crate-wide erasure - // on `StyleSheet`/`ParserOptions` (see css_parser.rs - // TODO(port): 'bump threading). + // on `StyleSheet`/`ParserOptions` (see the css_parser.rs + // `'bump`-threading note). let alloc: &'static Arena = unsafe { bun_ptr::detach_lifetime_ref::(self.arena) }; let (mut sheet, extra) = match bun_css::StyleSheet::::parse( diff --git a/src/bundler_jsc/analyze_jsc.rs b/src/bundler_jsc/analyze_jsc.rs index d4313d4eb65d..82232921c7f0 100644 --- a/src/bundler_jsc/analyze_jsc.rs +++ b/src/bundler_jsc/analyze_jsc.rs @@ -28,10 +28,9 @@ pub(crate) extern "C" fn zig__ModuleInfoDeserialized__toJSModuleRecord( // SourceProvider cache. // Slice-field validity / alignment caveats are documented on the - // `ModuleInfoDeserialized` accessors. - // TODO(port): switch element reads to `read_unaligned` per the upstream - // note in `analyze_transpiled_module.rs` if a strict-alignment target is - // ever added. + // `ModuleInfoDeserialized` accessors. If a strict-alignment target is ever + // added, switch element reads to `read_unaligned` per the upstream note in + // `analyze_transpiled_module.rs`. let strings_buf: &[u8] = res.strings_buf(); let strings_lens: &[u32] = res.strings_lens(); let requested_modules_keys: &[StringID] = res.requested_modules_keys(); @@ -217,7 +216,6 @@ pub(crate) extern "C" fn zig__ModuleInfoDeserialized__toJSModuleRecord( } // ─── opaque FFI types ───────────────────────────────────────────────────────── -// TODO(port): move to bundler_jsc_sys bun_opaque::opaque_ffi! { pub struct VariableEnvironment; } unsafe extern "C" { diff --git a/src/bunfig/arguments.rs b/src/bunfig/arguments.rs index 5bea346d3e94..ff128d009e57 100644 --- a/src/bunfig/arguments.rs +++ b/src/bunfig/arguments.rs @@ -62,7 +62,7 @@ fn load_bunfig( bun_ast::expr::data::Store::create(); let _store_reset = bun_ast::StoreResetGuard::new(); - // PORT NOTE: reshaped for borrowck — `defer { ctx.log.level = original }` + // Reshaped for borrowck: Zig's `defer { ctx.log.level = original }` // would capture `&mut *ctx.log` past the `Bunfig::parse(.., ctx)` reborrow. // Route through the raw `*mut Log` (process-lifetime, set in // `create_context_data()`); the guard restores `level` on unwind/return. @@ -100,9 +100,10 @@ pub fn load_config_path( config_path: &ZStr, ctx: Context<'_>, ) -> Result<(), bun_core::Error> { - // PORT NOTE: `comptime cmd.readGlobalConfig()` demoted to runtime — see - // `parse()` PORT NOTE; `Tag::read_global_config` is a const-ish lookup so - // the dead arm is still a single branch. + // Zig's `comptime cmd.readGlobalConfig()` is demoted to runtime here (see + // the note on `Parser::parse` in src/bunfig/bunfig.rs); + // `Tag::read_global_config` is a const-ish + // lookup so the dead arm is still a single branch. if cmd.read_global_config() { if let Err(err) = load_global_bunfig(cmd, ctx) { if auto_loaded { @@ -202,7 +203,7 @@ pub fn load_config( ctx.args.absolute_working_dir = Some(Box::<[u8]>::from(&secondbuf[..cwd_len])); } - // PORT NOTE: reshaped for borrowck — `join_abs_string_buf` ties the + // Reshaped for borrowck: `join_abs_string_buf` ties the // returned slice's lifetime to both `cwd` (borrowed from `ctx.args`) // and `config_buf`. We only need the length to NUL-terminate and // re-wrap, so capture `joined.len()` and drop the `ctx` borrow before diff --git a/src/bunfig/bunfig.rs b/src/bunfig/bunfig.rs index 8ed18c0faf5e..f245183fd785 100644 --- a/src/bunfig/bunfig.rs +++ b/src/bunfig/bunfig.rs @@ -148,7 +148,7 @@ pub(crate) struct Parser<'a> { json: Expr, source: &'a bun_ast::Source, log: &'a mut bun_ast::Log, - // PORT NOTE: Zig held both `bunfig: *api.TransformOptions` (= `&ctx.args`) + // Zig held both `bunfig: *api.TransformOptions` (= `&ctx.args`) // and `ctx: *Command.Context` simultaneously. Rust forbids the overlapping // borrow, so `bunfig` writes route through `self.ctx.args` directly. ctx: &'a mut ContextData, @@ -348,7 +348,7 @@ impl<'a> Parser<'a> { Ok(api::StringMap { keys, values }) } - // PORT NOTE: `comptime cmd: Command.Tag` demoted to a runtime arg — + // Zig's `comptime cmd: Command.Tag` is demoted to a runtime arg — // `bun_options_types::command_tag::Tag` does not derive `ConstParamTy` (it // already derives `enum_map::Enum`, which conflicts). The Zig original // monomorphised over `cmd` purely to dead-code-eliminate untaken arms; the @@ -1168,7 +1168,7 @@ impl Bunfig { } }; - // PORT NOTE: reshaped for borrowck — Zig stored both `&mut ctx` and + // Reshaped for borrowck: Zig stored both `&mut ctx` and // `&mut ctx.args` simultaneously inside Parser. In Rust we route bunfig // writes through `self.ctx.args` directly. `log` is derived from the // copied raw pointer above so it does not overlap the `&mut ctx` borrow. @@ -1259,7 +1259,7 @@ impl<'a> Parser<'a> { } fn parse_install(&mut self, install_obj: &Expr) -> Result<(), bun_core::Error> { - // PORT NOTE: Zig held `*BunInstall` and `*Parser` simultaneously. + // Zig held `*BunInstall` and `*Parser` simultaneously. // The helper methods (`expect*`, `add_error`, `parse_registry`) take // `&mut self`, which under Stacked Borrows would invalidate any // long-lived `&mut` derived from `self.ctx.install`. Move the box diff --git a/src/cares_sys/c_ares.rs b/src/cares_sys/c_ares.rs index 06d933ddff87..9a80abaa3bf6 100644 --- a/src/cares_sys/c_ares.rs +++ b/src/cares_sys/c_ares.rs @@ -120,7 +120,7 @@ pub enum NSClass { // Zig: `enum(c_int) { ..., _ }` (non-exhaustive). Values are only ever // constructed in Rust and passed *to* C, so a plain repr(i32) enum is sound. -// TODO(port): if c-ares ever returns an NSType, switch to a transparent newtype. +// If c-ares ever returns an NSType, this must become a transparent newtype. #[repr(i32)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum NSType { @@ -314,8 +314,6 @@ pub struct struct_hostent { // stable, so the wrappers below are reshaped to a trait: the implementing // type provides the callback as a trait method, and the `extern "C"` thunk is // monomorphized per `T: Trait`. -// TODO(port): a proc-macro may be cleaner if many callsites need distinct -// callbacks on the same `Type`. // ────────────────────────────────────────────────────────────────────────── pub trait HostentHandler: Sized { @@ -467,23 +465,10 @@ pub trait HostentWithTtlsHandler: Sized { impl hostent_with_ttls { // toJSResponse alias deleted — lives in bun_runtime::dns_jsc. - pub unsafe extern "C" fn host_callback_wrapper( - ctx: *mut c_void, - status: c_int, - timeouts: c_int, - hostent: Option>, - ) { - // TODO(port): Zig declared this as `ares_host_callback` (4th arg - // `?*hostent_with_ttls`) but that signature mismatches the C - // `ares_host_callback` (`?*struct_hostent`). Appears unused; verify. - // SAFETY: ctx was passed as *mut T to the ares call that registered this thunk. - let this = unsafe { bun_core::callback_ctx::(ctx) }; - if status != ARES_SUCCESS { - this.on_hostent_with_ttls(Error::get(status), timeouts, None); - return; - } - this.on_hostent_with_ttls(None, timeouts, hostent); - } + // Zig also declared a `hostCallbackWrapper` here whose 4th arg was + // `?*hostent_with_ttls`, force-cast to `ares_host_callback` (which passes + // `?*struct_hostent`) — type-confused and never registered with c-ares. + // It is not ported. pub unsafe extern "C" fn callback_wrapper( ctx: *mut c_void, @@ -675,17 +660,10 @@ impl AddrInfo { unsafe { core::ffi::CStr::from_ptr(self.name_) }.to_bytes() } - #[inline] - pub fn cnames(&self) -> &[AddrInfo_node] { - // TODO(port): Zig used `bun.span` on a [*c]AddrInfo_cname (sentinel- - // terminated linked list), returning `[]const AddrInfo_node` — note - // the type mismatch (cname vs node) in the original. This appears - // unused; preserving the empty-slice fast path only. - if self.cnames_.is_null() { - return &[]; - } - &[] - } + // Zig also exposed `cnames()`, which called `bun.span` on a + // `[*c]AddrInfo_cname` (a linked list, not a sentinel-terminated array) + // and returned `[]const AddrInfo_node` — type-confused and unused. It is + // not ported; consumers walk `cnames_` / `node` pointer chains directly. pub unsafe extern "C" fn callback_wrapper( ctx: *mut c_void, @@ -753,7 +731,7 @@ pub trait ChannelContainer: Sized { /// Trait for `Channel::resolve`: ties a lookup-name string to its NSType and /// the `extern "C"` parse-thunk used as the ares_callback. -/// TODO(port): Zig dispatched via `@field(NSType, "ns_t_" ++ lookup_name)` and +/// Zig dispatched via `@field(NSType, "ns_t_" ++ lookup_name)` and /// `cares_type.callbackWrapper(lookup_name, Type, callback)`. This trait is the /// Rust-side reshaping; the dns_jsc consumer impls it per (T, record-type). pub trait ResolveHandler: Sized { @@ -1972,10 +1950,8 @@ impl Error { EAI::MEMORY => Some(Error::ENOMEM), EAI::SERVICE => Some(Error::ESERVICE), EAI::SYSTEM => Some(Error::ESERVFAIL), - _ => { - // TODO(port): bun.todo(@src(), Error.ENOTIMP) - Some(Error::ENOTIMP) - } + // Any EAI code not mapped above is reported as "not implemented". + _ => Some(Error::ENOTIMP), } } } @@ -2221,9 +2197,19 @@ pub fn get_sockaddr(addr: &[u8], port: u16, sa: &mut sockaddr) -> c_int { -1 } -// Zig: `struct_in_addr = std.posix.sockaddr.in` — note this aliases the full -// sockaddr_in (not the 4-byte in_addr). Preserved for ABI parity in `Options.servers`. -// TODO(port): verify against c-ares header; this looks like a Zig-side misnomer. -type in_addr = sockaddr_in; +/// The C `struct in_addr` (4-byte IPv4 address), as c-ares' `ares_options.servers` +/// and the `ares_addr_node`/`ares_addr_port_node` unions declare it. +/// +/// Zig aliased this to `std.posix.sockaddr.in` (the full 16-byte sockaddr_in) — +/// a misnomer that never bit because the only uses are behind pointers +/// (`Options.servers`, never dereferenced; Bun never sets `ARES_OPT_SERVERS`) +/// or as the smaller arm of a union whose size/alignment are pinned by the +/// 16-byte `addr6` arm and whose contents are only accessed through type-erased +/// pointers (`addr_ptr`/`addr_mut_ptr`). Defined correctly here. +#[repr(C)] +#[derive(Copy, Clone)] +pub struct in_addr { + pub s_addr: u32, +} // ported from: src/cares_sys/c_ares.zig diff --git a/src/clap/args.rs b/src/clap/args.rs index c35b2147df1b..6f2fca38b1b5 100644 --- a/src/clap/args.rs +++ b/src/clap/args.rs @@ -61,8 +61,7 @@ impl<'a> ArgIter<'a> for SliceIterator<'a> { /// An argument iterator which wraps the ArgIterator in ::std. /// On windows, this iterator allocates. pub struct OsIterator { - // PORT NOTE: the Zig `arena: bun.ArenaAllocator` field was dropped — non-AST crate, - // and `remain` borrows the process-global argv so nothing is allocated per-call. + // `remain` borrows the process-global argv, so nothing is allocated per-call. pub remain: &'static [&'static [u8]], /// The executable path (this is the first argument passed to the program) @@ -81,8 +80,6 @@ impl OsIterator { res } - // PORT NOTE: `deinit` dropped — it only freed the arena, which no longer exists. - pub fn next(&mut self) -> Option<&'static [u8]> { pop_first(&mut self.remain) } @@ -127,8 +124,8 @@ pub enum ShellIteratorError { DanglingEscape, #[error("QuoteNotClosed")] QuoteNotClosed, - // PORT NOTE: Zig union included `mem.Allocator.Error` (OutOfMemory). Vec aborts on OOM - // under the global mimalloc allocator, so that variant is dropped. + // Zig's error union also included `mem.Allocator.Error` (OutOfMemory). Vec aborts + // on OOM under the global mimalloc allocator, so that variant does not exist here. } bun_core::named_error_set!(ShellIteratorError); @@ -136,7 +133,6 @@ bun_core::named_error_set!(ShellIteratorError); /// An argument iterator that takes a string and parses it into arguments, simulating /// how shells split arguments. pub struct ShellIterator<'a> { - // PORT NOTE: the Zig `arena: bun.ArenaAllocator` field was dropped (non-AST crate). // Allocated results are returned as `Cow::Owned` instead of arena-backed slices. pub str: &'a [u8], } @@ -157,8 +153,6 @@ impl<'a> ShellIterator<'a> { ShellIterator { str } } - // PORT NOTE: `deinit` dropped — it only freed the arena, which no longer exists. - pub fn next(&mut self) -> Result>, ShellIteratorError> { // Whenever possible, this iterator will return slices into `str` instead of // allocating. Sometimes this is not possible, for example, escaped characters @@ -167,8 +161,8 @@ impl<'a> ShellIterator<'a> { let mut start: usize = 0; let mut state = State::SkipWhitespace; - // PORT NOTE: reshaped for borrowck — copy the slice ref so we can reassign - // `self.str` before returning (Zig used `defer iter.str = ...`). + // Copy the slice ref so we can reassign `self.str` before returning + // (Zig used `defer iter.str = ...`). let s: &'a [u8] = self.str; for (i, &c) in s.iter().enumerate() { @@ -343,16 +337,22 @@ mod tests { } fn test_shell_iterator_ok(str: &[u8], allocations: usize, expect: &[&[u8]]) { - // TODO(port): Zig used `testing.FailingAllocator` to cap/count allocations. - // No allocator injection in the Rust port; `allocations` is unused. - let _ = allocations; + // Zig used `testing.FailingAllocator` to count allocations. The Rust port + // has no allocator injection, but every allocating result surfaces as a + // `Cow::Owned`, so counting owned results checks the same property: the + // borrowed (zero-copy) fast path is taken whenever possible. + let mut owned_results: usize = 0; let mut it = ShellIterator::init(str); for e in expect { match it.next() { Ok(actual) => { assert!(actual.is_some()); - assert_eq!(*e, &*actual.unwrap()); + let actual = actual.unwrap(); + if matches!(actual, Cow::Owned(_)) { + owned_results += 1; + } + assert_eq!(*e, &*actual); } Err(err) => panic!("expected {:?}, got error {:?}", e, err), } @@ -361,7 +361,7 @@ mod tests { match it.next() { Ok(actual) => { assert!(actual.is_none()); - // TODO(port): assert_eq!(allocations, allocator.allocations); + assert_eq!(allocations, owned_results); } Err(err) => panic!("expected end of iterator, got error {:?}", err), } diff --git a/src/clap/comptime.rs b/src/clap/comptime.rs index 66f8257ff0b9..a404b8186afd 100644 --- a/src/clap/comptime.rs +++ b/src/clap/comptime.rs @@ -560,7 +560,6 @@ impl ComptimeClap { ) -> Result where I: ArgIter<'static>, - // TODO(port): narrow error set { // `opt.allocator` dropped — global mimalloc. let mut multis: Vec> = (0..table.n_multi).map(|_| Vec::new()).collect(); @@ -588,8 +587,8 @@ impl ComptimeClap { pos.push(arg.value.unwrap()); if opt.stop_after_positional_at > 0 && pos.len() >= opt.stop_after_positional_at { let mut remaining_ = stream.iter.remain(); - // PORT NOTE: Zig called `bun.span` (NUL-scan) on `[:0]const u8` argv - // entries. Our `ArgIter` already yields sized `&[u8]`, so `span` is a + // Zig called `bun.span` (NUL-scan) on `[:0]const u8` argv entries. + // Our `ArgIter` already yields sized `&[u8]`, so `span` is a // no-op and is dropped. let first: &[u8] = if !remaining_.is_empty() { remaining_[0] @@ -626,7 +625,6 @@ impl ComptimeClap { Ok(Self { single_options, - // PORT NOTE: Zig left these `undefined` and filled them post-loop. multi_options: multis.into_iter().map(Vec::into_boxed_slice).collect(), flags, pos: pos.into_boxed_slice(), diff --git a/src/clap/lib.rs b/src/clap/lib.rs index 8c4d38d22d49..edff37e596a0 100644 --- a/src/clap/lib.rs +++ b/src/clap/lib.rs @@ -328,10 +328,10 @@ fn expect_param(expect: Param, actual: Param) { } /// Optional diagnostics used for reporting useful errors -// PORT NOTE: Zig `Diagnostic` borrows `arg`/`name.long` from the arg iterator. Rust -// can't tie that lifetime through `&mut Diagnostic` without invariance headaches, and -// this is an error-path-only struct, so it owns its bytes instead. The `name: Names` -// field is flattened to `short`/`long` because `Names.long` is `&'static`. +// Zig `Diagnostic` borrows `arg`/`name.long` from the arg iterator. Rust can't tie +// that lifetime through `&mut Diagnostic` without invariance headaches, and this is +// an error-path-only struct, so it owns its bytes instead. The `name: Names` field +// is flattened to `short`/`long` because `Names.long` is `&'static`. #[derive(Default)] pub struct Diagnostic { pub arg: Vec, @@ -349,7 +349,6 @@ impl Diagnostic { #[cold] #[inline(never)] pub fn report(&self, _stream: W, err: bun_core::Error) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let mut name_buf = [0u8; 1024]; let name: &[u8] = if let Some(s) = self.short { name_buf[0] = b'-'; @@ -419,8 +418,6 @@ impl Default for Help { /// Options that can be set to customize the behavior of parsing. #[derive(Default)] pub struct ParseOptions<'a> { - // PORT NOTE: `mem.Allocator param` field deleted — non-AST crate uses - // the global mimalloc. pub diagnostic: Option<&'a mut Diagnostic>, pub stop_after_positional_at: usize, } @@ -465,13 +462,9 @@ fn get_value_simple(param: &Param) -> &'static [u8] { param.id.value } -// TODO(port): `comptime params: []const Param(Id)` as a type parameter has no -// stable-Rust equivalent. `params` is carried at runtime; a proc-macro could -// restore the per-table monomorphization. +// Zig's `comptime params: []const Param(Id)` type parameter has no stable-Rust +// equivalent; `params` is carried at runtime instead. pub struct Args { - // PORT NOTE: Zig stored `arena: bun.ArenaAllocator` here and `deinit` freed it. - // Non-AST crate → arena removed; `ComptimeClap` owns its allocations. - // PERF(port): was arena bulk-free — profile if hot. pub clap: ComptimeClap, pub exe_arg: Option<&'static [u8]>, } @@ -514,13 +507,9 @@ pub fn parse( params: &'static [Param], opt: ParseOptions<'_>, ) -> Result, bun_core::Error> { - // TODO(port): narrow error set let mut iter = args::OsIterator::init(); let exe_arg = iter.exe_arg; - // PORT NOTE: Zig reused `iter.arena` as the allocator for `parseEx` and - // moved it into `res.arena`. Arena removed in port; ownership flows through - // `ComptimeClap` directly. let clap = parse_ex::( params, &mut iter, @@ -566,7 +555,6 @@ pub fn parse_ex( where I: args::ArgIter<'static>, { - // TODO(port): narrow error set ComptimeClap::::parse(params, iter, opt) } @@ -590,7 +578,6 @@ where Id: Copy, E: Into, { - // TODO(port): narrow error set let max_spacing: usize = 'blk: { let mut res: usize = 0; for param in params { @@ -706,8 +693,6 @@ where pub fn help_ex( stream: &mut W, params: &[Param], - // TODO(port): LIFETIMES.tsv classifies these as `fn(Param) -> &'static str`; - // using `&'static [u8]` to stay consistent with the bytes-not-str rule. help_text: fn(&Param) -> &'static [u8], value_text: fn(&Param) -> &'static [u8], ) -> Result<(), bun_core::Error> @@ -743,7 +728,6 @@ where #[cold] #[inline(never)] pub fn simple_print_param(param: &Param) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set bun_core::pretty!("\n"); if let Some(s) = param.names.short { if param.takes_value != Values::None && param.names.long.is_none() { @@ -836,10 +820,9 @@ pub fn simple_help(params: &[Param]) { #[cold] #[inline(never)] pub fn simple_help_bun_top_level(params: &[Param]) { - // TODO(port): Zig evaluates `computed_max_spacing` at `comptime` and emits - // `@compileError` on overflow, plus uses `inline for` + comptime string - // concat (`space_buf[..n] ++ desc_text`). None of that is const-evaluable - // in Rust over a slice param. Runtime equivalent below; could macro-gen. + // Zig evaluated `computed_max_spacing` at `comptime` and emitted + // `@compileError` on overflow; that is not const-evaluable in Rust over a + // slice param, so the overflow check is a runtime debug_assert below. const MAX_SPACING: usize = 30; const SPACE_BUF: &[u8; MAX_SPACING] = b" "; @@ -901,7 +884,6 @@ where Id: Copy, E: Into, { - // TODO(port): narrow error set let mut cos = CountingWriter::wrap(stream); for param in params { let Some(name) = param.names.short else { @@ -912,8 +894,8 @@ where } if cos.count == 0 { - // PORT NOTE: Zig wrote "[-" to `stream` (not `cs`), bypassing the - // counter. Preserving that quirk by writing to the inner writer. + // Zig wrote "[-" to `stream` (not `cs`), bypassing the counter. + // Preserving that quirk by writing to the inner writer. write!(cos.inner(), "[-")?; } cos.write_char(name as char)?; diff --git a/src/clap/streaming.rs b/src/clap/streaming.rs index 320858f1c5e7..24aa2beb03e9 100644 --- a/src/clap/streaming.rs +++ b/src/clap/streaming.rs @@ -65,8 +65,8 @@ pub struct StreamingClap<'p, 'a, Id, ArgIterator> { pub diagnostic: Option<&'p mut clap::Diagnostic>, } -// PORT NOTE: ArgIterator was a comptime duck-typed param in Zig; expressed here as -// the `args::ArgIter<'a>` trait so `next()`/`remain()` resolve. +// ArgIterator was a comptime duck-typed param in Zig; expressed here as the +// `args::ArgIter<'a>` trait so `next()`/`remain()` resolve. impl<'p, 'a, Id, ArgIterator> StreamingClap<'p, 'a, Id, ArgIterator> where ArgIterator: ArgIter<'a>, @@ -110,7 +110,7 @@ where None }; - // PORT NOTE: reshaped for borrowck — copy slice ref so &mut self is free inside loop. + // Copy the slice ref so `&mut self` is free inside the loop. let params = self.params; for param in params { if !param.names.matches_long(name) { @@ -214,7 +214,7 @@ where let index = state.index; let next_index = index + 1; - // PORT NOTE: reshaped for borrowck — copy slice ref so &mut self is free inside loop. + // Copy the slice ref so `&mut self` is free inside the loop. let params = self.params; for param in params { let Some(short) = param.names.short else { @@ -224,9 +224,10 @@ where continue; } - // Before we return, we have to set the new state of the clap - // PORT NOTE: Zig `defer` hoisted — every path below returns, and nothing - // between here and those returns reads `self.state`. + // Before we return, we have to set the new state of the clap. + // (Zig did this in a `defer`; hoisting it is equivalent — every path + // below returns, and nothing between here and those returns reads + // `self.state`.) if arg.len() <= next_index || param.takes_value != clap::Values::None { self.state = State::Normal; } else { @@ -331,8 +332,8 @@ where fn err(&mut self, arg: &[u8], short: Option, long: Option<&[u8]>, e: ArgError) -> ArgError { if let Some(d) = self.diagnostic.as_deref_mut() { - // PORT NOTE: Zig assigned borrowed `arg`/`name` slices; Rust `Diagnostic` - // owns its bytes (error path only) — see lib.rs. + // Zig assigned borrowed `arg`/`name` slices; Rust `Diagnostic` owns + // its bytes (error path only) — see lib.rs. d.arg = arg.to_vec(); d.short = short; d.long = long.map(|l| l.to_vec()); @@ -398,10 +399,34 @@ mod tests { Ok(Some(_)) => {} Ok(None) => break, Err(_err) => { - // TODO(port): io.fixedBufferStream + diag.report — `Diagnostic::report` - // currently routes through `bun_core::Output` (stderr) and ignores its - // writer arg, so we cannot capture output to compare against `expected`. - let _ = expected; + // Bun's `Diagnostic::report` deliberately ignores its writer arg + // and routes through `bun_core::Output` (stderr) — exactly like + // the Zig original (`report(diag, _: anytype, ...)` in clap.zig), + // so the rendered message can't be captured here (and never could + // be in the Zig tests either). Instead, rebuild the flag name the + // same way `report` does from the diagnostic fields and assert + // the expected message names it in quotes. + let mut name_buf = [0u8; 1024]; + let captured: &[u8] = if let Some(s) = diag.short { + name_buf[0] = b'-'; + name_buf[1] = s; + &name_buf[..2] + } else if let Some(l) = diag.long.as_deref() { + name_buf[0] = b'-'; + name_buf[1] = b'-'; + name_buf[2..2 + l.len()].copy_from_slice(l); + &name_buf[..2 + l.len()] + } else { + &diag.arg + }; + assert!( + expected.windows(captured.len() + 2).any(|w| w[0] == b'\'' + && w[w.len() - 1] == b'\'' + && &w[1..w.len() - 1] == captured), + "expected message {:?} does not name captured arg {:?}", + bstr::BStr::new(expected), + bstr::BStr::new(captured), + ); return; } } diff --git a/src/collections/StaticHashMap.rs b/src/collections/StaticHashMap.rs index c37c17d05537..f5b48039c4d2 100644 --- a/src/collections/StaticHashMap.rs +++ b/src/collections/StaticHashMap.rs @@ -47,7 +47,7 @@ impl Entry { K: Copy + Default, V: Copy + Default, { - // PORT NOTE: Zig used `std.mem.zeroes(K)` / `undefined` — key/value of + // Zig used `std.mem.zeroes(K)` / `undefined` — key/value of // an empty entry (hash == EMPTY_HASH) are never read. Rust cannot use // `mem::zeroed()` here: K may be `&[u8]` (or any `Copy` type with a // niche), for which all-zero bytes violate the validity invariant @@ -108,8 +108,8 @@ pub const fn static_slots(capacity: usize) -> usize { // StaticHashMap // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: the inline `[Entry; CAPACITY + overflow]` array length depends on -// a const fn of `CAPACITY`, which requires nightly `feature(generic_const_exprs)`. +// The inline `[Entry; CAPACITY + overflow]` array length depends on a const fn +// of `CAPACITY`, which requires nightly `feature(generic_const_exprs)`. // Stable workaround (same as ArrayBitSet): callers pass `SLOTS = static_slots(CAPACITY)` // as a second const param; a const-assert in `Default::default()` checks they match. pub struct StaticHashMap< @@ -141,8 +141,6 @@ impl: Copy` const-init; - // may need `MaybeUninit` + loop if K/V aren't const-zeroable. entries: [Entry::empty(); SLOTS], len: 0, shift: compute_shift(CAPACITY as u64), @@ -257,9 +255,9 @@ impl< let mut map = Self::init_capacity(capacity * 2)?; - // PORT NOTE: reshaped for borrowck — Zig walks raw `[*]Entry` pointers - // (`src`, `dst`, `end`); here we iterate by index over the old slice and - // index into the new boxed slice. + // Zig walks raw `[*]Entry` pointers (`src`, `dst`, `end`); here we + // iterate by index over the old slice and index into the new boxed + // slice. let mut dst: usize = 0; let mut src: usize = 0; while src != end { @@ -335,7 +333,7 @@ pub trait HashMapMixin { fn slice(&mut self) -> &mut [Entry] { // Zig recomputes `capacity + overflow` from `shift`; with Box<[T]>/[T; N] // the storage already carries its exact length, so just return it. - // PORT NOTE: assert kept for parity with Zig's implicit invariant. + // The assert is kept for parity with Zig's implicit invariant. let capacity = 1u64 << (63 - self.shift() + 1); let overflow = compute_overflow(capacity, self.shift()); debug_assert_eq!( @@ -381,9 +379,9 @@ pub trait HashMapMixin { V: Copy + Default, Ctx: HashContext, { - // PORT NOTE: Zig left `value = undefined` (never read until the caller - // writes via `value_ptr`). Use `Default` for the placeholder — V may - // not be zero-valid. + // Zig left `value = undefined` (never read until the caller writes via + // `value_ptr`). Use `Default` for the placeholder — V may not be + // zero-valid. let mut it: Entry = Entry { hash: Ctx::ctx_hash(&key), key, @@ -396,8 +394,8 @@ pub trait HashMapMixin { let mut inserted_at: Option = None; loop { - // PORT NOTE: reshaped for borrowck — copy entry out, drop borrow, - // re-borrow mutably for write/return. + // Copy the entry out, drop the borrow, re-borrow mutably for + // write/return. let entry = self.storage()[i]; if entry.hash >= it.hash { if Ctx::ctx_eql(&entry.key, &key) { @@ -554,15 +552,88 @@ pub trait HashMapMixin { mod tests { use super::*; - // TODO(port): Zig tests use `std.rand.DefaultPrng` (xoshiro256++). Need a - // matching PRNG for byte-identical key sequences, or accept any PRNG since - // these tests only check sortedness/round-trip, not specific keys. + /// Zig's `std.rand.DefaultPrng`: xoshiro256++ with the state seeded by + /// splitmix64. Key sequences are byte-identical to the Zig tests, so the + /// key-only expectations (duplicate-free keys, len- and count-driven + /// shift progression) carry over exactly. Hash values do NOT carry over: + /// `AutoHashContext` routes through `bun_wyhash::auto_hash` (mum-mix), + /// not Zig's `Wyhash.hash(0, asBytes)`, so probe displacement is a fresh + /// draw. The 100%-load probe bound of the static test was re-validated + /// for this hash by exact simulation of all 128 seeds: max slot index + /// touched (incl. delete's `i + 1` backshift read) is 548 of 632, with + /// no 64-bit hash collisions among any seed's 512 keys. + struct Xoshiro256PlusPlus { + s: [u64; 4], + } + + impl Xoshiro256PlusPlus { + fn init(seed: u64) -> Self { + fn splitmix64(state: &mut u64) -> u64 { + *state = state.wrapping_add(0x9E37_79B9_7F4A_7C15); + let mut z = *state; + z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + z ^ (z >> 31) + } + let mut sm = seed; + let mut s = [0u64; 4]; + for slot in s.iter_mut() { + *slot = splitmix64(&mut sm); + } + Self { s } + } + + fn next(&mut self) -> u64 { + let s = &mut self.s; + let result = s[0].wrapping_add(s[3]).rotate_left(23).wrapping_add(s[0]); + let t = s[1] << 17; + s[2] ^= s[0]; + s[3] ^= s[1]; + s[1] ^= s[2]; + s[0] ^= s[3]; + s[2] ^= t; + s[3] = s[3].rotate_left(45); + result + } + } #[test] fn static_hash_map_put_get_delete_grow() { - // TODO(port): blocked on generic_const_exprs for StaticHashMap inline array. - // let mut map: StaticHashMap = Default::default(); - // for seed in 0..128 { ... } + const CAP: usize = 512; + const SLOTS: usize = static_slots(CAP); + // Boxed: ~15 KB of entries is fine on the heap, gratuitous on the stack. + let mut map: Box> = + Box::new(Default::default()); + + // Miri is ~100× slower; 2 seeds still cover the put/get/delete cycle. + const SEEDS: u64 = if cfg!(miri) { 2 } else { 128 }; + for seed in 0..SEEDS { + let mut rng = Xoshiro256PlusPlus::init(seed); + + let keys: Vec = (0..512).map(|_| rng.next() as usize).collect(); + + assert_eq!(map.shift, 55); + + for (i, &key) in keys.iter().enumerate() { + map.put_assume_capacity(key, i); + } + assert_eq!(map.len, keys.len()); + + let mut it: u64 = 0; + for entry in map.slice().iter() { + if !entry.is_empty() { + assert!(it <= entry.hash, "Unsorted"); + it = entry.hash; + } + } + + for (i, &key) in keys.iter().enumerate() { + assert_eq!(map.get(key).unwrap(), i); + } + for (i, &key) in keys.iter().enumerate() { + assert_eq!(map.delete(key).unwrap(), i); + } + } } #[test] @@ -570,18 +641,11 @@ mod tests { // Miri is ~100× slower; 2 seeds still exercises grow (`shift` assert below). const SEEDS: u64 = if cfg!(miri) { 2 } else { 128 }; for seed in 0..SEEDS { - // TODO(port): replace with xoshiro256++ to match Zig DefaultPrng. - let mut state = seed.wrapping_mul(0x9E37_79B9_7F4A_7C15).wrapping_add(1); - let mut next = || { - state ^= state << 13; - state ^= state >> 7; - state ^= state << 17; - state - }; + let mut rng = Xoshiro256PlusPlus::init(seed); let mut keys = vec![0usize; 512]; for k in keys.iter_mut() { - *k = next() as usize; + *k = rng.next() as usize; } let mut map = AutoHashMap::::init_capacity(16).unwrap(); diff --git a/src/collections/array_hash_map.rs b/src/collections/array_hash_map.rs index 000845932494..b60b3ee92293 100644 --- a/src/collections/array_hash_map.rs +++ b/src/collections/array_hash_map.rs @@ -1271,8 +1271,8 @@ impl, A: MapAllocator> ArrayHashMap StringHash /// This is the *unordered* map context (vs. `StringContext` above which /// truncates to u32 for `ArrayHashMap`). /// -/// PORT NOTE: spelled as a module rather than a unit struct so callers can +/// Spelled as a module rather than a unit struct so callers can /// path-access the nested `Prehashed` / `PrehashedCaseInsensitive` types /// (`StringHashMapContext::Prehashed::…`) on stable Rust, which forbids /// inherent associated types. @@ -2241,7 +2241,7 @@ impl StringSet { pub fn clear_and_free(&mut self) { // Keys are `Box<[u8]>`; `clear` drops them. self.map.clear_retaining_capacity(); - // PORT NOTE: Zig also freed the backing arrays; Vec keeps capacity here + // Unlike Zig, this does not free the backing arrays; Vec keeps capacity here // (callers wanting that can drop the whole `StringSet`). } diff --git a/src/collections/array_list.rs b/src/collections/array_list.rs index 4446c5afa8de..48206e3e2d9d 100644 --- a/src/collections/array_list.rs +++ b/src/collections/array_list.rs @@ -1,7 +1,7 @@ #![forbid(unsafe_code)] //! Managed `ArrayList` wrappers. //! -//! PORT NOTE: The Zig original wraps `std.ArrayListAlignedUnmanaged` to add two things: +//! The Zig original wraps `std.ArrayListAlignedUnmanaged` to add two things: //! 1. A stored allocator (managed vs unmanaged split). //! 2. "Deep" semantics — `deinit`/`clear`/`shrink`/`replaceRange` call `deinit` on each //! removed item, with `*Shallow` variants that skip that. @@ -22,28 +22,28 @@ use super::vec_ext::VecExt; /// Prefer using a concrete type, like `ArrayListDefault`. /// /// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// PORT NOTE: `std.mem.Allocator` type param dropped — global mimalloc (non-AST crate). +// Zig's `std.mem.Allocator` type param is dropped — global mimalloc (non-AST crate). pub type ArrayList = ArrayListAlignedIn; /// Managed `ArrayList` using the default allocator. No overhead compared to an unmanaged /// `ArrayList`. /// /// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// PORT NOTE: `bun.DefaultAllocator` type param dropped — global mimalloc. +// Zig's `bun.DefaultAllocator` type param is dropped — global mimalloc. pub type ArrayListDefault = ArrayListAlignedIn; /// Managed `ArrayList` using a specific kind of allocator. /// /// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// PORT NOTE: `Allocator` type param dropped — global mimalloc. +// Zig's `Allocator` type param is dropped — global mimalloc. pub type ArrayListIn = ArrayListAlignedIn; /// Managed `ArrayListAligned` using an arbitrary allocator. /// /// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// TODO(port): const-generic alignment param. Rust `Vec` uses `align_of::()` and has no -// over-alignment knob; if any caller passes a non-null `alignment`, that call site needs a -// `#[repr(align(N))]` newtype wrapper around `T` instead. +// Zig's const-generic alignment param is dropped: Rust `Vec` uses `align_of::()` and has +// no over-alignment knob. A call site that needs a non-null `alignment` must wrap `T` in a +// `#[repr(align(N))]` newtype instead. pub type ArrayListAligned = ArrayListAlignedIn; /// Managed `ArrayListAligned` using the default allocator. @@ -54,8 +54,8 @@ pub type ArrayListAlignedDefault = ArrayListAlignedIn; /// Managed `ArrayListAligned` using a specific kind of allocator. /// /// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// PORT NOTE: Zig's `fn(...) type` factory → generic struct (PORTING.md §Idiom map). -// Allocator type param dropped; alignment param dropped (see ArrayListAligned TODO above). +// Zig's `fn(...) type` factory → generic struct (PORTING.md §Idiom map). +// Allocator type param dropped; alignment param dropped (see `ArrayListAligned` above). #[derive(Default)] pub struct ArrayListAlignedIn { /// Zig: `#unmanaged: Unmanaged = .empty` @@ -67,19 +67,19 @@ pub struct ArrayListAlignedIn { pub(crate) type Unmanaged = Vec; /// Zig: `Slice = Unmanaged.Slice` (= `[]align(alignment) T`, an owned slice when detached). -// TODO(port): Zig `Slice` is used both as a borrow (`items()`) and as an owned return -// (`toOwnedSlice`). Rust splits these: borrows are `&[T]`/`&mut [T]`, owned is `Box<[T]>`. +// Zig `Slice` is used both as a borrow (`items()`) and as an owned return (`toOwnedSlice`). +// Rust splits these: borrows are `&[T]`/`&mut [T]`, owned is `Box<[T]>`. pub type Slice = Box<[T]>; -// TODO(port): `SentinelSlice` — sentinel-terminated slices have no std Rust equivalent; only -// needed if a caller uses `fromOwnedSliceSentinel`. Map to `bun_core::ZStr`/`WStr` at call site. +// Zig `SentinelSlice` is not ported — sentinel-terminated slices have no std Rust equivalent. +// A caller that needs one maps to `bun_core::ZStr`/`WStr` at the call site. impl ArrayListAlignedIn { pub fn items(&self) -> &[T] { self.unmanaged.as_slice() } - // PORT NOTE: Zig `items()` returns a mutable `[]T`; Rust splits const/mut borrows. + // Zig `items()` returns a mutable `[]T`; Rust splits const/mut borrows. pub fn items_mut(&mut self) -> &mut [T] { self.unmanaged.as_mut_slice() } @@ -121,21 +121,14 @@ impl ArrayListAlignedIn { } } - // TODO(port): `from_owned_slice_sentinel` — sentinel-terminated owned slices are not a Rust - // type. If needed, accept `Box<[T]>` with the sentinel already stripped, or `ZStr`/`WStr`. + // Sentinel-terminated owned slices are not a Rust type, so unlike Zig's + // `fromOwnedSliceSentinel` this takes a `Box<[T]>` with the sentinel already stripped. pub fn from_owned_slice_sentinel(/* sentinel: T, */ slice: Slice) -> Self { Self { unmanaged: Vec::from(slice), } } - // TODO(port): `writer()` — Zig returns an `std.io.Writer` that appends bytes. For `T = u8` - // this is `impl std::io::Write for Vec` (already in std). For other `T` there is no - // meaningful writer. TODO(port): expose only on `ArrayListAlignedIn`. - pub fn writer(&mut self) -> &mut Vec { - &mut self.unmanaged - } - /// This method empties `self`. pub fn move_to_unmanaged(&mut self) -> Unmanaged { // Zig: `defer self.#unmanaged = .empty; return self.#unmanaged;` @@ -161,9 +154,9 @@ impl ArrayListAlignedIn { /// Creates a copy of this `ArrayList` with copies of its items. /// - /// PORT NOTE: Zig makes *bitwise* (shallow) copies regardless of whether `T` has a - /// `deinit`. Rust cannot bit-copy a non-`Copy` `T` safely. This is bound on `T: Clone`; - /// callers that relied on shallow-copy-then-`deinitShallow` need a redesign. + /// Zig makes *bitwise* (shallow) copies regardless of whether `T` has a `deinit`. Rust + /// cannot bit-copy a non-`Copy` `T` safely. This is bound on `T: Clone`; callers that + /// relied on shallow-copy-then-`deinitShallow` need a redesign. pub fn clone(&self) -> Result where T: Clone, @@ -218,21 +211,19 @@ impl ArrayListAlignedIn { &mut self.unmanaged[index..index + count] } - /// This method takes ownership of all elements in `new_items`. + /// Note that this `Clone`s each element of `new_items` (Zig took `[]const T` and + /// bit-copied, transferring ownership; a borrowed Rust slice cannot transfer ownership). pub fn insert_slice(&mut self, index: usize, new_items: &[T]) -> Result<(), AllocError> where T: Clone, { - // TODO(port): Zig takes `[]const T` and bit-copies, transferring ownership. Rust must - // `Clone` from a borrowed slice. If callers own the data, change signature to - // `impl IntoIterator` to avoid the clone. self.unmanaged .splice(index..index, new_items.iter().cloned()); Ok(()) } /// This method `Drop`s the removed items. - /// This method takes ownership of all elements in `new_items`. + /// Note that this `Clone`s each element of `new_items` (see `insert_slice`). pub fn replace_range( &mut self, start: usize, @@ -242,7 +233,7 @@ impl ArrayListAlignedIn { where T: Clone, { - // PORT NOTE: Zig deinits `items[start..start+len]` then calls the shallow path. + // Zig deinits `items[start..start+len]` then calls the shallow path. // `Vec::splice` already drops the removed range, so deep == direct splice. self.unmanaged .splice(start..start + len, new_items.iter().cloned()); @@ -250,7 +241,7 @@ impl ArrayListAlignedIn { } /// This method `Drop`s the removed items. - /// This method takes ownership of all elements in `new_items`. + /// Note that this `Clone`s each element of `new_items` (see `insert_slice`). pub fn replace_range_assume_capacity(&mut self, start: usize, len: usize, new_items: &[T]) where T: Clone, @@ -277,17 +268,16 @@ impl ArrayListAlignedIn { self.unmanaged.swap_remove(i) } - /// This method takes ownership of all elements in `new_items`. + /// Note that this `Clone`s each element of `new_items` (see `insert_slice`). pub fn append_slice(&mut self, new_items: &[T]) -> Result<(), AllocError> where T: Clone, { - // TODO(port): see `insert_slice` note re: Clone vs ownership transfer. self.unmanaged.extend_from_slice(new_items); Ok(()) } - /// This method takes ownership of all elements in `new_items`. + /// Note that this `Clone`s each element of `new_items` (see `insert_slice`). pub fn append_slice_assume_capacity(&mut self, new_items: &[T]) where T: Clone, @@ -296,19 +286,19 @@ impl ArrayListAlignedIn { self.unmanaged.extend_from_slice(new_items); } - /// This method takes ownership of all elements in `new_items`. + /// Note that this `Clone`s each element of `new_items` (see `insert_slice`). pub fn append_unaligned_slice(&mut self, new_items: &[T]) -> Result<(), AllocError> where T: Clone, { - // TODO(port): Zig `[]align(1) const T` allows reading T from an under-aligned address. - // Rust `&[T]` is always naturally aligned. If a caller truly has unaligned bytes, it - // needs `ptr::read_unaligned` at the call site. Treat as aligned here. + // Zig `[]align(1) const T` allows reading T from an under-aligned address. Rust `&[T]` + // is always naturally aligned, so this is identical to `append_slice`; a caller that + // truly has unaligned bytes needs `ptr::read_unaligned` at the call site. self.unmanaged.extend_from_slice(new_items); Ok(()) } - /// This method takes ownership of all elements in `new_items`. + /// Note that this `Clone`s each element of `new_items` (see `insert_slice`). pub fn append_unaligned_slice_assume_capacity(&mut self, new_items: &[T]) where T: Clone, @@ -345,7 +335,7 @@ impl ArrayListAlignedIn { where T: Clone, { - // PORT NOTE: Zig calls `resizeWithoutDeinit` first, *then* deinits the tail via a raw + // Zig calls `resizeWithoutDeinit` first, *then* deinits the tail via a raw // pointer past `len` (`items().ptr[new_len..len]`). That ordering is to avoid a failed // realloc leaving already-deinited items in the list. `Vec::resize` already drops the // truncated tail in the shrink case and never fails, so the ordering concern vanishes. @@ -356,7 +346,7 @@ impl ArrayListAlignedIn { /// This method `Drop`s the removed items. pub fn shrink_and_free(&mut self, new_len: usize) { self.prepare_for_deep_shrink(new_len); - // PORT NOTE: `prepare_for_deep_shrink` already truncated (dropping items); now free. + // `prepare_for_deep_shrink` already truncated (dropping items); now free. self.unmanaged.shrink_to_fit(); } @@ -417,7 +407,7 @@ impl ArrayListAlignedIn { &items[items.len() - 1] } - // PORT NOTE: Zig returns `*T` (mutable) from a `*const Self` receiver via interior aliasing. + // Zig returns `*T` (mutable) from a `*const Self` receiver via interior aliasing. // Rust splits this into `&T` / `&mut T` accessors. pub fn get_last_mut(&mut self) -> &mut T { let len = self.unmanaged.len(); @@ -450,7 +440,16 @@ impl ArrayListAlignedIn { // Zig `getStdAllocator` — dropped (no allocator field). } -// PORT NOTE: Zig `pub fn deinit` → `impl Drop`. The Zig body is +impl ArrayListAlignedIn { + /// Zig `writer()` returns an `std.io.Writer` that appends bytes; the Rust equivalent is + /// `impl std::io::Write for Vec` (already in std), so hand out the backing `Vec`. + /// Only exposed for `T = u8` — there is no meaningful writer for other element types. + pub fn writer(&mut self) -> &mut Vec { + &mut self.unmanaged + } +} + +// Zig `pub fn deinit` → `impl Drop`. The Zig body is // `bun.memory.deinit(self.items()); self.deinitShallow();` // i.e. drop every item, then free the backing buffer. `Vec`'s own `Drop` does both, so per // PORTING.md ("If the body only frees/deinits owned fields, delete the body entirely") no diff --git a/src/collections/bit_set.rs b/src/collections/bit_set.rs index fb2015c0a2f8..0af25a034498 100644 --- a/src/collections/bit_set.rs +++ b/src/collections/bit_set.rs @@ -117,13 +117,13 @@ fn set_range_value_masks(masks: &mut [usize], range: Range, value: bool) { /// can be copied by value, and does not require deinitialization. /// Both possible implementations fulfill the same interface. /// -// TODO(port): Zig's `StaticBitSet(size)` returns `IntegerBitSet(size)` when +// Zig's `StaticBitSet(size)` returns `IntegerBitSet(size)` when // `size <= @bitSizeOf(usize)` and `ArrayBitSet(usize, size)` otherwise. Stable -// Rust cannot select a struct definition from a const generic. Callers should -// pick `IntegerBitSet` or `ArrayBitSet` directly; this alias resolves to -// the array form (always correct, possibly one word larger than needed for -// N <= 64). -pub type StaticBitSet = IntegerBitSet; // TODO(b2): callers needing >64 bits use ArrayBitSet directly +// Rust cannot select a struct definition from a const generic, so this alias +// is the integer form and is only valid for `SIZE <= usize::BITS` (enforced by +// `IntegerBitSet`'s debug asserts). Callers needing more bits must use +// `ArrayBitSet` directly. +pub type StaticBitSet = IntegerBitSet; // ───────────────────────────── IntegerBitSet ───────────────────────────── @@ -131,10 +131,10 @@ pub type StaticBitSet = IntegerBitSet; // TODO(b2): cal /// This set is good for sets with a small size, but may generate /// inefficient code for larger sets, especially in debug mode. /// -// TODO(port): Zig uses `std.meta.Int(.unsigned, size)` for an exact-width -// backing integer (u0..u65535). Rust has no arbitrary-width ints; we back with -// `usize` and rely on `SIZE <= usize::BITS`. Phase B may swap to a trait that -// picks u8/u16/u32/u64/u128. +// Zig uses `std.meta.Int(.unsigned, size)` for an exact-width backing integer +// (u0..u65535). Rust has no arbitrary-width ints; this is backed by `usize` +// and requires `SIZE <= usize::BITS` (misuse surfaces via `FULL_MASK` +// saturation + debug asserts). #[repr(transparent)] #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct IntegerBitSet { @@ -444,8 +444,8 @@ pub const fn num_masks_for(bit_length: usize) -> usize { /// This set is good for sets with a larger size, but may use /// more bytes than necessary if your set is small. /// -// TODO(port): Zig is generic over `MaskIntType`; every in-tree caller uses -// `usize`. Dropped the type parameter. Phase B can re-generify if needed. +// Zig is generic over `MaskIntType`; every in-tree caller uses `usize`, so +// the type parameter is dropped. #[repr(C)] #[derive(Clone, Copy)] pub struct ArrayBitSet { @@ -760,12 +760,12 @@ impl ArrayBitSet { /// A bit set with runtime-known size, backed by an allocated slice /// of usize. The allocator must be tracked externally by the user. /// -// TODO(port): the Zig type stores `masks: [*]MaskInt` where `masks[-1]` holds -// the true allocation length (needed because Zig's allocator API requires the -// original length on free). The Rust port keeps the same layout because -// `List` constructs borrowed views into a shared buffer that must look like -// freestanding `DynamicBitSetUnmanaged`s. Phase B may refactor to `Vec` -// once `List` is reworked. +// Layout invariant: like the Zig type, `masks` is a `[*]MaskInt` where +// `masks[-1]` holds the true allocation length (Zig's allocator API requires +// the original length on free). The Rust port keeps that exact layout because +// `DynamicBitSetList` constructs borrowed views into a shared buffer that must +// look like freestanding `DynamicBitSetUnmanaged`s — do not swap the storage +// for `Vec` without reworking `DynamicBitSetList`. pub struct DynamicBitSetUnmanaged { /// The number of valid items in this bit set pub bit_length: usize, @@ -814,10 +814,8 @@ impl Drop for DynamicBitSetUnmanaged { } impl DynamicBitSetUnmanaged { - pub const EMPTY: fn() -> Self = Self::default; - // TODO(port): Zig has `pub const empty: Self = .{ ... }` as a const value. - // Rust can't const-init a static-mut-derived pointer; callers should use - // `DynamicBitSetUnmanaged::default()`. + // Zig's `pub const empty: Self = .{ ... }` const value has no Rust equivalent here + // (the empty sentinel pointer is computed at runtime); use `Self::default()`. /// Borrow the mask words as a shared slice of length `num_masks(bit_length)`. #[inline(always)] @@ -1383,7 +1381,6 @@ unsafe impl Send for DynamicBitSetList {} // Raw allocation helpers for DynamicBitSetUnmanaged. These mirror Zig's // allocator.alloc/realloc/free with the size-at-[-1] header convention. -// TODO(port): move to bun_alloc if useful elsewhere. unsafe fn dyn_free(base: *mut usize, len: usize) { if len == 0 { @@ -1504,7 +1501,6 @@ impl AutoBitSet { } pub fn eql(&self, b: &AutoBitSet) -> bool { - // TODO(b0): `strings` arrives in bun_core via move-in (was bun_core::strings). self.raw_bytes() == b.raw_bytes() } @@ -1560,9 +1556,8 @@ impl Drop for AutoBitSet { /// of usize. Thin wrapper around DynamicBitSetUnmanaged which keeps /// track of the allocator instance. /// -// TODO(port): in Rust the managed/unmanaged split disappears (global -// allocator). This wrapper is kept for diff parity; Phase B may collapse it -// into `DynamicBitSetUnmanaged` and re-export under both names. +// In Rust the managed/unmanaged split disappears (global allocator); this +// wrapper is kept so call sites diff cleanly against the Zig source. #[derive(Default)] pub struct DynamicBitSet { /// The number of valid items in this bit set @@ -1761,9 +1756,9 @@ pub enum IteratorDirection { // ───────────────────────────── BitSetIterator ───────────────────────────── -// The iterator is reusable between several bit set types -// TODO(port): Zig is generic over `MaskInt`; fixed to `usize` here since every -// in-tree caller uses `usize`. +// The iterator is reusable between several bit set types. +// Zig is generic over `MaskInt`; fixed to `usize` here since every in-tree +// caller uses `usize`. pub struct BitSetIterator<'a, const KIND_SET: bool, const DIR_FWD: bool> { // all bits which have not yet been iterated over bits_remain: usize, @@ -1864,10 +1859,7 @@ pub struct Range { pub end: usize, } -// ───────────────────────────── Tests ───────────────────────────── - -// TODO(port): the Zig source's test helpers (`testEql`, `testBitSet`, …) are -// dead code carried from the std fork — no `test` block invokes them. They -// rely on `anytype` duck-typing; re-author against a trait when adding tests. +// The Zig source's test helpers (`testEql`, `testBitSet`, …) were dead code +// carried from the std fork — no `test` block invoked them — and were not ported. // ported from: src/collections/bit_set.zig diff --git a/src/collections/comptime_string_map.rs b/src/collections/comptime_string_map.rs index 59db5df02220..72001d1f078b 100644 --- a/src/collections/comptime_string_map.rs +++ b/src/collections/comptime_string_map.rs @@ -4,9 +4,8 @@ //! //! `kvs` expects a list literal containing list literals or an array/slice of structs //! where `.0` is the `&[u8]` key and `.1` is the associated value of type `V`. -// TODO: https://github.com/ziglang/zig/issues/4335 -// PORT NOTE: The Zig original is a `fn(comptime KeyType, comptime V, comptime kvs_list) type` +// The Zig original is a `fn(comptime KeyType, comptime V, comptime kvs_list) type` // that does heavy comptime work: sorts kvs by (len, bytes), builds a `len_indexes` table, // then every lookup is an `inline while` over lengths × `inline for` over same-length keys // with `eqlComptime` (length-known SIMD compare). Rust cannot replicate the per-callsite @@ -35,7 +34,7 @@ pub struct ComptimeStringMapWithKeyType< const N: usize, const LEN_TABLE: usize, > { - // PORT NOTE: in Zig these were `precomputed.{min_len,max_len,sorted_kvs,len_indexes}` + // In Zig these were `precomputed.{min_len,max_len,sorted_kvs,len_indexes}` // computed in a `comptime blk:`. Here they are filled by the constructor macro. min_len: usize, max_len: usize, @@ -65,7 +64,6 @@ impl HasLength for &[T] { (*self).len() } } -// TODO(port): `String` arrives in bun_alloc via move-in (was bun_core::String — same-tier cycle). impl HasLength for &bun_alloc::String { #[inline] fn length(&self) -> usize { @@ -73,8 +71,8 @@ impl HasLength for &bun_alloc::String { } } -// PORT NOTE: `pub const Value = V;` (inherent assoc type) is nightly-only; -// callers can write `V` directly. +// Zig's `pub const Value = V;` (inherent assoc type) is nightly-only in Rust; +// callers write `V` directly. impl ComptimeStringMapWithKeyType where @@ -85,7 +83,6 @@ where /// /// Mirrors the `comptime blk:` in the Zig: sort by (len asc, bytes asc), then /// fill `len_indexes[len]` = first index whose key.len >= len. - // TODO(port): make this a `const fn` once const-sort is stable, or move to build.rs. // PERF(port): Zig did this at comptime (zero runtime cost); this runs once at init. pub fn new(mut sorted_kvs: [KV; N]) -> Self { // lenAsc comparator @@ -137,7 +134,7 @@ where /// Contiguous range in `kvs` whose keys have exactly `len`. /// - /// PORT NOTE: the .zig spec open-coded this at every lookup site because `len` was + /// The .zig spec open-coded this at every lookup site because `len` was /// `comptime` there and each needed its own `comptime brk:` block. In the Rust port /// `len` is runtime, so the duplication is vestigial — extract once and inline. #[inline(always)] @@ -150,7 +147,7 @@ where start..end } - // PORT NOTE: `comptime len: usize` → runtime `len: usize`. The Zig used the comptime + // Zig's `comptime len: usize` → runtime `len: usize`. The Zig used the comptime // value to compute `end` at comptime and `inline for` the range; we loop at runtime. // PERF(port): was comptime monomorphization — profile if hot. pub fn get_with_length(&self, str: &[K], len: usize) -> Option { @@ -218,10 +215,10 @@ where (start..end).find(|&i| str == self.kvs[i].key) } - // TODO(port): move to *_jsc — `fromJS` / `fromJSCaseInsensitive` were thin shims to - // `jsc/comptime_string_map_jsc.zig`. In Rust these become extension-trait methods in - // `bun_jsc` (e.g. `impl ComptimeStringMapJsc for ComptimeStringMap`). - // The base `bun_collections` crate has no JSC dependency. + // Zig's `fromJS` / `fromJSCaseInsensitive` were thin shims to + // `jsc/comptime_string_map_jsc.zig`; their Rust port lives in + // `src/jsc/comptime_string_map_jsc.rs` (the base `bun_collections` crate has + // no JSC dependency). pub fn get_with_eql(&self, input: I, eql: impl Fn(I, &'static [K]) -> bool) -> Option where @@ -258,9 +255,8 @@ impl ComptimeStringMapWithKeyType Option { self.get_with_eql(str, bun_alloc::String::eql_comptime) } @@ -280,11 +276,17 @@ where } // PERF(port): Zig built a `[i]u8` stack buffer per comptime length; we use a - // bounded stack buffer sized to max_len. Profile if it shows up on a hot path. - // TODO(port): if max_len can exceed a small bound at any call site, revisit. - let mut buf = [0u8; 256]; - debug_assert!(length <= buf.len()); - let lowercased = bun_core::strings::copy_lowercase(input, &mut buf[..length]); + // bounded stack buffer for the common case (every in-tree map has small keys) + // and fall back to a heap buffer for maps whose keys exceed it. + let mut stack_buf = [0u8; 256]; + let mut heap_buf: Vec; + let buf: &mut [u8] = if length <= stack_buf.len() { + &mut stack_buf[..length] + } else { + heap_buf = vec![0u8; length]; + &mut heap_buf[..] + }; + let lowercased = bun_core::strings::copy_lowercase(input, buf); self.get_with_length_and_eql(lowercased, length, eql) } @@ -299,10 +301,9 @@ where /// ... /// ]); /// ``` -// TODO(port): proc-macro — Zig sorted + built len_indexes at comptime. A `macro_rules!` -// cannot sort; either (a) require callers pre-sort and compute LEN_TABLE, (b) use a -// proc-macro, or (c) lazy-init via `once_cell::Lazy` + `ComptimeStringMapWithKeyType::new`. -// Currently uses (c) for correctness; could upgrade to a proc-macro for true const. +// Zig sorted + built len_indexes at comptime. A `macro_rules!` cannot sort, so this +// lazy-inits via `once_cell::Lazy` + `ComptimeStringMapWithKeyType::new` — correct, with a +// one-time runtime init cost instead of true const construction. #[macro_export] macro_rules! comptime_string_map { ($V:ty, [ $( ($key:expr, $val:expr) ),* $(,)? ]) => {{ @@ -376,7 +377,7 @@ mod tests { #[test] fn comptime_string_map_array_of_structs() { - // PORT NOTE: Zig tested that anonymous-struct and named-struct kv inputs both work. + // Zig tested that anonymous-struct and named-struct kv inputs both work. // In Rust there is one input shape (`KV`), so this collapses to the same test. let map = ComptimeStringMapWithKeyType::::new([ KV { @@ -495,7 +496,7 @@ mod tests { test_set(&map); } - // PORT NOTE: `TestEnum2` + its 39-entry `map`/`official` table existed only as a + // `TestEnum2` + its 39-entry `map`/`official` table existed only as a // benchmark fixture against `std.ComptimeStringMap` (no `test` block references it). // Omitted; can re-add as a criterion bench if needed. } diff --git a/src/collections/hive_array.rs b/src/collections/hive_array.rs index 20761f9a9123..9851ae2eefa5 100644 --- a/src/collections/hive_array.rs +++ b/src/collections/hive_array.rs @@ -8,7 +8,7 @@ use bun_core::asan; /// Fixed-width occupancy bitset for [`HiveArray`]. /// -/// PORT NOTE: Zig's `std.bit_set.IntegerBitSet(N)` is backed by an exact-width +/// Zig's `std.bit_set.IntegerBitSet(N)` is backed by an exact-width /// `uN` integer (`u128`, `u256`, `u2048`, …). The Rust port's /// [`IntegerBitSet`](crate::bit_set::IntegerBitSet) is backed by a single /// `usize`, so for `N > 64` it silently held only 64 usable bits — every @@ -171,7 +171,7 @@ impl HiveBitSetIter { /// All slot operations take `&self` and the buffer is `UnsafeCell` — slot /// pointers come from `UnsafeCell::get()` and so survive `&self` reborrows of /// the pool (the `bumpalo` / `typed-arena` shape). `HiveArray` is `!Sync`. -// PORT NOTE: Zig's `capacity: u16` is widened to `usize` here because Rust array +// Zig's `capacity: u16` is widened to `usize` here because Rust array // lengths require a `usize` const generic on stable. pub struct HiveArray { buffer: UnsafeCell<[MaybeUninit; CAPACITY]>, @@ -181,7 +181,7 @@ pub struct HiveArray { impl HiveArray { pub const SIZE: usize = CAPACITY; - // PORT NOTE: Zig had `pub var empty: Self` as a mutable static to work around + // Zig had `pub var empty: Self` as a mutable static to work around // https://github.com/ziglang/zig/issues/22462 and /21988. Rust has no such // limitation; callers should use `init()` (which is `const`). @@ -403,7 +403,7 @@ impl HiveArray { debug_assert!(self.used.is_set(index as usize)); debug_assert!(self.ptr_at(index as usize).cast_const() == value.cast_const()); - // PORT NOTE: Zig wrote `value.* = undefined;` — Zig has no destructors, + // Zig wrote `value.* = undefined;` — Zig has no destructors, // so the slot was simply marked logically uninitialized. In the Rust // port several `T` carry owned heap data (e.g. `NumberScope.name_counts: // StringHashMap`, `NetworkTask.url_buf: Box<[u8]>`); drop the slot @@ -589,7 +589,7 @@ impl Drop for HiveBox<'_, T, CAPACITY> { } } -// PORT NOTE: In Zig this was the nested type `HiveArray(T, capacity).Fallback`. +// In Zig this was the nested type `HiveArray(T, capacity).Fallback`. // Rust cannot nest a generic struct that captures outer generics, so it lives at // module scope with the same parameters. The Zig field // `hive: if (capacity > 0) Self else void` is always materialized here; the @@ -597,7 +597,7 @@ impl Drop for HiveBox<'_, T, CAPACITY> { // PERF(port): zero-capacity case carried a zero-size hive in Zig — profile in Phase B. pub struct Fallback { pub hive: HiveArray, - // PORT NOTE: `std.mem.Allocator param` dropped — global mimalloc. + // (Zig's `std.mem.Allocator` field is gone — the fallback uses the global allocator.) } impl Fallback { @@ -768,7 +768,7 @@ impl Fallback { // HiveRef // ────────────────────────────────────────────────────────────────────────── // -// PORT NOTE: ground truth is `bun.HiveRef` in src/bun.zig. It lives here (not +// Ground truth is `bun.HiveRef` in src/bun.zig. It lives here (not // in the `bun` crate) because every consumer names it through // `bun_collections::HiveRef`, and its only collaborator is `Fallback` above. // @@ -941,7 +941,7 @@ mod tests { const SIZE: usize = 64; // Choose an integer with a weird alignment - // PORT NOTE: Zig used `u127`; Rust has no arbitrary-width ints. `u128` is the closest. + // (Zig used `u127`; Rust has no arbitrary-width ints, so `u128` is the closest.) type Int = u128; let a = HiveArray::::init(); diff --git a/src/collections/lib.rs b/src/collections/lib.rs index 10e8ad091f9f..37613dca4413 100644 --- a/src/collections/lib.rs +++ b/src/collections/lib.rs @@ -24,9 +24,6 @@ pub use bun_core::bounded_array; pub mod identity_context; pub mod linear_fifo; -// TODO(b2-large): heavy nightly-feature usage (adt_const_params for enum-typed -// const generics, generic_const_exprs, inherent assoc types). Rewrite to -// stable: enum const params → const usize/bool, inherent assoc → free aliases. pub mod bit_set; pub mod pool; pub use pool::{ObjectPool, ObjectPoolTrait, ObjectPoolType, PoolGuard}; @@ -533,9 +530,8 @@ pub mod hash_map { } pub mod array_list; -// TODO(port): per PORTING.md the managed/unmanaged ArrayList split collapses to -// `Vec` (global mimalloc) outside AST crates; Phase B may drop most of these -// aliases once callers are migrated. +// Per PORTING.md the managed/unmanaged ArrayList split collapses to `Vec` +// (global mimalloc) outside AST crates; these aliases exist for Zig-diff parity. pub use array_list::ArrayList; // any `std.mem.Allocator` pub use array_list::ArrayListAligned; pub use array_list::ArrayListAlignedDefault; diff --git a/src/collections/linear_fifo.rs b/src/collections/linear_fifo.rs index 70149dde41e3..8dc2636544d2 100644 --- a/src/collections/linear_fifo.rs +++ b/src/collections/linear_fifo.rs @@ -10,8 +10,8 @@ use core::ptr; use bun_alloc::AllocError; -// TODO(port): std.heap.page_size_min — Zig resolves this per-target; 4096 is the -// conservative minimum on every platform Bun ships on. +// Zig resolves `std.heap.page_size_min` per-target; 4096 is the conservative +// minimum on every platform Bun ships on. const PAGE_SIZE_MIN: usize = 4096; /// Mirrors Zig's `LinearFifoBufferType = union(enum)`. @@ -32,9 +32,10 @@ pub enum LinearFifoBufferType { /// Backing-storage abstraction replacing Zig's `comptime buffer_type` switch. /// `POWERS_OF_TWO` mirrors the Zig `powers_of_two` const inside the returned /// struct; `DYNAMIC` mirrors `buffer_type == .Dynamic`. -// TODO(port): the Zig fn returns structurally different layouts per variant; -// trait+assoc-consts is the closest stable-Rust encoding. Phase B: confirm all -// in-tree callers are covered by the three impls below. +// The Zig fn returns structurally different layouts per variant; trait + +// assoc-consts is the closest stable-Rust encoding. No in-tree caller +// instantiates `SliceBuffer` directly; `threading::Channel::init_slice` wraps +// it for `.Slice` API parity. pub trait LinearFifoBuffer { const POWERS_OF_TWO: bool; const DYNAMIC: bool; @@ -61,9 +62,12 @@ pub trait LinearFifoBuffer { /// Reinterpret `&[MaybeUninit]` as `&[T]`. `MaybeUninit` has identical /// layout to `T`; exposing uninitialized bytes as `T` is sound only when any -/// bit pattern is a valid `T` (in-tree LinearFifo users are byte buffers — -/// see the `StaticBuffer` TODO below). Centralises the four per-buffer-kind -/// casts behind one audited block. +/// bit pattern is a valid `T`. NOT every in-tree element type satisfies this: +/// besides byte buffers and raw pointers, fifos today store `NonNull`-bearing +/// enums (`bun_test::RefDataValue`), `JSPromiseStrong`-bearing structs +/// (`ValkeyCommand::PromisePair`), and the `event_loop::Task` enum — see the +/// `StaticBuffer` note below for the pending MaybeUninit accessor rework. +/// Centralises the four per-buffer-kind casts behind one audited block. #[inline(always)] fn assume_init_slice(s: &[MaybeUninit]) -> &[T] { // SAFETY: see fn doc. @@ -112,10 +116,19 @@ fn poison(slice: &mut [T], n: usize) { // ── .Static ─────────────────────────────────────────────────────────────────── /// `buffer_type == .Static` — inline `[T; N]` storage. -// TODO(port): Zig leaves the array `undefined`; we use MaybeUninit and expose -// it as &[T] via pointer cast. Sound only for `T` whose any-bit-pattern is -// valid (in-tree users are byte buffers). Phase B: bound `T: Copy` or rework -// accessors to MaybeUninit if a non-POD T appears. +// INVARIANT: Zig leaves the array `undefined`; we use MaybeUninit and expose +// it as &[T] via pointer cast (`assume_init_slice`). The Zig-parity public API +// (`writable_slice` hands out `&mut [T]` over not-yet-written slots) bakes in +// the same exposure for every buffer kind. Sound only for `T` whose +// any-bit-pattern is valid — and in-tree element types ALREADY violate that: +// `RefDataValue` (NonNull payload), `PromisePair` +// (JSPromiseStrong), and the `Task` enum are stored in fifos today, so +// materialising `&[T]` over uninitialized slots for those types is latent UB. +// The fix is reworking the accessors to operate on `&[MaybeUninit]` and +// only assume-init the logically-written subranges. That cannot be done by +// touching this file alone — `writable_slice`-family callers in other crates +// see the signature change — so it is deferred to a dedicated change with +// Miri coverage for a NonNull-bearing element type. pub struct StaticBuffer([MaybeUninit; N]); impl LinearFifoBuffer for StaticBuffer { @@ -198,14 +211,17 @@ pub struct LinearFifo> { _marker: PhantomData, } -// PORT NOTE: Zig's `SliceSelfArg = if (.Static) *Self else Self` exists because +// Zig's `SliceSelfArg = if (.Static) *Self else Self` exists because // returning a slice into a by-value `Self` would dangle when buf is inline. In // Rust every accessor takes `&self`/`&mut self`, so the distinction disappears. -// TODO(port): Reader/Writer std.Io adapters. Zig exposes -// `pub const Reader = std.Io.GenericReader(*Self, error{}, readFn)` and a -// matching Writer. Phase B: impl `bun_io::Read`/`bun_io::Write` (and -// `core::fmt::Write`) on `LinearFifo`. +// Zig exposes `pub const Reader = std.Io.GenericReader(*Self, error{}, readFn)` +// and a matching Writer. The Rust equivalents are the impls on +// `LinearFifo` after the inherent impl below: `bun_core::write::Write` +// (the canonical byte sink, re-exported as `bun_io::Write` — Zig's +// `std.Io.Writer` port), plus `std::io::Read`, `std::io::Write`, and +// `core::fmt::Write` for std interop. There is no canonical in-tree Read +// trait, so `std::io::Read` stands in for the Zig reader. impl LinearFifo> { /// `init` for `.Static`. @@ -357,7 +373,6 @@ impl> LinearFifo { } /// Ensure that the buffer can fit at least `size` items - // TODO(port): narrow error set pub fn ensure_total_capacity(&mut self, size: usize) -> Result<(), AllocError> { if self.buf_len() >= size { return Ok(()); @@ -447,7 +462,7 @@ impl> LinearFifo { #[cfg(debug_assertions)] { // set old range to undefined. Note: may be wrapped around - // PORT NOTE: reshaped for borrowck — capture len, then re-borrow. + // reshaped for borrowck — capture len, then re-borrow. let slice_len = self.readable_slice_mut(0).len(); if slice_len >= count { poison(self.readable_slice_mut(0), count); @@ -497,7 +512,7 @@ impl> LinearFifo { } let n = slice.len().min(dst_left.len()); dst_left[..n].copy_from_slice(&slice[..n]); - // PORT NOTE: NLL drops `slice` borrow here before `&mut self`. + // NLL drops `slice` borrow here before `&mut self`. self.discard(n); dst_left = &mut dst_left[n..]; } @@ -505,7 +520,7 @@ impl> LinearFifo { total - dst_left.len() } - // TODO(port): `pub fn reader(self: *Self) Reader` — see Reader/Writer note. + // Zig `pub fn reader(self: *Self) Reader` → the `std::io::Read` impl below. /// Returns number of items available in fifo #[inline] @@ -535,12 +550,11 @@ impl> LinearFifo { /// Returns a writable buffer of at least `size` items, allocating memory as needed. /// Use `fifo.update` once you've written data to it. - // TODO(port): narrow error set pub fn writable_with_size(&mut self, size: usize) -> Result<&mut [T], AllocError> { self.ensure_unused_capacity(size)?; // try to avoid realigning buffer - // PORT NOTE: reshaped for borrowck — check len, drop borrow, maybe + // reshaped for borrowck — check len, drop borrow, maybe // realign, then take the final borrow. if self.writable_slice(0).len() < size { self.realign(); @@ -566,7 +580,7 @@ impl> LinearFifo { let mut src_left = src; while !src_left.is_empty() { - // PORT NOTE: reshaped for borrowck — scoped block drops the + // reshaped for borrowck — scoped block drops the // `writable` borrow before `self.update`. let n = { let writable = self.writable_slice(0); @@ -581,7 +595,6 @@ impl> LinearFifo { } /// Write a single item to the fifo - // TODO(port): narrow error set pub fn write_item(&mut self, item: T) -> Result<(), AllocError> { self.ensure_unused_capacity(1)?; self.write_item_assume_capacity(item); @@ -605,7 +618,6 @@ impl> LinearFifo { /// Appends the data in `src` to the fifo. /// Allocates more memory as necessary - // TODO(port): narrow error set pub fn write(&mut self, src: &[T]) -> Result<(), AllocError> where T: Copy, @@ -615,7 +627,9 @@ impl> LinearFifo { Ok(()) } - // TODO(port): `pub fn writer(self: *Self) Writer` — see Reader/Writer note. + // Zig `pub fn writer(self: *Self) Writer` → the `bun_core::write::Write` + // (canonical, = `bun_io::Write`), `std::io::Write`, and `core::fmt::Write` + // impls below. /// Make `count` items available before the current read location fn rewind(&mut self, count: usize) { @@ -632,7 +646,6 @@ impl> LinearFifo { } /// Place data back into the read stream - // TODO(port): narrow error set pub fn unget(&mut self, src: &[T]) -> Result<(), AllocError> where T: Copy, @@ -641,7 +654,7 @@ impl> LinearFifo { self.rewind(src.len()); - // PORT NOTE: reshaped for borrowck — copy into first chunk in a scoped + // reshaped for borrowck — copy into first chunk in a scoped // block, drop borrow, then re-borrow for the wrapped chunk. let slice_len = { let s = self.readable_slice_mut(0); @@ -737,9 +750,11 @@ impl> LinearFifo { /// Pump data from a reader into a writer /// stops when reader returns 0 bytes (EOF) /// Buffer size must be set before calling; a buffer length of 0 is invalid. - // TODO(port): `src_reader: anytype, dest_writer: *std.Io.Writer`. Phase B: - // bind to `bun_io::Read`/`bun_io::Write` (or whatever the byte-stream traits - // land as). Stubbed with generic bounds matching the called methods. + // Zig signature: `src_reader: anytype, dest_writer: *std.Io.Writer`. The + // closure bounds are the stable-Rust encoding of those duck-typed streams: + // `src_reader(buf)` ≙ `reader.read(buf)`, `dest_writer(buf)` ≙ + // `writer.write(buf)`, both returning a count (`Ok(0)` from the reader + // means EOF). This keeps `pump` generic over `T`, which `std::io` cannot. pub fn pump(&mut self, mut src_reader: R, dest_writer: &mut W) -> Result<(), E> where R: FnMut(&mut [T]) -> Result, @@ -748,7 +763,7 @@ impl> LinearFifo { debug_assert!(self.buf_len() > 0); loop { if self.writable_length() > 0 { - // PORT NOTE: reshaped for borrowck. + // reshaped for borrowck. let n = { let ws = self.writable_slice(0); src_reader(ws)? @@ -776,6 +791,52 @@ impl> LinearFifo { } } +// ── Reader/Writer adapters (Zig `reader()` / `writer()`) ───────────────────── + +impl> std::io::Read for LinearFifo { + /// Zig `readFn` — drains up to `dst.len()` buffered bytes. `Ok(0)` means + /// the fifo is empty (Zig `error{}` reader: EOF, never an error). + #[inline] + fn read(&mut self, dst: &mut [u8]) -> std::io::Result { + Ok(LinearFifo::read(self, dst)) + } +} + +impl> std::io::Write for LinearFifo { + /// Zig `appendWrite` — appends the whole buffer, growing if `.Dynamic`. + #[inline] + fn write(&mut self, src: &[u8]) -> std::io::Result { + LinearFifo::write(self, src) + .map_err(|AllocError| std::io::Error::from(std::io::ErrorKind::OutOfMemory))?; + Ok(src.len()) + } + + #[inline] + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +/// Zig `fifo.writer().print(fmt, args)` equivalent: `write!(fifo, ...)`. +impl> core::fmt::Write for LinearFifo { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + LinearFifo::write(self, s.as_bytes()).map_err(|AllocError| core::fmt::Error) + } +} + +/// Canonical in-tree byte sink (port of Zig `std.Io.Writer`, re-exported as +/// `bun_io::Write`), so a `LinearFifo` can be passed to every +/// `impl bun_io::Write` consumer. `written_len` keeps its panicking default: +/// a fifo drains, so it does not track total bytes written. +impl> bun_core::write::Write for LinearFifo { + /// Zig `appendWrite` — appends the whole buffer, growing if `.Dynamic`. + #[inline] + fn write_all(&mut self, buf: &[u8]) -> Result<(), bun_core::Error> { + LinearFifo::write(self, buf)?; + Ok(()) + } +} + // ── tests ───────────────────────────────────────────────────────────────────── #[cfg(test)] @@ -854,45 +915,101 @@ mod tests { fifo.shrink(0); - // TODO(port): writer().print / reader().readUntilDelimiterOrEof tests - // depend on the std.Io adapter port. + // Zig: `fifo.writer().print("{s}, {s}!", .{ "Hello", "World" })`. + { + use core::fmt::Write as _; + write!(fifo, "{}, {}!", "Hello", "World").unwrap(); + let mut result = [0u8; 30]; + let n = fifo.read(&mut result); + assert_eq!(b"Hello, World!", &result[..n]); + assert_eq!(0usize, fifo.readable_length()); + } + + // Zig: `fifo.writer().writeAll(...)` then + // `fifo.reader().readUntilDelimiterOrEof(&result, ' ')` four times. + { + std::io::Write::write_all(&mut fifo, b"This is a test").unwrap(); + let mut drained = Vec::new(); + std::io::Read::read_to_end(&mut fifo, &mut drained).unwrap(); + let words: Vec<&[u8]> = drained.split(|&c| c == b' ').collect(); + assert_eq!(vec![&b"This"[..], b"is", b"a", b"test"], words); + } - // TODO(port): pump test depends on std.io.fixedBufferStream port. + // Zig: pump from `fixedBufferStream("pump test")` into a fixed output + // buffer. The closures play the reader/writer roles. + { + fifo.ensure_total_capacity(1).unwrap(); + let input: &[u8] = b"pump test"; + let mut cursor = 0usize; + let mut out: Vec = Vec::new(); + fifo.pump( + |buf: &mut [u8]| -> Result { + let n = buf.len().min(input.len() - cursor); + buf[..n].copy_from_slice(&input[cursor..cursor + n]); + cursor += n; + Ok(n) + }, + &mut |bytes: &[u8]| { + out.extend_from_slice(bytes); + Ok(bytes.len()) + }, + ) + .unwrap(); + assert_eq!(input, &out[..]); + } } - // `inline for ([_]type{u1,u8,u16,u64}) |T|` × `inline for (buffer types)` - // — expanded for one representative element type; the rest are mechanical. - // TODO(port): macro-generate the full T×buffer_type matrix in Phase B. - #[test] - fn linear_fifo_generic_u8_static() { - let mut fifo = LinearFifo::>::init(); + // Shared body for the T×buffer_type matrix below (Zig's inner test block). + fn run_generic_fifo_test(mut fifo: LinearFifo) + where + T: Copy + PartialEq + core::fmt::Debug + From, + B: LinearFifoBuffer, + { + let zero = T::from(0u8); + let one = T::from(1u8); - fifo.write(&[0, 1, 1, 0, 1]).unwrap(); + fifo.write(&[zero, one, one, zero, one]).unwrap(); assert_eq!(5usize, fifo.readable_length()); { - assert_eq!(0u8, fifo.read_item().unwrap()); - assert_eq!(1u8, fifo.read_item().unwrap()); - assert_eq!(1u8, fifo.read_item().unwrap()); - assert_eq!(0u8, fifo.read_item().unwrap()); - assert_eq!(1u8, fifo.read_item().unwrap()); + assert_eq!(zero, fifo.read_item().unwrap()); + assert_eq!(one, fifo.read_item().unwrap()); + assert_eq!(one, fifo.read_item().unwrap()); + assert_eq!(zero, fifo.read_item().unwrap()); + assert_eq!(one, fifo.read_item().unwrap()); assert_eq!(0usize, fifo.readable_length()); } { - fifo.write_item(1).unwrap(); - fifo.write_item(1).unwrap(); - fifo.write_item(1).unwrap(); + fifo.write_item(one).unwrap(); + fifo.write_item(one).unwrap(); + fifo.write_item(one).unwrap(); assert_eq!(3usize, fifo.readable_length()); } { - let mut read_buf = [0u8; 3]; + let mut read_buf = [zero; 3]; let n = fifo.read(&mut read_buf); assert_eq!(3usize, n); // NOTE: It should be the number of items. } } + // Zig: `inline for ([_]type{u1,u8,u16,u64}) |T|` × `inline for (buffer + // types)`. `u1` has no Rust equivalent; the remaining element types are + // crossed with all three buffer kinds. + #[test] + fn linear_fifo_generic_matrix() { + macro_rules! per_type { + ($($T:ty),* $(,)?) => {$( + run_generic_fifo_test(LinearFifo::<$T, StaticBuffer<$T, 32>>::init()); + run_generic_fifo_test(LinearFifo::<$T, DynamicBuffer<$T>>::init()); + let mut backing = [<$T>::from(0u8); 32]; + run_generic_fifo_test(LinearFifo::<$T, SliceBuffer<$T>>::init(&mut backing)); + )*}; + } + per_type!(u8, u16, u64); + } + // 16-slot static buffer: `POWERS_OF_TWO` is true, matching the in-tree // `weak_refs` FIFO in the dev server's source-map store (cap 16), the one // real caller of `ordered_remove_item`. `i32` elements make every shift diff --git a/src/collections/pool.rs b/src/collections/pool.rs index f23dde33c12d..234ea38400f6 100644 --- a/src/collections/pool.rs +++ b/src/collections/pool.rs @@ -9,7 +9,7 @@ use bun_core::Error; // SinglyLinkedList // ────────────────────────────────────────────────────────────────────────── // -// PORT NOTE: Zig's `SinglyLinkedList(comptime T: type, comptime Parent: type)` +// Zig's `SinglyLinkedList(comptime T: type, comptime Parent: type)` // threads `Parent` only so that `Node.release()` can call `Parent.release(node)`. // In Rust the only `Parent` is `ObjectPool`, so `Node::release` is provided as // an inherent method on `ObjectPool` instead and the `Parent` type param is @@ -21,12 +21,12 @@ use bun_core::Error; pub struct Node { // INTRUSIVE: pool.zig:7 — next link in singly-linked free list pub next: *mut Node, - // PORT NOTE: Zig stored `std.mem.Allocator param` here so `destroyNode` + // Zig stored `std.mem.Allocator param` here so `destroyNode` // could free via the originating allocator. In Rust the global mimalloc // allocator owns every `Box>`, so the field is dropped and // `destroy_node` uses `heap::take`. // - // PORT NOTE: `MaybeUninit` not `T` — Zig's `else undefined` (pool.zig:203) + // `MaybeUninit` not `T` — Zig's `else undefined` (pool.zig:203) // is well-defined-until-read, but Rust's `assume_init()` on uninit bytes is // immediate UB for any `T` with validity invariants. Callers that use // `INIT == None` write `data` before reading, so we keep the bytes @@ -34,7 +34,7 @@ pub struct Node { pub data: MaybeUninit, } -// PORT NOTE: `pub const Data = T;` (inherent assoc type) is nightly-only; +// `pub const Data = T;` (inherent assoc type) is nightly-only; // callers can write `T` directly. impl Node { @@ -120,7 +120,7 @@ impl Node { count } - // PORT NOTE: `pub inline fn release(node: *Node) void { Parent.release(node) }` + // `pub inline fn release(node: *Node) void { Parent.release(node) }` // is expressed as `ObjectPool::::release(node)` at call sites; see // module-level note above. } @@ -237,7 +237,7 @@ pub trait ObjectPoolType: Sized { pub struct DataStruct { pub list: SinglyLinkedList, pub loaded: bool, - // PORT NOTE: Zig used `MaxCountInt = std.math.IntFittingRange(0, max_count)`. + // Zig used `MaxCountInt = std.math.IntFittingRange(0, max_count)`. // Rust const generics cannot pick an integer type from a const value; use // `usize` and accept the few extra bytes. // PERF(port): was IntFittingRange — narrow if it shows up on a hot path. @@ -247,7 +247,7 @@ pub struct DataStruct { impl Default for DataStruct { fn default() -> Self { Self { - // PORT NOTE: Zig had `list: LinkedList = undefined` — we zero it. + // Zig had `list: LinkedList = undefined` — we zero it. list: SinglyLinkedList::default(), loaded: false, count: 0, @@ -268,7 +268,7 @@ pub struct ObjectPool< S = UnwiredStorage, >(core::marker::PhantomData<(T, S)>); -// PORT NOTE: `pub const List = SinglyLinkedList(T)` / `pub const Node = Node(T)` +// `pub const List = SinglyLinkedList(T)` / `pub const Node = Node(T)` // inherent assoc types are nightly-only; callers write `SinglyLinkedList` / // `Node` directly. @@ -365,7 +365,7 @@ where // but we don't want to create 3 global variables per pool // instead, we create one global variable per pool // - // PORT NOTE: Rust cannot place a `static` / `thread_local!` inside a + // Rust cannot place a `static` / `thread_local!` inside a // generic `impl`; storage is supplied via the `S: PoolStorage` type // parameter (see `object_pool!` for the usual declaration). #[inline] @@ -385,7 +385,7 @@ where pub fn push(pooled: T) { if cfg!(debug_assertions) { - // PORT NOTE: Zig gated on `env.allow_assert`; that is + // Zig gated on `env.allow_assert`; that is // `Environment.isDebug` ⇒ `cfg!(debug_assertions)`. debug_assert!(!Self::full()); } @@ -450,9 +450,13 @@ where } if LOG_ALLOCATIONS { - // PORT NOTE: Zig wrote to stderr via std.fs; banned here. Route - // through `bun_core::Output` if this is ever flipped on. - // TODO(port): log "Allocate {type_name} - {size} bytes" + // Zig wrote to stderr via std.fs (banned here); route through + // `bun_core::output` instead. + let _ = bun_core::output::File::stderr().write_fmt(format_args!( + "Allocate {} - {} bytes\n", + core::any::type_name::(), + core::mem::size_of::() + )); } // Matches Zig's `data = if (Init) |i| i(..) else undefined` (pool.zig:203). @@ -508,7 +512,11 @@ where let mut d = cell.borrow_mut(); if MAX_COUNT > 0 && d.count >= MAX_COUNT { if LOG_ALLOCATIONS { - // TODO(port): log "Free {type_name} - {size} bytes" + let _ = bun_core::output::File::stderr().write_fmt(format_args!( + "Free {} - {} bytes\n", + core::any::type_name::(), + core::mem::size_of::() + )); } return true; } @@ -550,11 +558,11 @@ where } fn destroy_node(node: *mut Node) { - // TODO(port): Zig special-cased `Type != bun.Vec` here to skip - // `bun.memory.deinit(&node.data)` for `Vec` (a known leak the Zig - // comment calls out). In Rust, dropping `T` is the moral equivalent of - // `bun.memory.deinit`. If `Vec` (the `Vec` port) must keep - // leaking for compat, gate its `Drop` there — not here. + // Zig special-cased `Type != bun.Vec(u8)` here to skip + // `bun.memory.deinit(&node.data)` for `Vec` — a known leak its own + // comment calls out. Dropping `T` below is the moral equivalent of + // `bun.memory.deinit`, applied uniformly; if that compat leak is ever + // needed, gate the `Drop` on the `Vec` port itself, not here. // // SAFETY: `node` was created via `heap::alloc` in `push`/`get` and // is exclusively owned by the caller. `data` is initialized: `destroy_node` @@ -637,7 +645,7 @@ macro_rules! __object_pool_storage { fn with( f: impl FnOnce(&::core::cell::RefCell<$crate::pool::DataStruct<$ty>>) -> R, ) -> R { - // PORT NOTE: Zig's `threadsafe = false` used a plain global + // Zig's `threadsafe = false` used a plain global // `var data`; Rust forbids non-`Sync` statics, so this still // expands to a thread-local. Single-threaded callers see the // same one cell; cross-thread callers get per-thread pools diff --git a/src/collections/string_map.rs b/src/collections/string_map.rs index 9f29691412de..4d7a7b2859bd 100644 --- a/src/collections/string_map.rs +++ b/src/collections/string_map.rs @@ -72,8 +72,16 @@ impl StringMap { self.map.get(key).map(|v| &**v) } - // Zig `sort` takes an `anytype` ctx; defer until a caller needs it. - // TODO(port): StringMap::sort — wire once ArrayHashMap::sort lands. + /// Zig `sort(sort_ctx)` — forwards to the inner map's order-preserving + /// sort. Zig's `anytype` ctx exposed `lessThan(a_index, b_index)`; the + /// closure receives the parallel key/value slices plus the two indices so + /// it can compare on either column. + pub fn sort( + &mut self, + less_than: impl FnMut(&[Box<[u8]>], &[Box<[u8]>], usize, usize) -> bool, + ) { + self.map.sort(less_than); + } // `deinit` → Drop on the inner Vecs. } diff --git a/src/crash_handler/CPUFeatures.rs b/src/crash_handler/CPUFeatures.rs index c9deab1ab632..5628f371aa3b 100644 --- a/src/crash_handler/CPUFeatures.rs +++ b/src/crash_handler/CPUFeatures.rs @@ -1,7 +1,6 @@ // (no c types needed; kept for FFI clarity) use core::fmt; -// TODO(port): move to crash_handler_sys unsafe extern "C" { safe fn bun_cpu_features() -> u8; } @@ -14,8 +13,6 @@ pub(crate) struct CPUFeatures { // Zig: `packed struct(u8)` per-arch. All semantic fields are `bool`; the trailing // `padding: uN = 0` is unused bits. bitflags! models this directly (unknown bits // = padding). Bit order matches Zig packed-struct LSB-first layout. -// PORT NOTE: guide says "bitflags! if every field is bool" — padding is uN, but -// it is pure padding, so bitflags is the correct shape here. #[cfg(target_arch = "x86_64")] bitflags::bitflags! { diff --git a/src/crash_handler/handle_oom.rs b/src/crash_handler/handle_oom.rs index 4b16c972bdd0..cc5bff36d005 100644 --- a/src/crash_handler/handle_oom.rs +++ b/src/crash_handler/handle_oom.rs @@ -29,7 +29,7 @@ use bun_core::Error; /// let thing = match allocate_thing() { Ok(v) => v, Err(err) => bun::handle_oom(err) }; /// ``` /// -/// PORT NOTE: In Rust, `Vec`/`Box` allocation already aborts on OOM via the +/// In Rust, `Vec`/`Box` allocation already aborts on OOM via the /// global allocator's `handle_alloc_error`. Per PORTING.md §Allocators, /// callsites of `bun.handleOom(expr)` translate to bare `expr`. This function /// remains for the residual cases where a `Result` is threaded diff --git a/src/crash_handler/lib.rs b/src/crash_handler/lib.rs index 03ea00707275..73066e7b056a 100644 --- a/src/crash_handler/lib.rs +++ b/src/crash_handler/lib.rs @@ -72,7 +72,6 @@ pub use draft::*; // Local shim for `bun_debug` (no such crate exists yet). These are // std.debug.* placeholders the Zig side leaned on; the Rust port will replace // them with a real debug-info backend in a later pass. -// TODO(port): bun_debug::SelfInfo / SourceLocation / TtyConfig / capture_stack_trace // ────────────────────────────────────────────────────────────────────────── pub mod debug { use super::draft::StackTrace; @@ -124,7 +123,7 @@ pub mod debug { /// provides the symbol-name half (which is what `btjs` actually consumes for /// its `__`/`_llint_call_javascript` prefix checks). `source_location` is left /// `None`, which `print_line_info` already handles. - // PORT NOTE: full `readElfDebugInfo`/`readMachODebugInfo` (~2k LOC of DWARF) not + // The full `readElfDebugInfo`/`readMachODebugInfo` (~2k LOC of DWARF) are not // ported — `dladdr` is the libc-level equivalent for symbol-name resolution. pub struct Module { base_address: usize, @@ -223,7 +222,7 @@ pub mod debug { #[cfg(target_vendor = "apple")] fn lookup_module_dyld(&mut self, address: usize) -> Result<&mut Module, Error> { - // PORT NOTE: Zig walks `_dyld_get_image_header` + LoadCommandIterator. `dladdr` + // Zig walks `_dyld_get_image_header` + LoadCommandIterator. `dladdr` // gives the same `{base_address, fname}` pair on Darwin without the MachO walk. let mut info: libc::Dl_info = bun_core::ffi::zeroed(); // SAFETY: dladdr only reads; out-param is a valid Dl_info. @@ -253,7 +252,7 @@ pub mod debug { /// Port of `Module.getSymbolAtAddress`. #[cfg(windows)] pub fn get_symbol_at_address(&mut self, address: usize) -> Result { - // TODO(port-windows): SPEC DIVERGENCE — Zig's `std.debug.SelfInfo` + // SPEC DIVERGENCE — Zig's `std.debug.SelfInfo` // resolves symbols on Windows via the loaded PE's PDB // (`dbghelp.dll` `SymFromAddr`). That path is not yet ported, so // every Windows backtrace currently prints bare addresses even @@ -301,7 +300,7 @@ pub mod debug { Ok(SymbolInfo { name, compile_unit_name, - // PORT NOTE: DWARF line-table lookup not ported; dladdr does not provide + // DWARF line-table lookup is not ported; dladdr does not provide // file:line. `print_line_info` handles `None` by printing `???:?:?`. source_location: None, }) @@ -541,10 +540,10 @@ mod draft { Ok(()) } - // TODO(port): `Cli` arrives from move-in (MOVE_DOWN bun_runtime::cli::Cli → crash_handler). - // Only the two bits the crash handler needs — main-thread check and the - // one-byte command tag for the trace URL — land here as plain globals that - // `bun_runtime` populates at startup. + // The two bits of CLI state the crash handler needs — main-thread check and + // the one-byte command tag for the trace URL — live here as plain globals + // that `bun_runtime` populates at startup (the full `Cli` stays in + // `bun_runtime::cli`, a higher-tier crate). pub mod cli_state { use core::sync::atomic::{AtomicU8, AtomicU64, Ordering}; @@ -584,7 +583,7 @@ mod draft { /// Non-zero whenever the program triggered a panic. /// The counter is incremented/decremented atomically. - /// PORT NOTE: shared with bun_core::PANICKING so T0 callers see the same state. + /// Shared with bun_core::PANICKING so T0 callers see the same state. use bun_core::PANICKING; // D131: dedup — these read the shared `PANICKING` atomic and were byte-identical // to the bun_core (T0) copies. Re-export so `bun_crash_handler::{is_panicking, @@ -638,7 +637,6 @@ mod draft { pub enum CrashReason { /// From @panic() Panic(&'static [u8]), - // TODO(port): lifetime — Zig holds a borrowed []const u8; using &'static here as a placeholder. /// "reached unreachable code" Unreachable, @@ -720,7 +718,13 @@ mod draft { Parse(&'static [u8]), Visit(&'static [u8]), Print(&'static [u8]), - // TODO(port): lifetime — these slices borrow caller-owned paths; &'static is a placeholder. + // These slices are stored in the `CURRENT_ACTION` thread-local, so they + // are typed `'static`. Callers pass `Source.path` data whose + // `Path<'static>` is itself an upstream `into_static()` lifetime + // erasure of arena/resolver-owned bytes (see paths/lib.rs); the data + // is not truly `'static`. Correctness relies on the `scoped_action` + // RAII guard restoring the thread-local before the owning arena or + // resolver storage is freed. #[cfg(feature = "show_crash_trace")] BundleGenerateChunk(BundleGenerateChunk), #[cfg(not(feature = "show_crash_trace"))] @@ -807,7 +811,7 @@ mod draft { /// /// `source_dir`/`import_path` are caller-interned (DirnameStore / source text) /// and outlive the guard; the `&'static` lifetime erasure matches the existing - /// `Action::Parse`/`Visit`/`Print` slice fields (see TODO(port) above). + /// `Action::Parse`/`Visit`/`Print` slice fields (see the comment on those fields). #[inline] pub fn set_current_action_resolver( source_dir: &[u8], @@ -881,8 +885,8 @@ mod draft { // // Output.errorWriter() is not used here because it may not be configured // if the program crashes immediately at startup. - // TODO(port): std.fs.File.stderr().writerStreaming — local raw StderrWriter (bun_sys - // FileWriter only impls std::io::Write, not the local byte-Write trait) + // Zig: std.fs.File.stderr().writerStreaming — a local raw StderrWriter is used + // because bun_sys's FileWriter only impls std::io::Write, not the local byte-Write trait. let writer = &mut stderr_writer(); // The format of the panic trace is slightly different in debug @@ -939,9 +943,10 @@ mod draft { { abort(); } - } else if UNSUPPORTED_UV_FUNCTION.with(|c| c.get()).is_some() { - // TODO(port): bun_analytics::Features::unsupported_uv_function — using - // the threadlocal as a stand-in for the global counter check. + } else if bun_analytics::features::unsupported_uv_function + .load(Ordering::Relaxed) + > 0 + { let name: &[u8] = UNSUPPORTED_UV_FUNCTION .with(|c| c.get()) .map(|p| { @@ -1069,7 +1074,6 @@ mod draft { target_os = "freebsd" ))] { /* no-op */ } - // TODO(port): wasm @compileError("TODO") } if writer.write_all(b": ").is_err() { @@ -1171,8 +1175,10 @@ mod draft { "Bun has encountered a crash while running the \"{s}\" native plugin.\n\nTo send a redacted crash report to Bun's team,\nplease file a GitHub issue using the link below:\n\n", true, ), bstr::BStr::new(native_plugin_name)).is_err() { abort(); } - } else if UNSUPPORTED_UV_FUNCTION.with(|c| c.get()).is_some() { - // TODO(port): bun_analytics::Features::unsupported_uv_function + } else if bun_analytics::features::unsupported_uv_function + .load(Ordering::Relaxed) + > 0 + { let name: &[u8] = UNSUPPORTED_UV_FUNCTION .with(|c| c.get()) .map(|p| { @@ -1278,7 +1284,6 @@ mod draft { ); Output::flush(); - // TODO(port): comptime assert void == @TypeOf(bun.reloadProcess(...)) bun_core::reload_process(false, true); } } @@ -1497,7 +1502,7 @@ mod draft { err_generic!("Bun could not find a package.json file to install from"); bun_core::note!("Run \"bun init\" to initialize a project"); } else { - // PORT NOTE: Zig picked the format string at comptime; the macros need + // Zig picked the format string at comptime; the macros need // `:literal`, so branch on the const and call separately. if Environment::SHOW_CRASH_TRACE { err_generic!( @@ -1541,7 +1546,6 @@ mod draft { if msg == b"reached unreachable code" { CrashReason::Unreachable } else { - // TODO(port): lifetime — Zig borrows msg; erased to &'static for the noreturn path. // SAFETY: process is about to abort; the borrow is never invalidated. CrashReason::Panic(unsafe { bun_collections::detach_lifetime(msg) }) }, @@ -1768,7 +1772,6 @@ mod draft { { reset_on_posix(); } - // TODO(port): wasm @compileError("TODO") install_hooks(); } @@ -2089,7 +2092,6 @@ mod draft { { let cpu_features = CPUFeatures::get(); - // TODO(port): bun_analytics::GenerateHeader::GeneratePlatform { #[cfg(any( all(target_os = "linux", target_env = "gnu"), @@ -2243,7 +2245,8 @@ mod draft { ) .map_err(fmt_err)?; - // TODO(port): {B:<3.2} byte-size formatting — bun_fmt::bytes() doesn't take width/prec yet + // Zig used `{B:<3.2}`; bun_fmt::bytes() output differs only in spacing + // and adaptive precision — human-readable metadata, not the trace string. write!( writer, "RSS: {} | Peak: {} | Commit: {} | Faults: {}", @@ -2283,7 +2286,6 @@ mod draft { if PANICKING.fetch_sub(1, Ordering::SeqCst) != 1 { // Another thread is panicking, wait for the last one to finish // and call abort() - // TODO(port): builtin.single_threaded → unreachable // Sleep forever without hammering the CPU let futex = AtomicU32::new(0); @@ -2405,7 +2407,7 @@ mod draft { address: i32, // None -> from bun.exe object: Option>, - // TODO(port): Zig stores a borrowed slice into caller's `name_bytes`; using Box<[u8]> here + // Zig stores a borrowed slice into caller's `name_bytes`; Box<[u8]> here // since the only caller writes into a stack buffer and the value is consumed immediately. } @@ -2751,9 +2753,8 @@ mod draft { return false; } - // Honor DO_NOT_TRACK - // TODO(port): bun_analytics::is_enabled - if env_var::DO_NOT_TRACK::get() == Some(true) { + // Honor DO_NOT_TRACK (and the bunfig telemetry setting) + if !bun_analytics::is_enabled() { return false; } @@ -2844,7 +2845,7 @@ mod draft { // SAFETY: we just wrote a NUL terminator at len-1 let end = cmd_line.len() - 1; let cmd_line_slice = &mut cmd_line.slice()[0..end]; - // TODO(port): need [:0] sentinel slice — pass raw pointer + // Rust has no [:0] sentinel slices — pass the raw pointer instead. // SAFETY: all pointer args are either null or point to stack-local buffers/structs valid for the duration of the call; cmd_line is NUL-terminated above let spawn_result = unsafe { windows::kernel32::CreateProcessW( @@ -2885,7 +2886,7 @@ mod draft { let Ok(cwd) = bun_core::getcwd(&mut buf2) else { return; }; - // PORT NOTE: reshaped for borrowck — capture cwd bytes by value (it + // Reshaped for borrowck — capture cwd bytes by value (it // borrows buf2, not buf, so no actual overlap; copy len for clarity). let cwd_bytes = cwd.as_bytes(); let Some(curl) = bun_which::which(&mut buf, path_env, cwd_bytes, b"curl") else { @@ -2933,7 +2934,6 @@ mod draft { _ => {} } } - // TODO(port): wasm @compileError("Not implemented") #[cfg(not(unix))] let _ = url; } @@ -2995,7 +2995,8 @@ mod draft { err_int_workaround_for_zig_ccall_bug: u16, trace: &StackTrace, ) { - // TODO(port): std.meta.Int(.unsigned, @bitSizeOf(anyerror)) — bun_core::Error is errno-based + // Zig passed std.meta.Int(.unsigned, @bitSizeOf(anyerror)); bun_core::Error + // is errno-based, so the error round-trips through its u16 representation. let err = bun_core::Error::from_errno(err_int_workaround_for_zig_ccall_bug as i32); // The format of the panic trace is slightly different in debug @@ -3054,8 +3055,8 @@ mod draft { err: bun_core::Error, maybe_trace: Option<&StackTrace>, ) { - // TODO(port): builtin.have_error_return_tracing — Rust has no error-return tracing; - // decide whether to keep this entire mechanism or strip it. + // Rust has no error-return tracing; `HAVE_ERROR_RETURN_TRACING` is const + // false, so this path is currently dead and kept for parity with the Zig spec. if !debug::HAVE_ERROR_RETURN_TRACING { return; } @@ -3227,7 +3228,6 @@ mod draft { program: &bun_core::ZStr, trace: &StackTrace, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let mut argv: Vec> = Vec::new(); argv.push(program.as_bytes().to_vec()); argv.push(b"--exe".to_vec()); @@ -3372,10 +3372,10 @@ mod draft { pub source_location: Option, pub symbol_name: Box<[u8]>, pub compile_unit_name: Box<[u8]>, - // TODO(port): Zig stores borrowed slices owned by debug_info; using Box<[u8]> here. + // Zig stores borrowed slices owned by debug_info; Box<[u8]> here. } - // PORT NOTE: Zig's `SourceAtAddress.deinit` only freed `source_location.file_name`; + // Zig's `SourceAtAddress.deinit` only freed `source_location.file_name`; // `Option` owns it as `Box<[u8]>` so Drop handles it — no explicit deinit. // D130: deduped — canonical def lives in bun_core (T0). Re-export under the @@ -3402,7 +3402,7 @@ mod draft { .index .min(stack_trace.instruction_addresses.len()); - // PORT NOTE: Zig's `while (...) : ({ frames_left -= 1; frame_index = ... })` continue-expression + // Zig's `while (...) : ({ frames_left -= 1; frame_index = ... })` continue-expression // is inlined at every `continue` site and at end-of-loop below. while frames_left != 0 { if frame_index >= limits.frame_count { @@ -3769,7 +3769,7 @@ mod draft { /// `name` must be a valid NUL-terminated C string. #[unsafe(no_mangle)] pub(crate) unsafe extern "C" fn CrashHandler__unsupportedUVFunction(name: *const c_char) { - // TODO(port): bun_analytics::Features::increment_unsupported_uv_function + bun_analytics::features::unsupported_uv_function.fetch_add(1, Ordering::Relaxed); UNSUPPORTED_UV_FUNCTION.with(|c| c.set(if name.is_null() { None } else { Some(name) })); if env_var::feature_flag::BUN_INTERNAL_SUPPRESS_CRASH_ON_UV_STUB::get() == Some(true) { suppress_reporting(); diff --git a/src/csrf/lib.rs b/src/csrf/lib.rs index 77285aedeae4..873fe781f3f4 100644 --- a/src/csrf/lib.rs +++ b/src/csrf/lib.rs @@ -17,7 +17,6 @@ pub const DEFAULT_EXPIRATION_MS: u64 = 24 * 60 * 60 * 1000; pub const DEFAULT_ALGORITHM: Algorithm = Algorithm::Sha256; /// Error types for CSRF operations -// TODO(port): thiserror not in deps — manual Display/Error impl for now #[derive(strum::IntoStaticStr, Debug, Clone, Copy, PartialEq, Eq)] pub enum Error { InvalidToken, @@ -29,8 +28,8 @@ bun_core::impl_tag_error!(Error); bun_core::named_error_set!(Error); -/// Options for generating CSRF tokens -// TODO(port): Zig has per-field defaults; Rust callers must specify all fields +/// Options for generating CSRF tokens. Zig had per-field defaults (noted on +/// each field); Rust callers must specify all fields. pub struct GenerateOptions<'a> { /// Secret key to use for signing pub secret: &'a [u8], @@ -45,8 +44,8 @@ pub struct GenerateOptions<'a> { pub algorithm: Algorithm, // = DEFAULT_ALGORITHM } -/// Options for validating CSRF tokens -// TODO(port): Zig has per-field defaults; Rust callers must specify all fields +/// Options for validating CSRF tokens. Zig had per-field defaults (noted on +/// each field); Rust callers must specify all fields. pub struct VerifyOptions<'a> { /// The token to verify pub token: &'a [u8], @@ -162,7 +161,7 @@ pub fn verify(options: &VerifyOptions<'_>) -> bool { token = &token[0..token.len() - 1]; } - // PORT NOTE: reshaped for borrowck — compute decoded_len, then borrow buf immutably afterward + // Reshaped for borrowck — compute decoded_len, then borrow buf immutably afterward let decoded_len: usize = match encoding { // shares same decoder but encoder is different see encoding.zig TokenFormat::Base64Url | TokenFormat::Base64 => { diff --git a/src/css/context.rs b/src/css/context.rs index fe7ed8ece086..5b734f03b8c9 100644 --- a/src/css/context.rs +++ b/src/css/context.rs @@ -18,7 +18,7 @@ pub struct SupportsEntry { pub important_declarations: Vec, } -// PORT NOTE: `deinit(this, arena)` deleted — all fields own their storage and drop +// Zig's `deinit(this, arena)` is deleted — all fields own their storage and drop // automatically. `css.deepDeinit` over the Vecs is handled by `Vec`'s Drop. #[derive(Copy, Clone, PartialEq, Eq)] @@ -30,7 +30,7 @@ pub enum DeclarationContext { } pub struct PropertyHandlerContext<'a> { - // PORT NOTE: `arena` is the parser arena that owns the AST being + // `arena` is the parser arena that owns the AST being // minified; bound to `'a` alongside the other borrowed inputs. pub arena: &'a Bump, pub targets: css::targets::Targets, @@ -192,8 +192,6 @@ impl<'a> PropertyHandlerContext<'a> { media_type: css::media_query::MediaType::All, condition: Some(MediaCondition::Feature(Box::new_in( MediaFeature::Plain { - // TODO(port): verify exact MediaFeatureName / MediaFeatureValue - // variant shapes from css::media_query once ported. name: css::media_query::MediaFeatureName::Standard( MediaFeatureId::PrefersColorScheme, ), @@ -233,7 +231,7 @@ impl<'a> PropertyHandlerContext<'a> { dest } - // PORT NOTE: reshaped — Zig passed `comptime dir: []const u8` and `comptime decls: []const u8` + // Reshaped — Zig passed `comptime dir: []const u8` and `comptime decls: []const u8` // and used `@field` to select the Direction variant and the self.ltr/self.rtl Vec by name. // Rust has no @field; pass the Direction value and a borrow of the decls Vec directly. pub fn get_additional_rules_helper( @@ -267,7 +265,7 @@ impl<'a> PropertyHandlerContext<'a> { impl<'a> PropertyHandlerContext<'a> { pub fn reset(&mut self) { - // PORT NOTE: per-element `deinit()` calls dropped — Vec::clear drops each element, + // Per-element `deinit()` calls dropped — Vec::clear drops each element, // and SupportsEntry / Property own their resources via Drop. self.supports.clear(); self.ltr.clear(); @@ -329,7 +327,7 @@ impl<'a> PropertyHandlerContext<'a> { } let fallbacks = unparsed.value.get_fallbacks(bump, &self.targets); - // PORT NOTE: Zig `for (fallbacks.slice()) |c|` copies by value; `SmallList` + // Zig `for (fallbacks.slice()) |c|` copies by value; `SmallList` // has no `IntoIterator`, so spill to a Vec to preserve P3-before-LAB order. for condition_and_fallback in fallbacks.to_owned_slice().into_vec() { self.add_conditional_property( diff --git a/src/css/css_modules.rs b/src/css/css_modules.rs index 1385fa555ea5..de30c4b15628 100644 --- a/src/css/css_modules.rs +++ b/src/css/css_modules.rs @@ -5,12 +5,11 @@ use bun_alloc::{ArenaVec as BumpVec, ArenaVecExt as _}; use bun_collections::ArrayHashMap; use crate as css; -// TODO(port): narrow error set pub use crate::Error; // ───────────────────────────────────────────────────────────────────────── -// `reference_dashed`'s `dest.importRecord()` lookup is hoisted to the caller (see PORT NOTE on -// the method) to satisfy Rust borrowck (caller holds `&mut dest.css_module`). +// `reference_dashed`'s `dest.importRecord()` lookup is hoisted to the caller (see the comment +// on the method) to satisfy Rust borrowck (caller holds `&mut dest.css_module`). // ───────────────────────────────────────────────────────────────────────── pub struct CssModule<'a> { pub config: &'a Config, @@ -46,7 +45,7 @@ impl<'a> CssModule<'a> { } break 'source path.as_ref(); }; - // PORT NOTE: Zig `defer if (alloced) arena.free(source);` — arena-allocated, bulk-freed on bump.reset() + // Zig `defer if (alloced) arena.free(source);` — arena-allocated, bulk-freed on bump.reset() let _ = alloced; // PERF(port): was appendAssumeCapacity — profile if it shows up on a hot path hashes.push(hash( @@ -74,10 +73,10 @@ impl<'a> CssModule<'a> { } } - // PORT NOTE: `deinit` was a no-op (`// TODO: deinit`); Drop is implicit. No `impl Drop` needed. + // Zig's `deinit` was a no-op; Drop is implicit. No `impl Drop` needed. pub fn get_reference(&mut self, bump: &'a Bump, name: &'a [u8], source_index: u32) { - // PORT NOTE: Zig `getOrPut` returns an uninitialized value slot; + // Zig `getOrPut` returns an uninitialized value slot; // bun_collections::ArrayHashMap::get_or_put requires `V: Default` // (CssModuleExport can't be Default — BumpVec field). Reshaped to the // entry()-API instead. @@ -102,7 +101,7 @@ impl<'a> CssModule<'a> { } } - // PORT NOTE: Zig `referenceDashed` took `*Printer` so it could read + // Zig `referenceDashed` took `*Printer` so it could read // `dest.arena` and call `dest.importRecord(idx)`. In Rust the only // caller (`DashedIdentReference::to_css`) already holds a `&mut` borrow of // `dest.css_module` (which *is* `self`), so threading `&mut Printer` in @@ -134,7 +133,7 @@ impl<'a> CssModule<'a> { } None => { // Local export. Mark as used. - // PORT NOTE: Zig `getOrPut` returns an uninitialized value + // Zig `getOrPut` returns an uninitialized value // slot; `CssModuleExport` cannot be `Default` (BumpVec field), // so reshape to the `entry()` API like `get_reference` above. use bun_collections::array_hash_map::MapEntry; @@ -173,7 +172,7 @@ impl<'a> CssModule<'a> { false, ); - // PORT NOTE: std.fmt.allocPrint(arena, "--{s}", .{the_hash}) → bump Vec + // std.fmt.allocPrint(arena, "--{s}", .{the_hash}) → bump Vec // (bumpalo::Vec lacks io::Write; the format string was a pure concat anyway). let mut k = BumpVec::with_capacity_in(2 + the_hash.len(), bump); k.extend_from_slice(b"--"); diff --git a/src/css/css_parser.rs b/src/css/css_parser.rs index 2f1e72e2dd78..bacd2fdd5236 100644 --- a/src/css/css_parser.rs +++ b/src/css/css_parser.rs @@ -90,11 +90,9 @@ pub use crate::values::{ pub use gated_shims::*; -/// Minimal stand-ins for types that live in still-gated sibling *leaf* modules -/// (rules/{keyframes,page,container,...}, values::{number,string}). The hub -/// modules above are real; only the per-rule payload types `AtRulePrelude` -/// reaches into by name remain shimmed here. When a leaf un-gates, delete the -/// matching shim. +/// Re-exports of leaf-module payload types `AtRulePrelude` names directly, +/// plus crate-tier shims for `bun.ast` types that live above this crate's +/// dependency tier. mod gated_shims { // ── rules/ leaf-module payload re-exports ──────────────────────────── @@ -206,7 +204,7 @@ impl SourceLocation { } } - // PORT NOTE: Zig used `anytype` + `@TypeOf` to dispatch on + // Note: Zig used `anytype` + `@TypeOf` to dispatch on // `ParserError | BasicParseError | SelectorParseErrorKind`. In Rust this // becomes a trait `IntoParserError` implemented by each live variant // (the `BasicParseError` arm is dead/ill-typed in Zig — see note below). @@ -229,7 +227,7 @@ impl IntoParserError for ParserError { self } } -// PORT NOTE: Zig's `newCustomError` had a third `@TypeOf` arm for +// Note: Zig's `newCustomError` had a third `@TypeOf` arm for // `BasicParseError`, but that arm is dead and ill-typed — it wraps // `BasicParseError.intoDefaultParseError(err)` (a `ParseError(ParserError)`) // in `.custom`, which expects a `ParserError`. No caller ever passes @@ -247,8 +245,8 @@ pub type PrintResult = Maybe; #[cold] pub fn todo(msg: &str) -> ! { - // bun.analytics.Features.todo_panic = 1; - // TODO(port): analytics counter + // Zig: `bun.analytics.Features.todo_panic = 1;` + bun_core::Global::features::TODO_PANIC.store(1, core::sync::atomic::Ordering::Relaxed); panic!("TODO: {msg}"); } @@ -274,7 +272,7 @@ pub fn void_wrap( /// Shorthand longhand-reconstruction helpers. /// -/// PORT NOTE: Zig's `DefineShorthand` bodies are `@compileError(todo_stuff.depth)` +/// Note: Zig's `DefineShorthand` bodies are `@compileError(todo_stuff.depth)` /// — i.e. instantiating the comptime fn and reaching any method is a compile /// error. The faithful Rust mapping is a trait with **no default bodies**: any /// `impl DefineShorthand for T` that omits a method fails at compile time, same @@ -319,7 +317,7 @@ pub trait DefineShorthand: Sized { fn set_longhand(&mut self, property: &Property) -> bool; } -// PORT NOTE: Zig's `DefineListShorthand` / `DefineRectShorthand` / +// Note: Zig's `DefineListShorthand` / `DefineRectShorthand` / // `DefineSizeShorthand` / `DeriveParse` / `DeriveToCss` comptime fns became // proc-macros (`bun_css_derive::*`, re-exported below) plus the // `impl_rect_shorthand!` / `impl_size_shorthand!` macros in @@ -386,15 +384,16 @@ pub trait EnumProperty: Sized + Copy + Into<&'static str> { where Self: Into, { - // TODO(port): Zig hashed the raw enum int bytes. + // Zig hashed the enum's native-width tag bytes; the width only changes + // the hash value, which never leaves the process, so a fixed u32 is fine. let tag: u32 = (*self).into(); hasher.update(&tag.to_ne_bytes()); } } -/// `DeriveValueType` — maps each enum variant to a `MediaFeatureType` via a -/// comptime field map. -/// TODO(port): proc-macro `#[derive(ValueType)]` with attribute annotations. +/// `DeriveValueType` — maps each enum variant to a `MediaFeatureType`. Zig +/// used a comptime `ValueTypeMap`; the Rust impls are written by hand (see +/// `media_query.rs`, `rules/container.rs`), so no derive macro is needed. pub trait DeriveValueType { fn value_type(&self) -> MediaFeatureType; } @@ -559,7 +558,7 @@ fn parse_until_before( parse_fn: impl FnOnce(C, &mut Parser) -> CssResult, ) -> CssResult { let delimiters = parser.stop_before | delimiters_; - // PORT NOTE: reshaped for borrowck — Zig held `parser.input` aliased + // Note: reshaped for borrowck — Zig held `parser.input` aliased // between the outer Parser and a stack-local "delimited" Parser. In Rust // `&'a mut ParserInput<'a>` is invariant and cannot be reborrowed into a // second `Parser<'a>` while the first lives. We instead temporarily swap @@ -698,7 +697,7 @@ fn parse_nested_block( BlockType::SquareBracket => Delimiters::CLOSE_SQUARE_BRACKET, BlockType::Parenthesis => Delimiters::CLOSE_PARENTHESIS, }; - // PORT NOTE: reshaped for borrowck — same aliasing as parse_until_before. + // Note: reshaped for borrowck — same aliasing as parse_until_before. // Swap stop_before/at_start_of in place rather than constructing a second // Parser over the invariant `&'a mut ParserInput<'a>`. let saved_stop_before = parser.stop_before; @@ -875,7 +874,9 @@ impl CustomAtRuleParser for DefaultAtRuleParser { pub const ENABLE_TAILWIND_PARSING: bool = false; pub type BundlerAtRule = DefaultAtRule; -// TODO(port): when ENABLE_TAILWIND_PARSING == true, this is `TailwindAtRule`. +// Zig: `if (ENABLE_TAILWIND_PARSING) TailwindAtRule else DefaultAtRule` +// (css_parser.zig:1345); the flag is false, so only the DefaultAtRule form is +// ported. pub struct BundlerAtRuleParser<'a> { pub arena: &'a Bump, @@ -895,7 +896,9 @@ pub struct BundlerAtRuleParser<'a> { } impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { - // TODO(port): when ENABLE_TAILWIND_PARSING, Prelude = enum { Tailwind(TailwindAtRule) }. + // Zig gates `Prelude = union(enum) { tailwind: TailwindAtRule }` on + // ENABLE_TAILWIND_PARSING (false; css_parser.zig:1368) — only the void + // form is ported. type Prelude = (); type AtRule = BundlerAtRule; @@ -905,7 +908,8 @@ impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { input: &mut Parser, _: &ParserOptions, ) -> CssResult { - // TODO(port): tailwind branch (gated on ENABLE_TAILWIND_PARSING). + // Zig's tailwind branch is comptime-gated on ENABLE_TAILWIND_PARSING + // (false; css_parser.zig:1374) — only the error path ships. Err(input.new_error(BasicParseErrorKind::at_rule_invalid(name))) } @@ -927,7 +931,8 @@ impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { _: &ParserOptions, _: bool, ) -> Maybe { - // TODO(port): tailwind branch. + // Zig's tailwind branch is comptime-gated on ENABLE_TAILWIND_PARSING + // (false; css_parser.zig:1408) — only the error path ships. Err(()) } @@ -1022,9 +1027,8 @@ impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { // ───────────────────────────── AtRulePrelude ───────────────────────────── // -// The few leaf-module payload types not yet exposed by `rules/mod.rs` -// (KeyframesName, PageSelector, ContainerName, ContainerCondition) come from -// `gated_shims` above. +// The leaf-module payload types (KeyframesName, PageSelector, ContainerName, +// ContainerCondition) are re-exported from `rules/` via `gated_shims` above. pub enum AtRulePrelude { FontFace, @@ -1053,17 +1057,13 @@ pub enum AtRulePrelude { Supports(SupportsCondition), Viewport(VendorPrefix), Keyframes { - // TODO(port): real type is `css_rules::keyframes::KeyframesName` — - // leaf module gated in rules/mod.rs. name: KeyframesName, prefix: VendorPrefix, }, - // TODO(port): real type is `Vec` — gated. Page(Vec), MozDocument, Layer(SmallList), Container { - // TODO(port): real types in `css_rules::container` — gated. name: Option, condition: ContainerCondition, }, @@ -1112,7 +1112,7 @@ pub enum TopLevelState { } pub struct TopLevelRuleParser<'a, AtRuleParserT: CustomAtRuleParser> { - // PORT NOTE: Zig threaded `input.arena()` at every call site; the Rust + // Note: Zig threaded `input.arena()` at every call site; the Rust // `DeclarationList = bumpalo::Vec<'bump, Property>` needs the arena up // front, so cache it here (same `'static`-erased borrow `DeclarationBlock` // already uses crate-wide). @@ -1201,7 +1201,7 @@ pub struct NestedRuleParser<'a, T: CustomAtRuleParser> { pub options: &'a ParserOptions<'a>, pub at_rule_parser: &'a mut T, // todo_stuff.think_mem_mgmt - // PORT NOTE: `DeclarationList<'bump>` borrows the parser arena. Threading + // Note: `DeclarationList<'bump>` borrows the parser arena. Threading // `'bump` here cascades into every rule type; deferred (matches // `StyleRule`'s `'static` erasure in rules/style.rs). pub declarations: DeclarationList<'static>, @@ -1329,7 +1329,7 @@ mod rule_parsers { use super::*; use crate::selectors::parser as selector_parser; - // PORT NOTE: Zig threaded `composes_ctx: anytype` (pointer to the + // Note: Zig threaded `composes_ctx: anytype` (pointer to the // `NestedRuleParser`) directly into `parse_declaration`. Rust's borrow checker // forbids passing `&mut *this` while also borrowing `this.declarations` / // `this.important_declarations`, so split-borrow the three composes fields @@ -1633,7 +1633,7 @@ mod rule_parsers { composes_refs: &mut *self.composes_refs, local_properties: &mut *self.local_properties, }; - // PORT NOTE: reshaped for borrowck — Zig held `self.*` aliased. Spell + // Note: reshaped for borrowck — Zig held `self.*` aliased. Spell // out the impl with a fresh lifetime so `nested_parser` isn't forced // to borrow `rules` for `'a`. let parse_declarations = @@ -2013,7 +2013,7 @@ mod rule_parsers { Ok(()) } AtRulePrelude::Layer(mut layer) => { - // PORT NOTE (css_parser.zig:2393): Zig reads + // Note (css_parser.zig:2393): Zig reads // `prelude.layer.at(0).*` — a struct copy that leaves the list // intact — *then* calls `onLayerRule(&prelude.layer)` so the // hook still observes the 1-element list. Mirror that: clone @@ -2192,7 +2192,7 @@ mod rule_parsers { input: &mut Parser, ) -> CssResult<()> { let loc = this.get_loc(start); - // PORT NOTE: Zig `defer this.composes_refs.clearRetainingCapacity();`. + // Note: Zig `defer this.composes_refs.clearRetainingCapacity();`. // `composes_refs` is `&mut SmallList<..>` borrowed from the parent // `TopLevelRuleParser`, so dropping `NestedRuleParser` on an error path // does NOT clear the underlying storage. A safe `scopeguard::guard` @@ -2203,7 +2203,7 @@ mod rule_parsers { // `TopLevelRuleParser`) strictly outlives this frame. let composes_refs_ptr: *mut SmallList = &raw mut *this.composes_refs; scopeguard::defer! { - // SAFETY: see PORT NOTE above — no aliasing borrow live at drop. + // SAFETY: see the note above — no aliasing borrow live at drop. unsafe { (*composes_refs_ptr).clear_retaining_capacity(); } } // allow composes if: @@ -2311,7 +2311,7 @@ mod rule_parsers { type Declaration = (); fn parse_value(this: &mut Self, name: &[u8], input: &mut Parser) -> CssResult<()> { - // PORT NOTE: split-borrow — see `NestedComposesCtx` above. + // Note: split-borrow — see `NestedComposesCtx` above. // SAFETY: `input.arena()` re-borrows the parser arena through `&self`; // detach that borrow so `input` can be re-borrowed mutably below. The // arena outlives the parser (it owns all parsed allocations). @@ -2510,9 +2510,14 @@ impl PropertyUsage { } } -// TODO(port): Zig: `std.bit_set.ArrayBitSet(usize, ceilPow2(EnumFields(PropertyIdTag).len))`. -// Compute the variant count via `strum::EnumCount` instead of hardcoding 1024. -pub type PropertyBitset = ArrayBitSet<1024, { num_masks_for(1024) }>; +// Zig: `std.bit_set.ArrayBitSet(usize, ceilPowerOfTwo(EnumFields(PropertyIdTag).len))` +// (css_parser.zig:3015). `PropertyIdTag` is a dense `repr(u16)` enum with no +// explicit discriminants whose last variant is `Custom`, so the variant count +// is `Custom + 1`. +pub const PROPERTY_BITSET_BITS: usize = + (PropertyIdTag::Custom as usize + 1).next_power_of_two(); +pub type PropertyBitset = + ArrayBitSet; pub fn fill_property_bit_set( bitset: &mut PropertyBitset, @@ -2569,8 +2574,9 @@ pub struct StyleSheet { pub source_map_urls: Vec>>, pub license_comments: Vec<&'static [u8]>, // TODO(port): lifetime — arena pub options: ParserOptions<'static>, // TODO(port): lifetime - // Zig: `tailwind: if (AtRule == BundlerAtRule) ?*BundlerTailwindState else u0` - // TODO(port): conditional field; for now Option> always. + // Zig: `tailwind: if (AtRule == BundlerAtRule) ?*BundlerTailwindState else u0`. + // Rust has no comptime-conditional fields; `Option>` (always `None` + // for non-bundler sheets) is the permanent shape. pub tailwind: Option>, pub layer_names: Vec, @@ -2601,15 +2607,14 @@ impl StyleSheet { } // ── StyleSheet behavior (parse/minify/to_css) ──────────────────────────────── -// Method *bodies* are ported with `// PORT NOTE:` borrowck reshapes where Zig -// aliased pointers. +// Method *bodies* carry borrowck-reshape notes where Zig aliased pointers. mod stylesheet_impl { use super::*; impl StyleSheet { /// Minify and transform the style sheet for the provided browser targets. /// - /// PORT NOTE: `arena` is the arena that owns this stylesheet's AST + /// Note: `arena` is the arena that owns this stylesheet's AST /// (Zig: `arena: Allocator`). It is threaded into `MinifyContext` so /// downstream `deep_clone` calls allocate alongside the existing tree. pub fn minify( @@ -2698,7 +2703,7 @@ mod stylesheet_impl { local_names: Option<&'a LocalsResultsMap>, symbols: &'a bun_ast::symbol::Map, ) -> PrintResult { - // PORT NOTE: PrinterOptions has `&mut SourceMap` and so isn't Copy; capture + // Note: PrinterOptions has `&mut SourceMap` and so isn't Copy; capture // the lone field we re-read after moving `options` into Printer::new. let project_root = options.project_root; let mut printer = Printer::new( @@ -2803,9 +2808,10 @@ mod stylesheet_impl { ) -> PrintResult { // TODO: this is not necessary // Make sure we always have capacity > 0: https://github.com/napi-rs/napi-rs/issues/1124. - // TODO(port): writer adapter — Zig used std.Io.Writer.Allocating; here we - // route through bun_io::Write over Vec until 'bump dest threads. - // blocked_on: bun_io::Write impl for Vec / dest ownership reshape. + // Zig used std.Io.Writer.Allocating; `Vec` behind `bun_io::Write` + // is the equivalent allocating destination. + // PERF(port): Zig's writer fed an allocator the caller controlled; + // this always heap-allocates — profile if hot. let mut dest: Vec = Vec::with_capacity(1); let result = self.to_css_with_writer( arena, @@ -2830,7 +2836,7 @@ mod stylesheet_impl { import_records: Option<&mut Vec>, source_index: SrcIndex, ) -> Maybe<(StyleSheet, StylesheetExtra), Err> { - // PORT NOTE: Zig instantiated `StyleSheet(DefaultAtRule).parse`; Rust + // Note: Zig instantiated `StyleSheet(DefaultAtRule).parse`; Rust // cannot vary `Self`'s `AtRule` param against `DefaultAtRuleParser`, so // this returns the concrete `StyleSheet`. Callers that // need a custom at-rule call `parse_with` directly. @@ -2971,24 +2977,6 @@ mod stylesheet_impl { // bun.debugAssert() } - pub fn contains_tailwind_directives(&self) -> bool { - // TODO(port): Zig `@compileError` if AtRule != BundlerAtRule. - let mut found_import = false; - for rule in self.rules.v.iter() { - match rule { - CssRule::Custom(_) => return true, - // TODO: layer - CssRule::LayerBlock(_) => {} - CssRule::Import(_) => { - found_import = true; - } - _ => return false, - } - } - let _ = found_import; - false - } - pub fn new_from_tailwind_imports( options: ParserOptions<'static>, imports_from_tailwind: CssRuleList, @@ -3016,7 +3004,7 @@ mod stylesheet_impl { out: &mut CssRuleList, new_import_records: &mut Vec, ) { - // PORT NOTE: the Zig fn takes `*const @This()` but writes + // Note: the Zig fn takes `*const @This()` but writes // `rule.* = .ignored;` through it (Zig has no const-transitivity). // Writing through a `*const`-derived pointer is UB in Rust, so the // receiver is reshaped to `&mut self`. The sole caller (Tailwind @@ -3077,7 +3065,7 @@ mod stylesheet_impl { original_path: b"", flags: Default::default(), }); - // PORT NOTE: reshaped for borrowck — Zig did + // Note: reshaped for borrowck — Zig did // `out.v.appendAssumeCapacity(rule.*)` (bitwise copy) then // `rule.* = .ignored`. Rust moves the rule out via // `mem::replace` (no `Clone` bound needed) and pushes that. @@ -3099,6 +3087,27 @@ mod stylesheet_impl { } } + // Zig compile-errors unless `AtRule == BundlerAtRule` + // (css_parser.zig:3355); the concrete impl enforces the same restriction. + impl StyleSheet { + pub fn contains_tailwind_directives(&self) -> bool { + let mut found_import = false; + for rule in self.rules.v.iter() { + match rule { + CssRule::Custom(_) => return true, + // TODO: layer + CssRule::LayerBlock(_) => {} + CssRule::Import(_) => { + found_import = true; + } + _ => return false, + } + } + let _ = found_import; + false + } + } + impl StyleAttribute { pub fn parse( arena: &'static Bump, @@ -3149,8 +3158,8 @@ mod stylesheet_impl { // ); let symbols = bun_ast::symbol::Map::init_list(Default::default()); - // TODO(port): writer adapter — Zig used std.Io.Writer.Allocating; route - // through bun_io::Write over Vec until 'bump dest threads. + // Zig used std.Io.Writer.Allocating; `Vec` behind `bun_io::Write` + // is the equivalent allocating destination. let mut dest: Vec = Vec::new(); let mut printer = Printer::new( arena, @@ -3184,7 +3193,7 @@ mod stylesheet_impl { import_records: &mut Vec, source_index: SrcIndex, ) -> Maybe<(Self, StylesheetExtra), Err> { - // PORT NOTE: Zig aliased `import_records` into both `BundlerAtRuleParser` + // Note: Zig aliased `import_records` into both `BundlerAtRuleParser` // *and* the inner `Parser` (css_parser.zig:3245), and aliased `&options` // into the at-rule parser while also passing `options` by value (struct // copy) to `parseWith`. Rust forbids both overlaps directly: @@ -3226,7 +3235,7 @@ mod stylesheet_impl { // ───────────────────────────── StyleAttribute ───────────────────────────── pub struct StyleAttribute { - // PORT NOTE: `DeclarationBlock<'bump>` borrows the parser arena; lifetime + // Note: `DeclarationBlock<'bump>` borrows the parser arena; lifetime // erased to `'static` until 'bump threads through the rule tree (matches // `StyleRule.declarations` in rules/style.rs). pub declarations: DeclarationBlock<'static>, @@ -3471,7 +3480,7 @@ bitflags::bitflags! { const CSS_MODULES = 0b1; } } -// PORT NOTE: Zig packed struct had `css_modules: bool`. Expose accessor: +// Note: Zig packed struct had `css_modules: bool`. Expose accessor: impl ParserOpts { #[inline] pub fn css_modules(self) -> bool { @@ -4564,14 +4573,13 @@ pub struct Tokenizer<'a> { const FORM_FEED_BYTE: u8 = 0x0C; const REPLACEMENT_CHAR: u32 = 0xFFFD; const REPLACEMENT_CHAR_UNICODE: [u8; 3] = [0xEF, 0xBF, 0xBD]; -/// UTF-8 encoding of U+0FFD — used by `serializer` where Zig called -/// `bun.strings.encodeUTF8Comptime(0xFFD)` (css_parser.zig:6747, :6937). The -/// Zig literal is `0xFFD` (sic — likely a typo for `0xFFFD`), but the spec is -/// ground truth: encode 0x0FFD → [0xE0, 0xBF, 0xBD] to byte-match. -/// TODO(port): confirm whether the spec itself needs fixing to 0xFFFD. -// TODO(port): verify upstream — Zig wrote 0xFFD, comment says "replacement -// character" which is U+FFFD. Byte-matching the spec (0x0FFD) for now. -const REPLACEMENT_CHAR_UTF8: &[u8] = &[0xE0, 0xBF, 0xBD]; +/// UTF-8 encoding of U+FFFD REPLACEMENT CHARACTER, written by `serializer` +/// when escaping a NUL byte (css-syntax requires U+0000 → U+FFFD; upstream +/// rust-cssparser writes `"\u{FFFD}"`). Zig called +/// `bun.strings.encodeUTF8Comptime(0xFFD)` (css_parser.zig:6747, :6937) — a +/// typo for `0xFFFD` per its own "replacement character" comment — and so +/// emitted U+0FFD (`[0xE0, 0xBF, 0xBD]`); that bug is fixed here. +const REPLACEMENT_CHAR_UTF8: &[u8] = &REPLACEMENT_CHAR_UNICODE; const MAX_ONE_B: u32 = 0x80; const MAX_TWO_B: u32 = 0x800; const MAX_THREE_B: u32 = 0x10000; @@ -4669,7 +4677,7 @@ impl<'a> Tokenizer<'a> { pub fn see_function(&mut self, name: &[u8]) { if self.var_or_env_functions == SeenStatus::LookingForThem { - // PORT NOTE: Zig had `and` here (always false); preserved. + // Note: Zig had `and` here (always false); preserved. if strings::eql_case_insensitive_ascii_check_length(name, b"var") && strings::eql_case_insensitive_ascii_check_length(name, b"env") { @@ -5654,15 +5662,14 @@ fn byte_to_decimal_digit(b: u8) -> Option { } pub fn split_source_map(contents: &[u8]) -> Option<&[u8]> { - // FIXME: Use bun CodepointIterator - // TODO(port): Zig used std.unicode.Utf8Iterator. Approximate with byte - // scan since the delimiters are all ASCII. + // Zig used std.unicode.Utf8Iterator; a byte scan is equivalent because the + // delimiters are all ASCII and ASCII bytes never occur inside a multi-byte + // UTF-8 sequence. Zig returned `[0..iter.i]` where `i` is *after* the + // matched codepoint — hence `i + 1`. for (i, &c) in contents.iter().enumerate() { match c { b' ' | b'\t' | FORM_FEED_BYTE | b'\r' | b'\n' => { return Some(&contents[0..i + 1]); - // PORT NOTE: Zig returned `[0..iter.i]` where `i` is *after* - // the codepoint — preserved. } _ => {} } @@ -6005,12 +6012,10 @@ impl Token { } // `impl Display for Token` lives at crate root (lib.rs) — minimal rendering -// for error messages. The full Zig `Token.format` (CSS-serialization-correct) -// is `Token::to_css_generic` above; switch lib.rs's impl to delegate once -// dependents stop relying on the simple form. -// TODO(port): Zig `format` had subtle differences from `to_css_generic` -// (quoted_string→serialize_string, idhash→serialize_identifier). Specialize -// if it matters for diagnostics. +// for error messages only. The CSS-serialization-correct form is +// `Token::to_css_generic` above; Zig's `Token.format` additionally routed +// quoted_string→serialize_string and idhash→serialize_identifier, a purely +// cosmetic difference in diagnostics output. /// Byte-writer trait for `serializer` and `to_css_generic` (replaces Zig /// `anytype` writer). Aliased to the canonical `bun_io::Write`; the associated @@ -6616,7 +6621,7 @@ pub mod to_css { local_names, symbols, ); - // PORT NOTE: Zig special-cased `T == CSSString` → `CSSStringFns.toCss`; + // Note: Zig special-cased `T == CSSString` → `CSSStringFns.toCss`; // in Rust the `ToCss` impl on `CSSString` routes there directly, so the // generic dispatch suffices. this.to_css(&mut printer)?; diff --git a/src/css/declaration.rs b/src/css/declaration.rs index a537474326aa..07518aafdf41 100644 --- a/src/css/declaration.rs +++ b/src/css/declaration.rs @@ -4,10 +4,12 @@ use bun_alloc::ArenaVecExt as _; pub use css::Error; use css::{CssResult as Result, PrintErr, Printer}; -// PORT NOTE: every leaf property module is currently a `handler_stub!` ZST in -// properties/mod.rs (no-op `handle_property`/`finalize`). The real handler -// bodies un-gate per-module as the values/ calc lattice lands; this file -// composes over whichever surface is live. +// The `*Handler` types imported below are the real per-shorthand-group +// implementations from their leaf modules (BackgroundHandler, BoxShadowHandler, +// BorderRadiusHandler, …). A few leaf modules still keep individual method +// bodies internally gated until their deps land — see the per-module status +// notes at the top of properties/mod.rs; this file composes over whichever +// surface is live. use crate::css_properties::align::AlignHandler; use crate::css_properties::background::BackgroundHandler; use crate::css_properties::border::BorderHandler; @@ -50,8 +52,8 @@ pub struct DebugFmt<'a, 'bump>(&'a DeclarationBlock<'bump>); impl<'a, 'bump> core::fmt::Display for DebugFmt<'a, 'bump> { fn fmt(&self, writer: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - // PORT NOTE: debug formatter — uses a throwaway local arena for the - // printer's scratch buffers (Zig threaded the parser arena). + // Debug formatter: uses a throwaway local arena for the printer's + // scratch buffers (Zig threaded the parser arena). let bump = Bump::new(); let mut arraylist: Vec = Vec::new(); let symbols = bun_ast::symbol::Map::init_list(Default::default()); @@ -103,12 +105,12 @@ impl<'bump> DeclarationBlock<'bump> { important_handler: &mut DeclarationHandler<'bump>, context: &mut css::PropertyHandlerContext, ) { - // PORT NOTE: Zig threaded `context.arena` through every append; the + // Zig threaded `context.arena` through every append; the // Rust `PropertyHandlerContext` dropped that field, so we recover the // arena from the handler's own bump-backed accumulator instead. let bump: &'bump Bump = handler.decls.bump(); - // PORT NOTE: Zig used a local generic `handle` fn with comptime field + // Zig used a local generic `handle` fn with comptime field // name + bool. Unrolled to two calls over a shared inner fn; reshaped // for borrowck (iterate via &mut, move prop out and overwrite slot). #[inline] @@ -145,7 +147,7 @@ impl<'bump> DeclarationBlock<'bump> { handler.finalize(context); important_handler.finalize(context); - // PORT NOTE: Zig swapped old lists out, deferred their deinit, then + // Zig swapped old lists out, deferred their deinit, then // assigned the handler accumulators. In Rust the old bumpalo Vecs drop // implicitly on overwrite (arena reclaims on reset). self.important_declarations = @@ -168,7 +170,6 @@ impl<'bump> DeclarationBlock<'bump> { let length = self.len(); let mut i: usize = 0; - // PORT NOTE: Zig used `inline for` over field names with @field; unrolled to 2 arms. for decl in self.declarations.iter() { decl.to_css(dest, false)?; if i != length - 1 { @@ -198,7 +199,6 @@ impl<'bump> DeclarationBlock<'bump> { let mut i: usize = 0; let length = self.len(); - // PORT NOTE: Zig used `inline for` over field names with @field; unrolled to 2 arms. for decl in self.declarations.iter() { dest.newline()?; decl.to_css(dest, false)?; @@ -224,7 +224,7 @@ impl<'bump> DeclarationBlock<'bump> { // ─── parse ──────────────────────────────────────────────────────────────── // -// PORT NOTE: every consumer (`StyleRule`, `Keyframe`, `PageRule`, +// Every consumer (`StyleRule`, `Keyframe`, `PageRule`, // `StyleAttribute`, `NestedRuleParser`) stores `DeclarationBlock<'static>` — // the crate-wide `'bump`-erasure placeholder until `'bump` threads through // `CssRule`. `parse()` therefore lives on the `'static` instantiation and @@ -258,7 +258,7 @@ impl DeclarationBlock<'static> { } // errdefer doesn't fire on `return .{ .err = ... }` — Result(T) is a tagged // union, not an error union. Free any declarations accumulated so far. - // PORT NOTE: in Rust, `declarations`/`important_declarations` are bumpalo + // In Rust, `declarations`/`important_declarations` are bumpalo // Vec and drop on early return; deepDeinit is implicit via Drop. return Err(e); } @@ -307,9 +307,6 @@ impl<'bump> DeclarationBlock<'bump> { } pub fn deep_clone(&self, bump: &'bump Bump) -> Self { - // PORT NOTE: `css.implementDeepClone` is comptime field reflection; - // for a struct it deep-clones each field. `Property::deep_clone` is - // the inherent per-variant impl in properties_generated.rs. Self { important_declarations: bun_alloc::vec_from_iter_in( self.important_declarations @@ -333,10 +330,6 @@ pub(crate) struct PropertyDeclarationParser<'a, 'bump> { pub options: &'a css::ParserOptions<'a>, } -// PORT NOTE: Zig's nested AtRuleParser/QualifiedRuleParser/DeclarationParser/ -// RuleBodyItemParser are structural duck-typing namespaces consumed by -// RuleBodyParser(T) at comptime. In Rust these are trait impls. - impl<'a, 'bump> css::AtRuleParser for PropertyDeclarationParser<'a, 'bump> { type Prelude = (); type AtRule = (); @@ -432,7 +425,7 @@ pub fn parse_declaration<'bump>( ) } -// PORT NOTE: Zig `composes_ctx: anytype` — branches on +// Zig's `composes_ctx: anytype` branches on // `comptime @TypeOf(composes_ctx) != void`. The Rust shape is a `ComposesCtx` // trait (defined in `css_parser.rs`); `NoComposesCtx` returns // `DisallowEntirely` so the `void` fast-path collapses into the match's @@ -459,9 +452,6 @@ where delimiters |= css::Delimiters::CURLY_BRACKET; } let source_location = input.current_source_location(); - // PORT NOTE: Zig threaded `&closure` + fn through `parseUntilBefore`; the - // Rust method takes a single `FnOnce(&mut Parser)`, so capture by move - // (`PropertyId` is `Copy`, `options` is a borrow). let mut property = input.parse_until_before(delimiters, |input2: &mut css::Parser| { css::Property::parse(property_id, input2, options) })?; @@ -481,8 +471,6 @@ where composes_ctx.record_composes(composes); } css::ComposesState::DisallowNested(info) => { - // PORT NOTE: Zig passed an empty notes slice; `warn_fmt` - // is the no-notes path. options.warn_fmt( format_args!("\"composes\" is not allowed inside nested selectors"), info.line, @@ -517,9 +505,10 @@ where /// Per-shorthand-group handler state used by `DeclarationBlock::minify`. /// -/// PORT NOTE: each `*Handler` is a `handler_stub!` ZST until its leaf module -/// un-gates; `Direction` is the data-only `properties::text` enum. The struct -/// shape is the real Zig layout — only the handler *bodies* are deferred. +/// Each `*Handler` is the real implementation from its leaf module (see the +/// per-module status notes in properties/mod.rs for any internally-gated +/// bodies); `Direction` is the data-only `properties::text` enum. The struct +/// shape matches the Zig layout. pub struct DeclarationHandler<'bump> { pub background: BackgroundHandler, pub border: BorderHandler, diff --git a/src/css/dependencies.rs b/src/css/dependencies.rs index 5ebca15c04a6..b773fb91b3b1 100644 --- a/src/css/dependencies.rs +++ b/src/css/dependencies.rs @@ -33,25 +33,21 @@ impl Location { column: loc.column, } } - - // PORT NOTE: Zig `hash` / `eql` methods called `css.implementHash` / `css.implementEql` - // (comptime struct-field reflection). Replaced by `#[derive(Hash, PartialEq, Eq)]` above - // per PORTING.md §Comptime reflection. } /// An `@import` dependency. pub struct ImportDependency { /// The url to import. - // TODO(port): lifetime — arena-borrowed from `rule.url` (CSS arena); consider `&'bump [u8]`. + // Lifetime: arena-borrowed from `rule.url` (CSS arena); valid until the arena is reset. pub url: *const [u8], /// The placeholder that the URL was replaced with. - // TODO(port): lifetime — arena-allocated by `css_modules::hash`. + // Lifetime: arena-allocated by `css_modules::hash`. pub placeholder: *const [u8], /// An optional `supports()` condition. - // TODO(port): lifetime — arena-allocated by `to_css::string`. + // Lifetime: arena-allocated by `to_css::string`. pub supports: Option<*const [u8]>, /// A media query. - // TODO(port): lifetime — arena-allocated by `to_css::string`. + // Lifetime: arena-allocated by `to_css::string`. pub media: Option<*const [u8]>, /// The location of the dependency in the source file. pub loc: SourceRange, @@ -109,7 +105,6 @@ impl ImportDependency { let placeholder = crate::css_modules::hash( bump, - // PORT NOTE: Zig "{s}_{s}", .{ filename, rule.url } → fmt::Arguments format_args!( "{}_{}", bstr::BStr::new(filename), @@ -119,7 +114,7 @@ impl ImportDependency { ); ImportDependency { - // TODO(zack): should we clone this? lightningcss does that + // lightningcss clones this; we borrow from the arena instead. url: std::ptr::from_ref::<[u8]>(rule.url), placeholder: std::ptr::from_ref::<[u8]>(placeholder), supports, @@ -130,9 +125,12 @@ impl ImportDependency { line: rule.loc.line + 1, column: rule.loc.column, }, + // Assumes the `@import "url"` form: 8 = len of `@import `, +2 for the + // quotes. The `@import url(...)` form yields a slightly-off range — + // a limitation inherited from lightningcss (and the Zig port). 8, rule.url.len() + 2, - ), // TODO: what about @import url(...)? + ), } } } @@ -140,10 +138,10 @@ impl ImportDependency { /// A `url()` dependency. pub struct UrlDependency { /// The url of the dependency. - // TODO(port): lifetime — arena-borrowed from `import_records[..].path.pretty`. + // Lifetime: arena-borrowed from `import_records[..].path.pretty`. pub url: *const [u8], /// The placeholder that the URL was replaced with. - // TODO(port): lifetime — arena-allocated by `css_modules::hash`. + // Lifetime: arena-allocated by `css_modules::hash`. pub placeholder: *const [u8], /// The location of the dependency in the source file. pub loc: SourceRange, @@ -173,7 +171,7 @@ impl UrlDependency { /// Represents the range of source code where a dependency was found. pub struct SourceRange { /// The filename in which the dependency was found. - // TODO(port): lifetime — borrowed from caller (printer's filename); arena/static. + // Lifetime: borrowed from the caller (printer's filename); arena- or statically-backed. pub file_path: *const [u8], /// The starting line and column position of the dependency. pub start: Location, diff --git a/src/css/error.rs b/src/css/error.rs index 206ca1e44dc3..f0987500fce6 100644 --- a/src/css/error.rs +++ b/src/css/error.rs @@ -5,7 +5,6 @@ use crate::{Location, SourceLocation, Token}; // Arena-owned byte slice. CSS is an AST crate (see PORTING.md §Allocators); these // slices point into the parser arena / source text and are never individually freed. -// TODO(port): arena slice lifetime — thread `<'bump>` or switch to StoreRef. use crate::Str; #[inline(always)] @@ -194,7 +193,6 @@ impl ErrorLocation { &self, source: &bun_ast::Source, ) -> Result { - // TODO(port): narrow error set (Zig narrowed to alloc-only). // SAFETY: `'bump`-erasure — `bun_ast::Location.line_text` is `Option<&'static [u8]>` // (`Str` placeholder per src/logger/lib.rs); the slice borrows // `source.contents` which outlives the diagnostic. Re-thread once diff --git a/src/css/generics.rs b/src/css/generics.rs index 74f7a4c27608..01fe96f301de 100644 --- a/src/css/generics.rs +++ b/src/css/generics.rs @@ -646,7 +646,7 @@ mod inherent_bridge { use crate::properties::border_image::{ BorderImage, BorderImageRepeat, BorderImageSideWidth, BorderImageSlice, }; - // PORT NOTE: BorderImageRepeat/SideWidth/Slice carry inherent + // BorderImageRepeat/SideWidth/Slice carry inherent // `deep_clone(&self, &Arena)` / `eql(&self, &Self)` (no `Clone`/`PartialEq` // derives — see border_image.rs), so route through bridge_deep_clone/eql. bridge_deep_clone!(BorderImageRepeat, BorderImageSideWidth, BorderImageSlice); @@ -655,8 +655,8 @@ mod inherent_bridge { bridge_eql!(BorderImage); use crate::properties::border_radius::BorderRadius; - // PORT NOTE: BorderRadius has inherent deep_clone/eql (Size2D lacks - // Clone/PartialEq derives — see border_radius.rs PORT NOTE). + // BorderRadius has inherent deep_clone/eql (Size2D lacks + // Clone/PartialEq derives — see border_radius.rs). bridge_deep_clone!(BorderRadius); bridge_eql!(BorderRadius); @@ -865,9 +865,11 @@ mod inherent_bridge { bridge_eql_partialeq!(PropertyId); } -// TODO(port): Zig also special-cases `@typeInfo(T).struct.layout == .packed` → -// bitwise `==`. In Rust those are `bitflags!` types implementing `PartialEq`; -// add `impl CssEql for T` or per-type impls. +// Zig special-cased packed structs (`@typeInfo(T).struct.layout == .packed`) with a +// bitwise `==`. In Rust those are `bitflags!` types whose derived `PartialEq` already +// compares contents, routed through the per-type `bridge_eql_partialeq!` calls above. +// (A blanket `impl CssEql for T` would collide with the container +// blanket impls under coherence, so coverage stays per-type.) // ─────────────────────────────────────────────────────────────────────────────── // Hash @@ -1182,7 +1184,7 @@ is_compatible_container!( // // `Parse` is intentionally lifetime-free: every value-type parser takes // `&mut Parser<'_>` (the borrowed source slice) and returns an owned value. -// TODO(refactor): `'bump` arena threading is a follow-up; until then the parser +// `'bump` arena threading is a follow-up; until then the parser // holds the arena and arena-backed lists go through `from_list(Vec)`. /// `T::parse(&mut Parser) -> CssResult`. @@ -1193,7 +1195,7 @@ pub trait Parse: Sized { /// `T::parse_with_options(&mut Parser, &ParserOptions) -> CssResult`. /// /// Zig falls through to `parse` when a type has no `parseWithOptions` decl. -/// PORT NOTE: Rust can't express that as a `where Self: Parse` default method +/// Rust can't express that as a `where Self: Parse` default method /// — the bound becomes part of the *method signature*, so the free /// `parse_with_options::` below would require `T: Parse` even for impls /// that override the body. Instead the fallthrough lives in @@ -1522,8 +1524,8 @@ impl TrySign for CSSNumber { Some(CSSNumberFns::sign(*self)) } } -// TODO(port): Zig fallback `if @hasDecl(T, "sign") T.sign else T.trySign` — -// model as a trait with default method delegating to `Sign` where available. +// Zig fell back via `@hasDecl(T, "sign")`; Rust has no decl probing, so each type +// implements `TrySign` explicitly (delegating to its inherent `sign` where one exists). pub trait TryMap: Sized { // Zig: `comptime map_fn: *const fn(f32) f32` — generic param preserves monomorphization. diff --git a/src/css/lib.rs b/src/css/lib.rs index 101d7f2b906a..39f791191b71 100644 --- a/src/css/lib.rs +++ b/src/css/lib.rs @@ -26,7 +26,6 @@ extern crate self as bun_css; /// _ => Err(location.new_unexpected_token_error(token)), /// }} /// ``` -// TODO(port): swap body to phf when CI hasher lands. #[macro_export] macro_rules! match_ignore_ascii_case { ($name:expr, { $( $($lit:literal)|+ $(if $guard:expr)? => $arm:expr ,)* _ => $fallback:expr $(,)? }) => {{ @@ -387,15 +386,16 @@ pub struct Num { pub struct Dimension { pub num: Num, /// e.g. "px" - // TODO(port): arena lifetime — &'static placeholder per PORTING.md §AST crates. + // Borrows the parser arena/source; `&'static` placeholder per PORTING.md §AST crates. pub unit: &'static [u8], } /// CSS lexer token. Data-only definition hoisted out of `css_parser.rs`; the /// `to_css*`/`eql`/`hash` impls stay in `css_parser.rs` (gated) since they /// depend on `serializer::*` and `generics`. -// TODO(port): every &'static [u8] payload borrows the parser arena/source; -// thread `<'a>` once the bumpalo arena lifetime is plumbed. +// Every `&'static [u8]` payload actually borrows the parser arena/source text and +// must not outlive the arena; `&'static` is the crate-wide placeholder until the +// bumpalo arena lifetime is plumbed through. #[derive(Clone, Debug)] pub enum Token { Ident(&'static [u8]), diff --git a/src/css/media_query.rs b/src/css/media_query.rs index 328d6423a18f..7501eaeb3f5f 100644 --- a/src/css/media_query.rs +++ b/src/css/media_query.rs @@ -10,9 +10,8 @@ use bun_alloc::ArenaPtr; pub use crate::Error; -// TODO(port): the CSS crate borrows strings from parser input with lifetime `'i` -// (matching lightningcss). This module avoids struct lifetime params; consider -// threading `'i` through `MediaType::Custom`, `Ident`, `DashedIdent`, etc. +// Strings here borrow parser input/arena memory but the structs carry no lifetime +// params (crate-wide `&'static`/raw-slice placeholder convention); see lib.rs. // ───────────────────────── value-type imports ───────────────────────── // Real `values/` payloads — the calc lattice has un-gated, so the local @@ -181,11 +180,11 @@ pub enum MediaType { /// `screen`. Screen, /// An unknown / deprecated / custom media type. - // TODO(port): arena lifetime — Zig borrowed parser input. + // Lifetime: arena-borrowed parser input; valid until the arena is reset. Custom(*const [u8]), } -// PORT NOTE: hand-rolled — derived PartialEq on `*const [u8]` compares +// Hand-rolled — derived PartialEq on `*const [u8]` compares // address+len, not byte content. Spec `MediaType.eql` compares slice bytes // (via `css.implementEql`); adjacent-@media merging (rules.zig) depends on // content equality across distinct arena offsets. @@ -203,7 +202,6 @@ impl PartialEq for MediaType { } // Flags for `parse_query_condition`. -// PORT NOTE: Zig `packed struct(u8)` with two bool fields → bitflags! bitflags::bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct QueryConditionFlags: u8 { @@ -255,8 +253,8 @@ pub enum MediaFeatureName { Unknown(Ident), } -// PORT NOTE: `eql` was hand-written byte compare on the ident slices; data-only -// PartialEq derived once `Ident`/`DashedIdent` gain `PartialEq` (values/ un-gate). +// Data-only PartialEq, derived once `Ident`/`DashedIdent` gained `PartialEq` +// (Zig's `eql` was a hand-written byte compare on the ident slices). impl PartialEq for MediaFeatureName { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -296,7 +294,7 @@ pub enum QueryFeature { } /// Comparison operator in a range media feature. -// PORT NOTE: discriminants are power-of-two bitflags — Zig media_query.zig +// Discriminants are power-of-two bitflags — Zig media_query.zig // bitwise-ORs `@intFromEnum(start_operator) | @intFromEnum(end_operator)` to // validate interval operator pairs. Do NOT use implicit 0..=4. #[repr(u8)] @@ -315,7 +313,7 @@ pub enum MediaFeatureComparison { } /// [media feature value](https://drafts.csswg.org/mediaqueries/#typedef-mf-value). -// PORT NOTE: `Debug` hand-rolled below — `Length` (calc tree) does not derive +// `Debug` hand-rolled below — `Length` (calc tree) does not derive // `Debug`, but the `MediaCondition`/`QueryFeature` chain wants it for // diagnostics. #[derive(Clone)] @@ -355,7 +353,7 @@ impl core::fmt::Debug for MediaFeatureValue { } } -// PORT NOTE: derive(PartialEq) blocked on `Ident`/`EnvironmentVariable` lacking +// derive(PartialEq) blocked on `Ident`/`EnvironmentVariable` lacking // std `PartialEq`; hand-roll all arms (Zig: `css.implementEql`). impl PartialEq for MediaFeatureValue { fn eq(&self, other: &Self) -> bool { @@ -963,8 +961,6 @@ impl MediaFeatureName { pub(crate) fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { match self { MediaFeatureName::Standard(v) => v.to_css(dest), - // PORT NOTE: Zig `DashedIdentFns.toCss` → `dest.writeDashedIdent` - // (handles css-module name rewriting). MediaFeatureName::Custom(d) => d.to_css(dest), MediaFeatureName::Unknown(v) => v.to_css(dest), } @@ -1021,7 +1017,7 @@ impl MediaFeatureName { None }; - // PORT NOTE: Zig `allocPrint("-webkit-{s}", .{name})` then + // Zig `allocPrint("-webkit-{s}", .{name})` then // `parse_utility.parseString(.., FeatureId.parse)` — the re-tokenize is // only to feed `DefineEnumProperty.parse` an ident token. Here // `FeatureIdTrait::from_str` does the same case-insensitive table lookup @@ -1057,7 +1053,7 @@ impl MediaFeatureName { impl MediaFeatureComparison { pub(crate) fn to_css(self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { match self { - // PORT NOTE(suspect): Zig emits '-' for `Equal` (media_query.zig:1156), + // Suspect but intentional: Zig emits '-' for `Equal` (media_query.zig:1156), // diverging from the spec `=` and from this enum's strum tag. Ported // byte-for-byte; revisit if upstream fixes. MediaFeatureComparison::Equal => dest.delim(b'-', true), @@ -1121,7 +1117,12 @@ impl MediaFeatureValue { MediaFeatureValue::Resolution(res) => MediaFeatureValue::Resolution(res.add_f32(other)), MediaFeatureValue::Ratio(ratio) => MediaFeatureValue::Ratio(ratio.add_f32(other)), MediaFeatureValue::Ident(id) => MediaFeatureValue::Ident(id), - MediaFeatureValue::Env(env) => MediaFeatureValue::Env(env), // TODO: calc support + // Matches Zig (media_query.zig `addF32`): env() values pass through + // unchanged — the calc-lattice add (wrapping the env() reference in a + // calc() to apply the strict-inequality boundary adjustment) is not + // implemented in either port, so range bounds on env() values lose the + // +/-0.001 nudge. + MediaFeatureValue::Env(env) => MediaFeatureValue::Env(env), } } @@ -1212,7 +1213,7 @@ impl MediaFeatureValue { return Ok(MediaFeatureValue::Resolution(res)); } - // PORT NOTE: Zig `input.tryParse(EnvironmentVariable.parse, .{})` left + // Zig `input.tryParse(EnvironmentVariable.parse, .{})` left // `options`/`depth` undefined (tryParse builds `ArgsTuple` and only // fills index 0) — UB. Fixed here by threading the real `ParserOptions` // down from `QueryFeature::parse` and passing `depth = 0`. @@ -1249,7 +1250,7 @@ fn write_min_max( dest.delim(b':', false)?; - // PORT NOTE: Zig deepCloned `value` into `dest.arena` then mutated; here + // Zig deepCloned `value` into `dest.arena` then mutated; here // `MediaFeatureValue: Clone` so we clone-by-value. let adjusted: Option = match operator { MediaFeatureComparison::GreaterThan => Some(value.clone().add_f32(0.001)), @@ -1271,7 +1272,7 @@ fn write_min_max( // bodies. Un-gated this round so `rules::dc::{media_list,query_feature}` can // route through real impls instead of `#[derive(Clone)]` passthroughs. // -// PORT NOTE: written as **inherent** methods (not `#[derive(DeepClone)]`) to +// Written as **inherent** methods (not `#[derive(DeepClone)]`) to // match the Zig hand-written bodies exactly: Zig copies `name`/`qualifier`/ // `media_type`/`operator` fields by value (they are `Copy`/arena-slice types // under the generics.zig "const strings" rule) and only recurses into the @@ -1737,7 +1738,7 @@ impl QueryFeature { ) -> Result { // We need to find the feature name first so we know the type. let start = input.state(); - // PORT NOTE: Zig loops `MediaFeatureName.parse` then checks + // Zig loops `MediaFeatureName.parse` then checks // `isExhausted()` — but `expectIdent` does not advance on error, so // the literal Zig body would spin on a non-ident token. The intent // (matching lightningcss) is to *skip* tokens until the name is @@ -1774,7 +1775,7 @@ impl QueryFeature { let start_operator = operator.unwrap(); let end_operator = end_operator_.unwrap(); // Start and end operators must be matching. - // PORT NOTE: discriminants are bitflags (1/2/4/8/16) — see the + // Discriminants are bitflags (1/2/4/8/16) — see the // comment on `MediaFeatureComparison`. Zig bitwise-ORs them. const GT: u8 = MediaFeatureComparison::GreaterThan as u8; const GTE: u8 = MediaFeatureComparison::GreaterThanEqual as u8; diff --git a/src/css/printer.rs b/src/css/printer.rs index fdca930e0642..93302f34653f 100644 --- a/src/css/printer.rs +++ b/src/css/printer.rs @@ -15,8 +15,7 @@ use css_values::ident::DashedIdent; pub use css::Error; -// TODO(port): move to _sys / clarify which Write trait. Zig used *std.Io.Writer -// (byte-oriented: writeAll/writeByte/print/splatByteAll). Using a local dyn trait alias. +// Byte-oriented writer (writeAll/writeByte/print equivalents); Zig used *std.Io.Writer. use bun_io::Write; /// Options that control how CSS is serialized to a string. @@ -148,8 +147,9 @@ pub struct Printer<'a> { /// from JavaScript. Useful for polyfills, for example. pub pseudo_classes: Option>, pub indentation_buf: BumpVec<'a, u8>, - // TODO(port): lifetime — ctx is set to a stack-local during with_context() and restored - // after; `&'a StyleContext<'a>` will not borrow-check there. May need raw `*const StyleContext`. + // INVARIANT: `with_context()` points this at a stack-local `StyleContext` (via an + // unsafe variance cast — see the SAFETY note there) and always restores the parent + // before that frame returns; never stash `ctx` beyond the `with_context` call. pub ctx: Option<&'a css::StyleContext<'a>>, /// Number of parent-selector substitutions performed for `&` while /// serializing the current rule prelude with compiled nesting (targets @@ -183,7 +183,6 @@ pub struct Printer<'a> { pub local_names: Option<&'a css::LocalsResultsMap>, /// NOTE This should be the same mimalloc heap arena arena pub arena: &'a Bump, - // TODO: finish the fields } #[cfg(debug_assertions)] @@ -300,7 +299,7 @@ impl<'a> Printer<'a> { Err(PrintErr::CSSPrintError) } - // PORT NOTE: deinit() dropped — scratchbuf/indentation_buf/dependencies are arena-backed + // deinit() dropped — scratchbuf/indentation_buf/dependencies are arena-backed // BumpVec<'a, _>; freed in bulk by `arena.reset()`. No explicit Drop impl needed. /// If `import_records` is null, then the printer will error when it encounters code that relies on import records (urls()) @@ -391,7 +390,7 @@ impl<'a> Printer<'a> { let import_record = &info.import_records[import_record_idx as usize]; let [a, b] = bun_core::cheap_prefix_normalizer(self.public_path, import_record.path.text); - // PORT NOTE: reshaped for borrowck — copied (a, b) out before re-borrowing &mut self + // Reshaped for borrowck — copied (a, b) out before re-borrowing &mut self let a = a.to_vec(); let b = b.to_vec(); // PERF(port): two small heap copies above; Zig borrowed directly. Profile if it shows up on a hot path. @@ -518,13 +517,22 @@ impl<'a> Printer<'a> { Ok(()) } - /// Alias of `write_str` for callers that want to be explicit about - /// possibly-newline-containing byte content (Zig: `writeBytes`). + /// Like `write_str`, but newline-containing byte content is permitted: + /// `line`/`col` are tracked across newlines (matching `write_char` applied + /// byte-by-byte), whereas `write_str` debug-asserts that no newlines are + /// present. Used for raw token round-trip content (whitespace tokens, + /// comments, unparsed contents). #[inline] pub fn write_bytes(&mut self, s: &[u8]) -> PrintResult<()> { - // TODO(port): Zig writeBytes did not assert no-newline; tracked - // line/col separately. For now route through write_str. - self.col += u32::try_from(s.len()).expect("int cast"); + // Unlike `write_str`, newlines are allowed here; track line/col across them + // (matching `write_char` applied byte-by-byte) so source maps stay correct. + if let Some(last_newline) = s.iter().rposition(|&b| b == b'\n') { + let new_lines = s.iter().filter(|&&b| b == b'\n').count(); + self.line += u32::try_from(new_lines).expect("int cast"); + self.col = u32::try_from(s.len() - last_newline - 1).expect("int cast"); + } else { + self.col += u32::try_from(s.len()).expect("int cast"); + } if self.dest.write_all(s).is_err() { return Err(self.add_fmt_error()); } @@ -569,7 +577,7 @@ impl<'a> Printer<'a> { let Some(symbol) = self.symbols.get_const(ref_) else { return Err(self.add_fmt_error()); }; - // PORT NOTE: copy out the arena slice before re-borrowing &mut self. + // Copy out the arena slice before re-borrowing &mut self. let name = symbol.original_name.slice(); return self.serialize_identifier(name); } @@ -588,7 +596,7 @@ impl<'a> Printer<'a> { pub fn write_ident(&mut self, ident: &'a [u8], handle_css_module: bool) -> PrintResult<()> { if handle_css_module { if self.css_module.is_some() { - // PORT NOTE: borrowck reshape — Zig captured `&mut self` inside the closure + // Borrowck reshape — Zig captured `&mut self` inside the closure // while `css_module` (a field of self) was simultaneously borrowed. We instead // copy the `'a`-lifetime references out of `css_module` up front so the // closure can hold the sole `&mut self`. @@ -662,7 +670,7 @@ impl<'a> Printer<'a> { None => false, }; if dashed_idents { - // PORT NOTE: same borrowck reshape as `write_ident`. + // Same borrowck reshape as `write_ident`. let source_index = self.loc.source_index as usize; let arena = self.arena; let (config, hash, source): (&'a css::css_modules::Config, &'a [u8], &'a [u8]) = { @@ -836,9 +844,8 @@ impl<'a> Printer<'a> { let ctx = css::StyleContext { selectors, parent }; - // TODO(port): lifetime — `&ctx` is stack-local but field type is `&'a StyleContext<'a>`. - // Zig relied on restoring `parent` before return. Consider changing the field to raw - // `*const StyleContext` or restructuring StyleContext as an explicit stack. + // `&ctx` is stack-local but the field type is `&'a StyleContext<'a>`; like Zig, + // soundness relies on restoring `parent` before this frame returns. // SAFETY: ctx outlives the call to func; self.ctx is restored to `parent` before return. // Inner-lifetime variance cast via raw pointer (`StyleContext<'x>` and // `StyleContext<'a>` share layout; only the borrow-checker tag differs). diff --git a/src/css/properties/align.rs b/src/css/properties/align.rs index f812330a582c..30ffee5e7e1b 100644 --- a/src/css/properties/align.rs +++ b/src/css/properties/align.rs @@ -624,9 +624,6 @@ pub struct Gap { } impl Gap { - // TODO(port): PropertyFieldMap was a comptime struct mapping fields → CSS property names - // (.row = "row-gap", .column = "column-gap"); could encode as derive attrs. - pub(crate) fn parse(input: &mut Parser) -> CssResult { let row = GapValue::parse(input)?; let column = input @@ -659,9 +656,6 @@ pub struct PlaceItems { } impl PlaceItems { - // TODO(port): PropertyFieldMap (.align = "align-items", .justify = "justify-items") - // TODO(port): VendorPrefixMap (.align = true) - pub(crate) fn parse(input: &mut Parser) -> CssResult { let align = AlignItems::parse(input)?; let justify = match input.try_parse(JustifyItems::parse) { @@ -724,9 +718,6 @@ pub struct PlaceSelf { } impl PlaceSelf { - // TODO(port): PropertyFieldMap (.align = "align-self", .justify = "justify-self") - // TODO(port): VendorPrefixMap (.align = true) - pub(crate) fn parse(input: &mut Parser) -> CssResult { let align = AlignSelf::parse(input)?; let justify = match input.try_parse(JustifySelf::parse) { @@ -817,9 +808,6 @@ pub struct PlaceContent { } impl PlaceContent { - // TODO(port): PropertyFieldMap (.align = PropertyIdTag::AlignContent, .justify = PropertyIdTag::JustifyContent) - // TODO(port): VendorPrefixMap (.align = true, .justify = true) - pub(crate) fn parse(input: &mut Parser) -> CssResult { let align = AlignContent::parse(input)?; let justify = match JustifyContent::parse(input) { @@ -978,10 +966,9 @@ pub struct AlignHandler { // ─── helper macros (Zig used `comptime prop: []const u8` + `@field` / `@unionInit`) ─── // -// TODO(port): the Zig source threads field names as comptime strings into helper fns -// and uses @field/@unionInit for reflection. Rust cannot pass field names as values, so -// these are macro_rules! that expand at each call site; a small proc-macro could dedupe -// if maintenance burden is high. +// The Zig source threads field names as comptime strings into helper fns and uses +// @field/@unionInit for reflection. Rust cannot pass field names as values, so these +// are macro_rules! that expand at each call site. macro_rules! handle_property_maybe_flush { ($this:expr, $dest:expr, $context:expr, $field:ident, $val:expr, $vp:expr) => {{ @@ -1057,9 +1044,9 @@ macro_rules! flush_legacy_property { prefixes_2009.insert(VendorPrefix::MOZ); } if !prefixes_2009.is_empty() { - // TODO(port): Zig branched on `T == BoxOrdinalGroup` to bypass - // from_standard. Never true at any callsite in this file; preserved - // as a note in case the macro is reused elsewhere. + // Zig branched on `T == BoxOrdinalGroup` to bypass from_standard. + // Never true at any callsite in this file; noted in case the + // macro is reused elsewhere. let s = <$ty2009>::from_standard(val); if let Some(a) = s { $dest.push(Property::$variant2009((a, prefixes_2009))); @@ -1140,9 +1127,6 @@ macro_rules! flush_shorthand_helper { align.clone(), justify_actual.clone(), ))); - // TODO(port): Zig built `prop.ty{ .align = ..., .justify = ... }` directly. - // Using a `from_align_justify` ctor here; could inline the struct init. - *$align_val = None; *$justify_val = None; } @@ -1174,8 +1158,6 @@ macro_rules! flush_shorthand_helper { align.clone(), justify.clone(), ))); - // TODO(port): see note above re: from_align_justify ctor. - *$align_val = None; *$justify_val = None; } @@ -1185,7 +1167,6 @@ macro_rules! flush_shorthand_helper { } // Tiny ctors used by flush_shorthand_helper! above. -// TODO(port): inline as struct literals once Property variant shapes are settled. impl PlaceContent { fn from_align_justify(align: AlignContent, justify: JustifyContent) -> Self { Self { align, justify } @@ -1332,8 +1313,8 @@ impl AlignHandler { Property::Unparsed(val) => { if is_align_property(&val.property_id) { self.flush(dest, context); - // PORT NOTE: Zig pushed `property.deepClone(context.arena)`. `Property` - // has no blanket `Clone` yet; reconstruct from the matched payload (same as flex.rs). + // Zig pushed `property.deepClone(context.arena)`. `Property` has no + // blanket `Clone` yet; reconstruct from the matched payload (same as flex.rs). let bump = dest.bump(); dest.push(Property::Unparsed(val.deep_clone(bump))); } else { diff --git a/src/css/properties/animation.rs b/src/css/properties/animation.rs index 6df914f02443..61a6e13fd749 100644 --- a/src/css/properties/animation.rs +++ b/src/css/properties/animation.rs @@ -38,13 +38,10 @@ pub struct Animation { } impl Animation { - // TODO(port): PropertyFieldMap / VendorPrefixMap were comptime anonymous-struct - // metadata consumed by reflection in the shorthand codegen. Replace these with - // a derive macro (e.g. #[derive(Shorthand)]) that emits the field→PropertyIdTag - // and field→has-vendor-prefix tables. - // PORT NOTE: PropertyFieldMap dropped — `PropertyIdTag::Animation*` variants - // are not yet generated (animation longhands are unparsed-only for now), and - // the table was unread comptime metadata. Re-add when the variants land. + // PropertyFieldMap omitted: `PropertyIdTag::Animation*` variants are not yet + // generated (animation longhands are unparsed-only for now), and the Zig table + // was unread comptime metadata. Re-add the field→PropertyIdTag table once the + // variants land. pub const VENDOR_PREFIX_MAP: &'static [(&'static str, bool)] = &[ ("name", true), @@ -140,7 +137,7 @@ impl Animation { } pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // PORT NOTE: reshaped `inline .ident, .string => |name|` — Zig's inline + // reshaped `inline .ident, .string => |name|` — Zig's inline // switch monomorphized over two payload types; Rust extracts the inner // string slice up front instead. let name_str: Option<&[u8]> = match &self.name { @@ -225,7 +222,7 @@ impl Animation { } /// A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property. -// PORT NOTE: no `#[derive(PartialEq, Eq, Hash)]` — `CustomIdent`/`CSSString` +// no `#[derive(PartialEq, Eq, Hash)]` — `CustomIdent`/`CSSString` // carry raw `*const [u8]` arena pointers; derived eq/hash would compare by // pointer. Hand-written `eql`/`hash` below compare by content. #[derive(Clone, Copy)] @@ -239,7 +236,7 @@ pub enum AnimationName { } impl AnimationName { - // PORT NOTE: hand-written (not `#[derive]`) because `CSSString` is a raw + // hand-written (not `#[derive]`) because `CSSString` is a raw // `*const [u8]` arena pointer — generics blanket impls cover `&[u8]` but // not raw slices. Mirrors Zig `css.implementEql/Hash/DeepClone`. pub fn eql(&self, other: &Self) -> bool { @@ -283,7 +280,7 @@ impl AnimationName { } pub fn parse(input: &mut Parser) -> css::Result { - // PORT NOTE: ported from src/css/properties/animation.zig — `none` keyword, + // ported from src/css/properties/animation.zig — `none` keyword, // then ``, else ``. if input .try_parse(|i| i.expect_ident_matching(b"none")) @@ -291,7 +288,7 @@ impl AnimationName { { return Ok(AnimationName::None); } - // PORT NOTE: `expect_string` returns a slice borrowing `&mut self`, which + // `expect_string` returns a slice borrowing `&mut self`, which // `try_parse`'s `R` type param can't carry. Erase the lifetime through a // raw pointer inside the closure; the slice lives in the input arena and // outlives this parse (CSSString = &'static [u8]). @@ -315,7 +312,7 @@ impl AnimationName { // SAFETY: arena-owned slice valid for 'bump. let name: &[u8] = unsafe { crate::arena_str(s.v) }; if css_module_animation_enabled { - // PORT NOTE: reshaped for borrowck — capture arena/source_index + // reshaped for borrowck — capture arena/source_index // before borrowing dest.css_module mutably. let arena = dest.arena; let source_index = dest.loc.source_index; @@ -329,7 +326,7 @@ impl AnimationName { // SAFETY: arena-owned slice valid for 'bump. let name: &[u8] = unsafe { crate::arena_str(*s) }; if css_module_animation_enabled { - // PORT NOTE: reshaped for borrowck + // reshaped for borrowck let arena = dest.arena; let source_index = dest.loc.source_index; if let Some(css_module) = &mut dest.css_module { @@ -351,8 +348,6 @@ impl AnimationName { } /// A value for the [animation-iteration-count](https://drafts.csswg.org/css-animations/#animation-iteration-count) property. -// TODO(port): css.DeriveParse / css.DeriveToCss were comptime mixins generating -// parse()/to_css() from variant shape. Implement as #[derive(Parse, ToCss)]. #[derive(PartialEq)] pub enum AnimationIterationCount { /// The animation will repeat the specified number of times. @@ -531,7 +526,7 @@ impl AnimationTimeline { } } -// PORT NOTE: hand-written `PartialEq` — `DashedIdent` carries a raw +// hand-written `PartialEq` — `DashedIdent` carries a raw // `*const [u8]` arena pointer; derive would compare by pointer, not content. impl PartialEq for AnimationTimeline { fn eq(&self, other: &Self) -> bool { diff --git a/src/css/properties/background.rs b/src/css/properties/background.rs index de6369e50a1b..a0a96c83ab74 100644 --- a/src/css/properties/background.rs +++ b/src/css/properties/background.rs @@ -14,7 +14,7 @@ use bun_alloc::Arena as Bump; use bun_alloc::ArenaVecExt as _; /// A value for the [background](https://www.w3.org/TR/css-backgrounds-3/#background) shorthand property. -// PORT NOTE: Clone derive gated on `Image` gaining `Clone` upstream. +// Clone derive gated on `Image` gaining `Clone` upstream. #[cfg_attr(any(), derive(Clone))] pub struct Background { /// The background image. @@ -244,7 +244,6 @@ impl Background { #[inline] pub(crate) fn deep_clone(&self, arena: &Bump) -> Self { - // PORT NOTE: `css.implementDeepClone` reflection — expanded field-wise. // `Image` is the only non-`Clone` field; it provides its own `deep_clone`. Self { image: self.image.deep_clone(arena), @@ -606,7 +605,7 @@ pub struct BackgroundHandler { pub has_any: bool, } -// PORT NOTE: the Zig uses comptime field-name strings + @field for `flushHelper` / +// The Zig uses comptime field-name strings + @field for `flushHelper` / // `initSmallListHelper` / `push`. Rust cannot index struct fields by string at runtime; // these helpers are expanded into small per-field macros below. A derive macro // could replace them. @@ -699,7 +698,7 @@ impl BackgroundHandler { Property::BackgroundClip(x) => { let val: &SmallList = &x.0; let vendor_prefix: VendorPrefix = x.1; - // PORT NOTE: reshaped for borrowck — Zig held &mut into self.clips + // Reshaped for borrowck: Zig held &mut into self.clips // across self.flush(). Compute the predicate first, then dispatch. let needs_flush = if let Some((clips, vp)) = &self.clips { vendor_prefix != *vp && !SmallList::eql(val, clips) @@ -737,7 +736,7 @@ impl BackgroundHandler { clips.append_assume_capacity(b.clip); } let mut clips_vp = VendorPrefix::NONE; - // PORT NOTE: reshaped for borrowck — drop borrow before calling flush(). + // Drop the `self.clips` borrow before calling flush(). let needs_flush = if let Some((existing_clips, existing_vp)) = &self.clips { clips_vp != *existing_vp && !SmallList::eql(&clips, existing_clips) } else { @@ -905,7 +904,7 @@ impl BackgroundHandler { }; let mut backgrounds: SmallList = SmallList::init_capacity(len); - // PORT NOTE: reshaped for borrowck — Zig zipped 8 slices by value; here we + // Reshaped for borrowck: Zig zipped 8 slices by value; here we // index by `i` and clone each element to avoid 8 simultaneous borrows. for i in 0..(len as usize) { backgrounds.append_assume_capacity(Background { @@ -939,8 +938,12 @@ impl BackgroundHandler { if self.flushed_properties.is_empty() { let mut fallbacks = crate::small_list::get_fallbacks(&mut backgrounds, arena, &context.targets); - // PORT NOTE: Vec has no owning iterator; pop in reverse then - // re-reverse via a temp Vec to preserve order. + // Pop in reverse then re-reverse via a temp Vec to + // preserve order while consuming the list. Shape kept from + // the Zig port, where the fallback list was an arena + // SmallList with no owning iterator; `fallbacks` is a plain + // Vec here, so a direct `for fb in fallbacks` would also + // work. let mut tmp: Vec> = Vec::with_capacity(fallbacks.len()); while let Some(fb) = fallbacks.pop() { @@ -1001,8 +1004,11 @@ impl BackgroundHandler { if !self.flushed_properties.contains(BackgroundProperty::IMAGE) { let mut fallbacks = crate::small_list::get_fallbacks(&mut images, arena, &context.targets); - // PORT NOTE: Vec has no owning iterator; pop in reverse then - // re-reverse via a temp Vec to preserve order. + // Pop in reverse then re-reverse via a temp Vec to preserve + // order while consuming the list. Shape kept from the Zig + // port, where the fallback list was an arena SmallList with no + // owning iterator; `fallbacks` is a plain Vec here, so a + // direct `for fb in fallbacks` would also work. let mut tmp: Vec> = Vec::with_capacity(fallbacks.len()); while let Some(fb) = fallbacks.pop() { tmp.push(fb); @@ -1156,7 +1162,7 @@ impl BackgroundHandler { let arena = dest.bump(); for decl in self.decls.drain(..) { - // PORT NOTE: Zig was `appendSlice` (bitwise copy of arena-backed + // Zig was `appendSlice` (bitwise copy of arena-backed // values). `Property` is not `Clone` here, so move out via drain. let _ = arena; dest.push(decl); diff --git a/src/css/properties/border.rs b/src/css/properties/border.rs index f6aa3be4fa14..23c8ef9011ef 100644 --- a/src/css/properties/border.rs +++ b/src/css/properties/border.rs @@ -245,7 +245,7 @@ impl BorderSideWidth { } pub fn deep_clone(&self, _arena: &Bump) -> Self { - // PORT NOTE: css.implementDeepClone — Length is value-type; Clone suffices. + // css.implementDeepClone — Length is value-type; Clone suffices. self.clone() } } @@ -254,8 +254,8 @@ crate::css_eql_partialeq!(BorderSideWidth); // ────────────────────────────────────────────────────────────────────────── // ImplFallbacks (Zig: `pub fn ImplFallbacks(comptime T: type) type`) // ────────────────────────────────────────────────────────────────────────── -// TODO(port): Zig used `inline for (std.meta.fields(T))` reflection. We expand -// the field list at macro invocation. All fields are `CssColor`. +// Zig used `inline for (std.meta.fields(T))` reflection; we expand the field +// list at macro invocation instead. All fields are `CssColor`. // Hoisted here because `macro_rules!` is order-sensitive. macro_rules! impl_fallbacks { ($T:ty; $($field:ident),+) => { @@ -357,9 +357,9 @@ macro_rules! define_size_shorthand { } impl $name { - // TODO(port): bring this back - // (old using name space) css::DefineShorthand(@This(), PropertyIdTag::$shorthand_id); - + // Zig: `css.DefineShorthand(@This(), PropertyIdTag.$shorthand_id)` + // consumed this table via comptime reflection; in Rust the table is + // kept as data for a future shorthand derive to consume. pub const PROPERTY_FIELD_MAP: &[(&str, PropertyIdTag)] = &[ ("start", PropertyIdTag::$start_id), ("end", PropertyIdTag::$end_id), @@ -442,7 +442,6 @@ impl BorderShorthand { where S: for<'a> css::DeepClone<'a> + Into, { - // TODO(port): Zig accepted `anytype`; all callers pass GenericBorder. self.width = Some(border.width.deep_clone(arena)); self.style = Some(border.style.deep_clone(arena).into()); self.color = Some(border.color.deep_clone(arena)); @@ -555,9 +554,10 @@ bitflags::bitflags! { // blocked_on: PropertyIdTag variant name verification (PascalCase mapping) impl BorderProperty { pub(crate) fn try_from_property_id(property_id: PropertyIdTag) -> Option { - // TODO(port): Zig used `inline for` over PropertyIdTag fields + @hasDecl. - // Expanded to an explicit match over every PropertyIdTag whose name - // starts with "border" and has a matching const above. + // Zig used `inline for` over PropertyIdTag fields + @hasDecl. Expanded + // here to an explicit match over every PropertyIdTag whose name starts + // with "border" and has a matching const above — keep in sync when new + // Border* PropertyIdTag variants are added. use PropertyIdTag as P; Some(match property_id { P::BorderTopColor => Self::BORDER_TOP_COLOR, @@ -638,7 +638,7 @@ mod border_handler_body { // FlushContext + flush_category! (Zig: nested struct with inline fns and // extensive comptime string-dispatch) // ────────────────────────────────────────────────────────────────────────── - // PORT NOTE: hoisted above `impl BorderHandler` — macro_rules! is order- + // hoisted above `impl BorderHandler` — macro_rules! is order- // sensitive and the flush_category!() callsites in `flush()` need these. // Route the large `Property` enum construction through a non-inlined @@ -658,12 +658,12 @@ mod border_handler_body { } struct FlushContext<'a, 'bump, 'ctx> { - // PORT NOTE: Zig stored `self: *BorderHandler`; we only need flushed_properties + // Zig stored `self: *BorderHandler`; we only need flushed_properties // here because the per-side BorderShorthand pointers are passed separately. flushed_properties: &'a mut BorderProperty, dest: &'a mut DeclarationList<'bump>, ctx: &'a mut PropertyHandlerContext<'ctx>, - // PORT NOTE: `arena` field dropped from PropertyHandlerContext; the + // `arena` field dropped from PropertyHandlerContext; the // arena is recovered once via `dest.bump()` and threaded here. arena: &'bump Bump, logical_supported: bool, @@ -671,10 +671,10 @@ mod border_handler_body { } // `f.logicalProp(ltr, ltr_key, rtl, rtl_key, val)` — ltr_key/rtl_key were unused. - // PORT NOTE: `$val` is evaluated *before* reborrowing `$f` so callers may pass + // `$val` is evaluated *before* reborrowing `$f` so callers may pass // expressions that read `f.arena` without tripping E0502. macro_rules! fc_logical_prop { - // PORT NOTE: the `GenericBorder` shorthand pairs carry distinct const-generic + // the `GenericBorder` shorthand pairs carry distinct const-generic // discriminants per side (BorderLeft = P=3, BorderRight = P=1), so a single // `__val` cannot `deep_clone()` into both `Property` variants. Recast via // `clone_as::()` instead. Callers always pass a `to_border()` result here, @@ -715,7 +715,7 @@ mod border_handler_body { } // `f.push(p, val)` - // PORT NOTE: Zig's `@field(BorderProperty, p)` keyed both Property and BorderProperty + // Zig's `@field(BorderProperty, p)` keyed both Property and BorderProperty // off one kebab string. Here `$p` is the PascalCase Property/PropertyIdTag variant; // the bitflags const is derived via try_from_property_id so a single ident suffices. macro_rules! fc_push { @@ -1235,7 +1235,7 @@ mod border_handler_body { dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) -> bool { - // PORT NOTE: `arena` field dropped from PropertyHandlerContext; the + // `arena` field dropped from PropertyHandlerContext; the // arena is recovered via `dest.bump()` (DeclarationList = bumpalo::Vec). let arena = dest.bump(); @@ -1522,7 +1522,7 @@ mod border_handler_body { let logical_shorthand_supported = !context.should_compile_logical(Feature::LogicalBorderShorthand); - // PORT NOTE: reshaped for borrowck — Zig stored `self: *BorderHandler` in + // reshaped for borrowck — Zig stored `self: *BorderHandler` in // FlushContext and accessed self.border_* through it. We instead take // independent &mut borrows of each shorthand, plus &mut self.flushed_properties. let arena = dest.bump(); @@ -1632,7 +1632,7 @@ mod border_handler_body { context.add_unparsed_fallbacks(arena, &mut upppppppppp); self.flushed_properties .insert(BorderProperty::try_from_property_id(PropertyIdTag::$id).unwrap()); - // TODO(port): Zig did NOT push to dest here (likely a bug upstream) — preserved. + // Zig did NOT push to dest here (likely a bug upstream) — preserved. }}; } diff --git a/src/css/properties/border_image.rs b/src/css/properties/border_image.rs index d79fef7960fe..310c7445e100 100644 --- a/src/css/properties/border_image.rs +++ b/src/css/properties/border_image.rs @@ -34,9 +34,9 @@ pub struct BorderImage { } impl BorderImage { - // TODO(port): PropertyFieldMap / VendorPrefixMap were comptime anonymous-struct - // tables consumed via @field reflection by the shorthand codegen. Replace with - // a trait impl (e.g. `impl ShorthandProperty for BorderImage`). + // Zig PropertyFieldMap / VendorPrefixMap were comptime anonymous-struct + // tables consumed via @field reflection by the shorthand codegen; recorded + // here for a future shorthand trait/derive to consume. // PropertyFieldMap: // source -> PropertyIdTag::BorderImageSource // slice -> PropertyIdTag::BorderImageSlice @@ -46,11 +46,11 @@ impl BorderImage { // VendorPrefixMap: all fields = true pub(crate) fn parse(input: &mut css::Parser) -> Result { - // PORT NOTE: Zig passed `{}` ctx + a no-op callback struct; collapsed to a closure. + // Zig passed `{}` ctx + a no-op callback struct; collapsed to a closure. Self::parse_with_callback(input, |_: &mut css::Parser| false) } - // PORT NOTE: Zig signature was (input, ctx: anytype, comptime callback: anytype) + // Zig signature was (input, ctx: anytype, comptime callback: anytype) // where callback(ctx, input) -> bool. Collapsed ctx into the closure capture. pub(crate) fn parse_with_callback( input: &mut css::Parser, @@ -200,21 +200,19 @@ impl BorderImage { targets: &css::targets::Targets, ) -> SmallList { let fallbacks = self.source.get_fallbacks(arena, targets); - // PORT NOTE: `defer fallbacks.deinit(arena)` dropped — SmallList drops at scope exit. + // `defer fallbacks.deinit(arena)` dropped — SmallList drops at scope exit. let mut res = SmallList::::init_capacity(fallbacks.len()); - for fallback in fallbacks.slice() { - // TODO(port): Zig moved `fallback` by value; SmallList lacks - // by-value drain in Rust port — deep_clone the source image - // until SmallList grows IntoIterator. + for fallback in fallbacks { + // Matches Zig: `fallback` is moved into the cloned shorthand. let mut clone = self.deep_clone(arena); - clone.source = fallback.deep_clone(arena); + clone.source = fallback; res.append(clone); } res } pub(crate) fn deep_clone(&self, arena: &Arena) -> Self { - // PORT NOTE: Zig css.implementDeepClone iterated @typeInfo fields. Expanded + // Zig css.implementDeepClone iterated @typeInfo fields. Expanded // explicitly here — keep in sync with the BorderImage field list. BorderImage { source: self.source.deep_clone(arena), @@ -306,7 +304,7 @@ pub enum BorderImageSideWidth { } impl BorderImageSideWidth { - // PORT NOTE: `css.DeriveParse(@This()).parse` / `css.DeriveToCss(@This()).toCss` + // `css.DeriveParse(@This()).parse` / `css.DeriveToCss(@This()).toCss` // were comptime-reflected derives. Hand-expanded — declaration order matches // Zig (Number → LengthPercentage → keyword `auto`). pub(crate) fn parse(input: &mut css::Parser) -> Result { @@ -456,10 +454,10 @@ impl BorderImageProperty { pub(crate) const BORDER_IMAGE: BorderImageProperty = BorderImageProperty::all(); - // PORT NOTE: bitflags provides `is_empty()` already; Zig `isEmpty` maps to it. + // bitflags provides `is_empty()` already; Zig `isEmpty` maps to it. pub(crate) fn try_from_property_id(property_id: PropertyIdTag) -> Option { - // PORT NOTE: Zig used `inline for` over struct fields + @field to match + // Zig used `inline for` over struct fields + @field to match // "border-image-" ++ field.name. Unrolled explicitly here. match property_id { PropertyIdTag::BorderImageSource => Some(BorderImageProperty::SOURCE), @@ -494,7 +492,7 @@ impl BorderImageHandler { ) -> bool { let arena = dest.bump(); - // PORT NOTE: Zig defined `flushHelper`/`propertyHelper` as local struct fns + // Zig defined `flushHelper`/`propertyHelper` as local struct fns // using @field for comptime field access. Ported as macro_rules! to keep the // per-field name dispatch without reflection. macro_rules! flush_helper { @@ -645,7 +643,7 @@ impl BorderImageHandler { .prefixes(self.vendor_prefix, css::prefixes::Feature::BorderImage); if self.flushed_properties.is_empty() { let fallbacks = border_image.get_fallbacks(arena, &context.targets); - for fallback in fallbacks.slice() { + for fallback in fallbacks { // Match prefix of fallback. e.g. -webkit-linear-gradient // can only be used in -webkit-border-image, not -moz-border-image. // However, if border-image is unprefixed, gradients can still be. @@ -653,9 +651,7 @@ impl BorderImageHandler { if p.is_empty() { p = prefix; } - // TODO(port): Zig moved `fallback` by value; SmallList has no - // by-value drain yet — deep_clone until IntoIterator lands. - dest.push(Property::BorderImage((fallback.deep_clone(arena), p))); + dest.push(Property::BorderImage((fallback, p))); } } } @@ -669,22 +665,19 @@ impl BorderImageHandler { self.flushed_properties .insert(BorderImageProperty::BORDER_IMAGE); } - (mut source, slice, width, outset, repeat) => { - if let Some(mut_source) = &mut source { + (source, slice, width, outset, repeat) => { + if let Some(mut mut_source) = source { if !self .flushed_properties .contains(BorderImageProperty::BORDER_IMAGE_SOURCE) { let img_fallbacks = mut_source.get_fallbacks(arena, &context.targets); - for fallback in img_fallbacks.slice() { - // TODO(port): same by-value move note as above. - dest.push(Property::BorderImageSource(fallback.deep_clone(arena))); + for fallback in img_fallbacks { + dest.push(Property::BorderImageSource(fallback)); } } - dest.push(Property::BorderImageSource(mut_source.deep_clone(arena))); - // TODO(port): Zig pushed `mut_source.*` by value (move). Cloning here to - // avoid partial-move out of `source: Option`. + dest.push(Property::BorderImageSource(mut_source)); self.flushed_properties .insert(BorderImageProperty::BORDER_IMAGE_SOURCE); } diff --git a/src/css/properties/border_radius.rs b/src/css/properties/border_radius.rs index bd664d747b8c..35f9e07b3b18 100644 --- a/src/css/properties/border_radius.rs +++ b/src/css/properties/border_radius.rs @@ -12,7 +12,7 @@ use crate::css_values::size::Size2D; use bun_alloc::ArenaVecExt as _; /// A value for the [border-radius](https://www.w3.org/TR/css-backgrounds-3/#border-radius) property. -// PORT NOTE: `Size2D` carries no `Clone`/`PartialEq` derives (it exposes +// `Size2D` carries no `Clone`/`PartialEq` derives (it exposes // inherent `deep_clone`/`eql` instead, matching the Zig protocol surface), so // `BorderRadius` can't `#[derive]` them either. The handler below uses // `Size2D::deep_clone`/`Size2D::eql` directly. @@ -44,11 +44,9 @@ impl BorderRadius { && Size2D::eql(&self.bottom_left, &other.bottom_left) } - // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"border-radius", PropertyFieldMap); - - // TODO(port): PropertyFieldMap / VendorPrefixMap were Zig anonymous-struct decl literals - // consumed by comptime DefineShorthand reflection. Represent as assoc consts for now; - // wire these into the shorthand trait/derive once it exists. + // Zig: `css.DefineShorthand(@This(), css.PropertyIdTag.@"border-radius", PropertyFieldMap)` + // consumed these anonymous-struct decl literals via comptime reflection. Kept + // as assoc consts for a future shorthand trait/derive to consume. pub const PROPERTY_FIELD_MAP: [(&'static str, &'static str); 4] = [ ("top_left", "border-top-left-radius"), ("top_right", "border-top-right-radius"), @@ -69,7 +67,7 @@ impl BorderRadius { // errdefer-style cleanup of `widths` is implicit via Drop on the `?` path. Rect::::parse_with(input, LengthPercentage::parse)? } else { - // PORT NOTE: Zig `widths.deepClone(arena)` — `LengthPercentage` is + // Zig `widths.deepClone(arena)` — `LengthPercentage` is // `Clone`-via-derive (no arena indirection), so per-field `.clone()` is exact. Rect { top: widths.top.clone(), @@ -100,7 +98,7 @@ impl BorderRadius { } pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // PORT NOTE: Zig built `Rect(*const LengthPercentage)` and reused + // Zig built `Rect(*const LengthPercentage)` and reused // `Rect.toCss`. `Rect::<&T>::to_css` would need `&T: ToCss + PartialEq`; // inline the 4-side serialization to avoid that bound (logic is identical // to `values::rect::Rect::to_css`). @@ -178,7 +176,7 @@ pub struct BorderRadiusHandler { // token-level field/variant access. Rust has no field-by-name reflection, so these are // macro_rules! that paste the field ident and the corresponding Property variant ident. -// PORT NOTE: `Size2D::is_compatible` is bounded on `T: values::protocol::IsCompatible`, +// `Size2D::is_compatible` is bounded on `T: values::protocol::IsCompatible`, // which `LengthPercentage` (= `DimensionPercentage`) does not yet impl. // Hand-roll the per-component check via `LengthPercentage::is_compatible` (inherent // method) until that protocol impl lands. @@ -235,7 +233,7 @@ macro_rules! logical_property_helper { $self.flush($d, $ctx); } - // PORT NOTE: Zig stored `property.deepClone(arena)`. `Property` itself + // Zig stored `property.deepClone(arena)`. `Property` itself // has no blanket `Clone`; callers pass an already-deep_clone'd `Property`. $self.$prop = Some($val); $self.category = PropertyCategory::Logical; @@ -300,7 +298,7 @@ impl BorderRadiusHandler { context: &mut PropertyHandlerContext, ) -> bool { let bump = dest.bump(); - // PORT NOTE: `Property::deep_clone` is still gated; reconstruct the + // `Property::deep_clone` is still gated; reconstruct the // matched variant directly (Size2D deep-clones via inherent method). match property { Property::BorderTopLeftRadius((val, vp)) => { diff --git a/src/css/properties/box_shadow.rs b/src/css/properties/box_shadow.rs index 84ba72d9f34d..e9203dadf924 100644 --- a/src/css/properties/box_shadow.rs +++ b/src/css/properties/box_shadow.rs @@ -28,8 +28,8 @@ pub struct BoxShadow { pub inset: bool, } -// PORT NOTE: `SmallList::{deep_clone,eql,is_compatible}` are bounded on the -// `generics::{DeepClone,CssEql,IsCompatible}` traits. Wire BoxShadow into all +// `SmallList::{deep_clone,eql,is_compatible}` are bounded on the +// `generics::{DeepClone,CssEql,IsCompatible}` traits. BoxShadow implements all // three so the handler can use `SmallList` directly without // hand-rolling per-field loops. impl<'bump> css::generic::DeepClone<'bump> for BoxShadow { @@ -127,7 +127,6 @@ impl BoxShadow { dest.write_char(b' ')?; self.y_offset.to_css(dest)?; - // PORT NOTE: Zig `Length.eql` → Rust `PartialEq` (see values/length.rs). if self.blur != Length::zero() || self.spread != Length::zero() { dest.write_char(b' ')?; self.blur.to_css(dest)?; @@ -146,8 +145,7 @@ impl BoxShadow { } pub(crate) fn deep_clone(&self, arena: &Arena) -> Self { - // PORT NOTE: Zig css.implementDeepClone iterated @typeInfo fields. Expanded - // explicitly here — keep in sync with the BoxShadow field list. `Length` + // Expanded field-wise — keep in sync with the BoxShadow field list. `Length` // has no `DeepClone` trait impl yet but is `Clone` (Box deep-clones). BoxShadow { color: self.color.deep_clone(arena), @@ -160,8 +158,7 @@ impl BoxShadow { } pub(crate) fn eql(&self, rhs: &Self) -> bool { - // PORT NOTE: Zig css.implementEql iterated @typeInfo fields. Expanded - // explicitly. `Length` Zig `eql` → Rust `PartialEq` (values/length.rs). + // Expanded field-wise — keep in sync with the BoxShadow field list. self.color.eql(&rhs.color) && self.x_offset == rhs.x_offset && self.y_offset == rhs.y_offset @@ -204,7 +201,7 @@ impl BoxShadowHandler { self.flush(dest, context); } - // PORT NOTE: reshaped for borrowck — Zig held simultaneous &mut into + // Reshaped for borrowck: Zig held simultaneous &mut into // self.box_shadows across self.flush(). Compute the predicate first, // then either flush+replace or update in place. let needs_flush = if let Some(bxs) = &self.box_shadows { @@ -273,7 +270,7 @@ impl BoxShadowHandler { fallbacks.insert(shadow.color.get_necessary_fallbacks(&context.targets)); } - // PORT NOTE: Zig used `initCapacity(len)` + `setLen(len)` + per-index field + // Zig used `initCapacity(len)` + `setLen(len)` + per-index field // writes via `inline for std.meta.fields(BoxShadow)` skipping `color`. That // pattern would observe partially-uninit `BoxShadow` values in Rust, so we // build each fully-formed `BoxShadow` and `append`. Behavior is identical. diff --git a/src/css/properties/css_modules.rs b/src/css/properties/css_modules.rs index c3f02e4a881e..a157c84c2258 100644 --- a/src/css/properties/css_modules.rs +++ b/src/css/properties/css_modules.rs @@ -79,7 +79,6 @@ impl Composes { } pub fn deep_clone(&self, bump: &Arena) -> Self { - // PORT NOTE: Zig `css.implementDeepClone` is comptime field reflection. // `CustomIdent` is `Copy` (arena-ptr payload), so an element-wise copy // into a fresh `SmallList` is the deep clone. let mut names = CustomIdentList::default(); @@ -95,7 +94,6 @@ impl Composes { } pub fn eql(lhs: &Self, rhs: &Self) -> bool { - // PORT NOTE: Zig `css.implementEql` is comptime field reflection. if lhs.names.len() != rhs.names.len() { return false; } @@ -139,7 +137,6 @@ impl crate::generics::CssEql for Specifier { impl Specifier { pub fn eql(lhs: Self, rhs: Self) -> bool { - // PORT NOTE: Zig `css.implementEql` (variant-wise reflection) → hand-match. match (lhs, rhs) { (Specifier::Global, Specifier::Global) => true, (Specifier::ImportRecordIndex(a), Specifier::ImportRecordIndex(b)) => a == b, @@ -179,12 +176,11 @@ impl Specifier { } pub fn deep_clone(self, _bump: &Arena) -> Self { - // PORT NOTE: Zig `css.implementDeepClone` — variants are `Copy`. + // Variants are `Copy`; the deep clone is the value itself. self } pub fn hash(self, hasher: &mut Wyhash) { - // PORT NOTE: Zig `css.implementHash` (variant-wise reflection) → hand-match. match self { Specifier::Global => hasher.update(&0u32.to_ne_bytes()), Specifier::ImportRecordIndex(i) => { diff --git a/src/css/properties/custom.rs b/src/css/properties/custom.rs index c75e77d7b6d3..9f2e5e88652c 100644 --- a/src/css/properties/custom.rs +++ b/src/css/properties/custom.rs @@ -79,8 +79,8 @@ mod ext { /// Vec`, which this round adds in css_parser.rs). pub(super) fn url_to_css(this: &Url, dest: &mut Printer) -> PrintResult<()> { let dep: Option = if dest.dependencies.is_some() { - // PORT NOTE: reshaped for borrowck — `get_import_records` borrows - // &mut *dest, so capture arena/filename first. + // `get_import_records` borrows &mut *dest, so capture + // arena/filename first. let arena = dest.arena; // SAFETY: filename borrows the printer arena/options which outlive `dest`. let filename: &[u8] = unsafe { &*std::ptr::from_ref::<[u8]>(dest.filename()) }; @@ -102,7 +102,6 @@ mod ext { dest.write_char(b')')?; if let Some(dependencies) = &mut dest.dependencies { - // PORT NOTE: bun.handleOom dropped — Vec::push aborts on OOM via global arena dependencies.push(crate::Dependency::Url(d)); } @@ -111,8 +110,8 @@ mod ext { let import_record = dest.import_record(this.import_record_idx)?; let is_internal = import_record.tag.is_internal(); - // PORT NOTE: reshaped for borrowck — `get_import_record_url` reborrows - // &mut *dest, so capture `is_internal` first. + // `get_import_record_url` reborrows &mut *dest, so capture + // `is_internal` first. let url: &'static [u8] = { let u = dest.get_import_record_url(this.import_record_idx)?; // SAFETY: import-record paths are arena/source-owned and outlive `dest`. @@ -482,7 +481,7 @@ impl TokenList { if tokens[tokens.len() - 1].is_whitespace() { end -= 1; } - // PORT NOTE: Zig does `insertSlice(0, slice)` (shallow memcpy) then `tokens.deinit()` + // Zig does `insertSlice(0, slice)` (shallow memcpy) then `tokens.deinit()` // (frees only the backing array). `drain` moves the elements out without deep-cloning. let newlist: Vec = tokens.drain(start..end).collect(); return Ok(TokenList { v: newlist }); @@ -567,7 +566,7 @@ impl TokenList { let Ok(tok) = input.next_including_whitespace() else { break; }; - // PORT NOTE: reshaped for borrowck — clone the token so we can call &mut methods on `input` below. + // Clone the token so we can call &mut methods on `input` below. let tok = tok.clone(); match &tok { Token::Whitespace(_) | Token::Comment(_) => { @@ -1191,7 +1190,7 @@ impl EnvironmentVariableName { } /// A UA-defined environment variable name. -// PORT NOTE: Zig `css.DefineEnumProperty(@This())` provides eql/hash/parse/ +// Zig `css.DefineEnumProperty(@This())` provides eql/hash/parse/ // to_css/deep_clone via comptime reflection over @tagName. Replaced by an // `EnumProperty` impl below (kebab-case match) — same protocol surface. #[derive(Clone, Copy, PartialEq, Eq, strum::IntoStaticStr, CssHash)] @@ -1336,7 +1335,7 @@ impl TokenOrValue { // `TokenList` payload, and `media_query::MediaFeatureValue` derives // `Debug + Clone` over an `EnvironmentVariable` payload. The leaf value types // (`Url`, `CustomIdent`, …) don't all `#[derive(Clone)]` yet, so hand-roll -// the structural clone here. PORT NOTE: Zig had no `Clone` distinction — +// the structural clone here. Zig had no `Clone` distinction — // shallow struct copy was implicit; arena-slice payloads (`*const [u8]`) are // `Copy`, and the only owning fields are `Vec` / `Vec`. @@ -1538,7 +1537,7 @@ pub enum CustomPropertyName { Unknown(Ident), } -// PORT NOTE: `DashedIdent`/`Ident` carry `*const [u8]` arena slices and +// `DashedIdent`/`Ident` carry `*const [u8]` arena slices and // intentionally don't derive `PartialEq` (pointer-eq would be wrong). // `PropertyId` derives `PartialEq`, so compare the underlying bytes here. impl PartialEq for CustomPropertyName { diff --git a/src/css/properties/display.rs b/src/css/properties/display.rs index 676843912d5d..a187c15e76a0 100644 --- a/src/css/properties/display.rs +++ b/src/css/properties/display.rs @@ -11,7 +11,7 @@ pub enum Display { Pair(DisplayPair), } -// PORT NOTE: Zig `DeriveParse`/`DeriveToCss` for a 2-payload union(enum) tries each +// Zig `DeriveParse`/`DeriveToCss` for a 2-payload union(enum) tries each // payload's `parse` in declaration order; `toCss` dispatches to the active payload. impl Display { pub(crate) fn parse(input: &mut Parser) -> css::Result { @@ -130,7 +130,7 @@ impl DisplayPair { let location = input.current_source_location(); let ident = input.expect_ident_cloned()?; - // PORT NOTE: Zig used `bun.ComptimeStringMap(..).getASCIIICaseInsensitive`. + // Zig used `bun.ComptimeStringMap(..).getASCIIICaseInsensitive`. // 8 keys → if-chain over `eql_case_insensitive_ascii::` (phf values // would have to be const-eval, and `VendorPrefix` bitflags are not). use bun_core::eql_case_insensitive_ascii as eq; @@ -161,7 +161,6 @@ impl DisplayPair { } pub(crate) fn to_css(self, dest: &mut Printer) -> Result<(), PrintErr> { - // PORT NOTE: reshaped Zig if-else chain into match for tagged-union payload extraction. match (self.outside, &self.inside, self.is_list_item) { (DisplayOutside::Inline, DisplayInside::FlowRoot, false) => { return dest.write_str("inline-block"); @@ -243,7 +242,7 @@ impl DisplayInside { let location = input.current_source_location(); let ident = input.expect_ident_cloned()?; - // PORT NOTE: Zig used `bun.ComptimeStringMap(..).getASCIIICaseInsensitive`. + // Zig used `bun.ComptimeStringMap(..).getASCIIICaseInsensitive`. // 10 keys → if-chain over `eql_case_insensitive_ascii::`. use bun_core::eql_case_insensitive_ascii as eq; Ok(if eq(ident, b"flow", true) { diff --git a/src/css/properties/flex.rs b/src/css/properties/flex.rs index 2470c4ab847b..cff9a292f5a3 100644 --- a/src/css/properties/flex.rs +++ b/src/css/properties/flex.rs @@ -59,9 +59,8 @@ pub struct FlexFlow { pub wrap: FlexWrap, } -// (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"flex-flow", PropertyFieldMap); -// TODO(port): PropertyFieldMap / VendorPrefixMap are comptime shorthand metadata consumed by -// css::DefineShorthand reflection. Port as part of the shorthand derive. +// Zig: `css.DefineShorthand(@This(), css.PropertyIdTag.@"flex-flow", PropertyFieldMap)` +// consumed comptime shorthand metadata; recorded here for a future shorthand derive: // PropertyFieldMap = { direction: PropertyIdTag::FlexDirection, wrap: PropertyIdTag::FlexWrap } // VendorPrefixMap = { direction: true, wrap: true } @@ -122,8 +121,7 @@ pub struct Flex { pub basis: LengthPercentageOrAuto, } -// (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.flex, PropertyFieldMap); -// TODO(port): PropertyFieldMap / VendorPrefixMap shorthand metadata — see FlexFlow note. +// Zig: `css.DefineShorthand(@This(), css.PropertyIdTag.flex, PropertyFieldMap)` — see FlexFlow note. // PropertyFieldMap = { grow: PropertyIdTag::FlexGrow, shrink: PropertyIdTag::FlexShrink, basis: PropertyIdTag::FlexBasis } // VendorPrefixMap = { grow: true, shrink: true, basis: true } @@ -539,8 +537,8 @@ impl FlexHandler { dest: &mut css::DeclarationList, context: &mut css::PropertyHandlerContext, ) -> bool { - // TODO(port): Zig used local closures with `@field(self, prop)` comptime reflection. - // Ported as macro_rules! token-pasting on field idents. + // Zig used local closures with `@field(self, prop)` comptime reflection; + // ported as macro_rules! token-pasting on field idents. macro_rules! maybe_flush { ($prop:ident, $val:expr, $vp:expr) => {{ // If two vendor prefixes for the same property have different @@ -558,7 +556,7 @@ impl FlexHandler { maybe_flush!($prop, $val, $vp); // Otherwise, update the value and add the prefix - // PORT NOTE: Zig threaded `context.arena` into `css.generic.deepClone`; + // Zig threaded `context.arena` into `css.generic.deepClone`; // every payload here is `Clone` (Copy enums / f32 / i32 / LengthPercentageOrAuto), // so `.clone()` is the faithful equivalent. if let Some(field) = &mut self.$prop { @@ -645,7 +643,7 @@ impl FlexHandler { Property::Unparsed(val) => { if Self::is_flex_property(&val.property_id) { self.flush(dest, context); - // PORT NOTE: Zig pushed `property.deepClone(context.arena)`. `Property` + // Zig pushed `property.deepClone(context.arena)`. `Property` // has no blanket `deep_clone` yet; reconstruct from the matched payload. let bump = dest.bump(); dest.push(Property::Unparsed(val.deep_clone(bump))); @@ -694,8 +692,8 @@ impl FlexHandler { let mut order = self.order.take(); let mut flex_order = self.flex_order.take(); - // TODO(port): Zig `legacyProperty` / `singleProperty` use `@unionInit(Property, name, ...)` - // (comptime token-pasting). Ported as macro_rules! taking the Property variant ident. + // Zig `legacyProperty` / `singleProperty` use `@unionInit(Property, name, ...)` + // (comptime token-pasting); ported as macro_rules! taking the Property variant ident. macro_rules! legacy_property { ($variant:ident, $key:expr) => {{ if let Some(value) = $key { @@ -744,7 +742,7 @@ impl FlexHandler { } if let (Some(dir_val), Some(wrap_val)) = (&mut direction, &mut wrap) { - // PORT NOTE: reshaped for borrowck — Zig took simultaneous &mut into both Options. + // reshaped for borrowck — Zig took simultaneous &mut into both Options. let dir: &FlexDirection = &dir_val.0; let dir_prefix: &mut VendorPrefix = &mut dir_val.1; let wrapinner: &FlexWrap = &wrap_val.0; @@ -850,10 +848,11 @@ impl FlexHandler { } }}; } - // TODO(port): single_property! macro encodes Zig's comptime `prop_2009`/`prop_2012` branches. - // The Zig version gates the entire 2009 block on `comptime prop_2009 != null`; here the macro - // arms with `prop_2009 = None` pass a no-op closure, so the `prefix.contains(NONE)` check - // still runs but has no effect. Verify this matches behavior exactly. + // single_property! encodes Zig's comptime `prop_2009`/`prop_2012` branches. The Zig + // version gates the entire 2009 block at comptime on `prop_2009 != null`; here the + // `prop_2009 = None` arms pass a no-op closure instead. That block has no side effects + // other than invoking the body (it only reads `prefix`/`targets` into locals), so + // running it with a no-op body is behaviorally identical to skipping it. single_property!( FlexDirection, @@ -890,7 +889,7 @@ impl FlexHandler { } if let (Some(g_val), Some(s_val), Some(b_val)) = (&mut grow, &mut shrink, &mut basis) { - // PORT NOTE: reshaped for borrowck + // reshaped for borrowck let g = g_val.0; let g_prefix: &mut VendorPrefix = &mut g_val.1; let s = s_val.0; diff --git a/src/css/properties/font.rs b/src/css/properties/font.rs index e3202c4d6b09..cfb8ed4716e7 100644 --- a/src/css/properties/font.rs +++ b/src/css/properties/font.rs @@ -39,7 +39,6 @@ use css::CssResult; /// A value for the [font-weight](https://www.w3.org/TR/css-fonts-4/#font-weight-prop) property. #[derive(Clone, PartialEq)] -// TODO(port): css.DeriveParse / css.DeriveToCss were comptime-reflection derives; provide proc-macro #[derive(Parse, ToCss)] pub enum FontWeight { /// An absolute font weight. Absolute(AbsoluteFontWeight), @@ -50,7 +49,7 @@ pub enum FontWeight { } impl FontWeight { - // PORT NOTE: Zig `css.DeriveParse(@This()).parse` for a union(enum) with one + // Zig `css.DeriveParse(@This()).parse` for a union(enum) with one // payload variant + 2 keyword variants tries the payload first, then matches // the remaining keywords against `expect_ident`. pub(crate) fn parse(input: &mut css::Parser) -> CssResult { @@ -86,8 +85,7 @@ impl FontWeight { } } - // eql → derived PartialEq - // deepClone → derived Clone; TODO(port): arena-aware deep_clone if needed + // eql → derived PartialEq; deepClone → derived Clone (no arena-owned data) } /// An [absolute font weight](https://www.w3.org/TR/css-fonts-4/#font-weight-absolute-values), @@ -105,7 +103,7 @@ pub enum AbsoluteFontWeight { } impl AbsoluteFontWeight { - // PORT NOTE: Zig `css.DeriveParse(@This()).parse` — payload (`CSSNumber`) first, + // Zig `css.DeriveParse(@This()).parse` — payload (`CSSNumber`) first, // then keyword variants. pub(crate) fn parse(input: &mut css::Parser) -> CssResult { if let Ok(n) = input.try_parse(CSSNumberFns::parse) { @@ -237,7 +235,7 @@ pub enum FontStretch { } impl FontStretch { - // PORT NOTE: Zig `css.DeriveParse(@This()).parse` — two payload variants + // Zig `css.DeriveParse(@This()).parse` — two payload variants // tried in declaration order. pub(crate) fn parse(input: &mut css::Parser) -> CssResult { if let Ok(kw) = input.try_parse(FontStretchKeyword::parse) { @@ -334,9 +332,11 @@ pub enum FontFamily { /// A generic family name. Generic(GenericFontFamily), /// A custom family name. - // TODO(port): arena-backed slice — should be &'bump [u8] once 'bump lifetime is threaded through - // PORT NOTE: with *const [u8] derived PartialEq/Eq/Hash would compare by pointer; Zig's custom - // HashContext hashes/compares by content (Wyhash over bytes) — provide manual impls below. + // Arena-backed slice: the pointer targets bytes owned by the parser arena and is + // only valid while that arena is alive (becomes `&'bump [u8]` once the 'bump + // lifetime is threaded through these types). With `*const [u8]`, derived + // PartialEq/Eq/Hash would compare by pointer; Zig's custom HashContext + // hashes/compares by content (Wyhash over bytes) — manual impls below. FamilyName(*const [u8]), } @@ -433,7 +433,7 @@ impl FontFamily { // `CssEql`/`DeepClone` via `bridge_clone_partialeq!` in `generics.rs`. } -// PORT NOTE: Zig's `css.implementEql` / `css.implementHash` walked fields by +// Zig's `css.implementEql` / `css.implementHash` walked fields by // reflection and compared/hashed `[]const u8` by *content*. With `*const [u8]` // in Rust, derived `PartialEq`/`Hash` would compare pointers, so hand-roll. impl PartialEq for FontFamily { @@ -452,7 +452,7 @@ impl Eq for FontFamily {} impl core::hash::Hash for FontFamily { fn hash(&self, state: &mut H) { - // PORT NOTE: Zig `css.implementHash` hashes the active tag then the + // Zig `css.implementHash` hashes the active tag then the // payload bytes. With `*const [u8]` a derived Hash would hash the // pointer address, breaking FontFamilyHashMap dedupe semantics. core::mem::discriminant(self).hash(state); @@ -468,7 +468,7 @@ impl core::hash::Hash for FontFamily { impl Clone for FontFamily { fn clone(&self) -> Self { - // PORT NOTE: shallow — arena slice pointers are `Copy`; matches Zig's + // shallow — arena slice pointers are `Copy`; matches Zig's // implicit struct copy. `deepClone` would re-alloc the slice in 'bump. match self { FontFamily::Generic(g) => FontFamily::Generic(*g), @@ -647,7 +647,7 @@ pub enum LineHeight { } impl LineHeight { - // PORT NOTE: Zig `css.DeriveParse(@This()).parse` — keyword variant first + // Zig `css.DeriveParse(@This()).parse` — keyword variant first // (`normal`), then payload variants in declaration order. pub(crate) fn parse(input: &mut css::Parser) -> CssResult { if input @@ -686,7 +686,7 @@ impl LineHeight { } /// A value for the [font](https://www.w3.org/TR/css-fonts-4/#font-prop) shorthand property. -// PORT NOTE: Zig's `eql`/`deepClone` were reflection-based (`css.implementEql` +// Zig's `eql`/`deepClone` were reflection-based (`css.implementEql` // / `css.implementDeepClone`); the field-wise `#[derive(DeepClone, CssEql)]` // is the Rust equivalent — every field type carries the trait via the // blankets/bridges in `generics.rs`. @@ -746,7 +746,7 @@ impl Font { } if variant_caps.is_some() { - // PORT NOTE: Zig has `if (variant_caps != null)` here — preserved verbatim (likely upstream bug; should be `== null`) + // Zig has `if (variant_caps != null)` here — preserved verbatim (likely upstream bug; should be `== null`) if let Ok(value) = input.try_parse(FontVariantCaps::parse_css2) { variant_caps = Some(value); count += 1; @@ -776,7 +776,7 @@ impl Font { None }; - // PORT NOTE: Zig `Vec(FontFamily).parse` parsed a comma-separated + // Zig `Vec(FontFamily).parse` parsed a comma-separated // list and packed it; route through `parse_comma_separated` + move. let family = input .parse_comma_separated(FontFamily::parse) @@ -878,7 +878,8 @@ impl FontProperty { pub(crate) fn try_from_property_id( property_id: crate::properties::PropertyIdTag, ) -> Option { - // TODO(port): Zig used `inline for` over std.meta.fields + @field; expanded by hand + // Zig used `inline for` over std.meta.fields + @field; expanded by hand — + // keep in sync when new Font* PropertyIdTag variants are added. use crate::properties::PropertyIdTag; match property_id { PropertyIdTag::FontFamily => Some(FontProperty::FONT_FAMILY), @@ -918,12 +919,12 @@ impl FontHandler { context: &mut crate::PropertyHandlerContext<'_>, ) -> bool { use crate::properties::Property; - // PORT NOTE: `arena` field dropped from PropertyHandlerContext; the + // `arena` field dropped from PropertyHandlerContext; the // arena is recovered via `dest.bump()` (DeclarationList = bumpalo::Vec). let arena = dest.bump(); - // TODO(port): Zig used `comptime prop: []const u8` + @field for property_helper / flush_helper / push. - // No Rust equivalent for field-name reflection — expanded as macro_rules! over (handler_field, Property variant, FontProperty flag). + // Zig used `comptime prop: []const u8` + @field for property_helper / flush_helper / + // push; expanded as macro_rules! over (handler_field, Property variant, FontProperty flag). macro_rules! flush_helper { ($this:expr, $field:ident, $val:expr) => {{ if $this.$field.is_some() @@ -1052,13 +1053,16 @@ impl FontHandler { let mut i: usize = 0; while i < f.len() { - // TODO(port): seen.getOrPut equivalent — using entry API - let key = f.at(i).clone(); - if seen.contains_key(&key) { - let _ = f.ordered_remove(i); - } else { - seen.insert(key, ()); - i += 1; + // Zig: `seen.getOrPut` — single-lookup entry API. + use bun_collections::array_hash_map::MapEntry; + match seen.entry(f.at(i).clone()) { + MapEntry::Occupied(_) => { + let _ = f.ordered_remove(i); + } + MapEntry::Vacant(v) => { + v.insert(()); + i += 1; + } } } } @@ -1128,8 +1132,8 @@ impl FontHandler { } } -// TODO(port): SYSTEM_UI was `const FontFamily = .{ .generic = .system_ui }`; cannot be a `const` here -// because FontFamily contains a raw pointer. Compare against the Generic variant directly instead. +// Zig SYSTEM_UI was `const FontFamily = .{ .generic = .system_ui }`; matched against +// the Generic variant directly here instead of a const (FontFamily holds a raw pointer). fn is_system_ui(f: &FontFamily) -> bool { matches!(f, FontFamily::Generic(GenericFontFamily::SystemUi)) } @@ -1159,7 +1163,7 @@ fn compatible_font_family( } if let Some(families) = family.as_mut() { - // PORT NOTE: Zig (font.zig:1029-1035) iterates `families.sliceConst()` + // Zig (font.zig:1029-1035) iterates `families.sliceConst()` // by value while inserting into `families` mid-loop, then `break`s. // In Rust the immutable slice borrow would alias the &mut needed for // `insert` (and `insert` may reallocate, invalidating the iterator). diff --git a/src/css/properties/grid.rs b/src/css/properties/grid.rs index 7ea72033c05f..38f2430d4902 100644 --- a/src/css/properties/grid.rs +++ b/src/css/properties/grid.rs @@ -9,7 +9,6 @@ use bun_core::strings; /// A [track sizing](https://drafts.csswg.org/css-grid-2/#track-sizing) value /// for the `grid-template-rows` and `grid-template-columns` properties. -// TODO(port): css.DeriveParse / css.DeriveToCss → #[derive(Parse, ToCss)] proc-macro pub enum TrackSizing { /// No explicit grid tracks. None, @@ -39,7 +38,6 @@ impl TrackList { if let Ok(track_size) = input.try_parse(TrackSize::parse) { // TODO: error handling - // TODO(port): Zig original omits arena arg here (`items.append(.{...})`); mirroring with input.arena() items.push(TrackListItem::TrackSize(track_size)); } else if let Ok(repeat) = input.try_parse(TrackRepeat::parse) { // TODO: error handling @@ -142,7 +140,6 @@ impl TrackSize { input.expect_function_matching(b"fit-content")?; - // TODO(port): css.voidWrap(LengthPercentage, LengthPercentage.parse) — wraps a parse fn for parseNestedBlock; using a closure directly let len = input.parse_nested_block(|i: &mut Parser| LengthPercentage::parse(i))?; Ok(TrackSize::FitContent(len)) @@ -289,13 +286,10 @@ impl TrackRepeat { input.expect_function_matching(b"repeat")?; input.parse_nested_block(|i: &mut Parser| -> css::Result { - // TODO(port): Zig uses `@call(.auto, @field(RepeatCount, "parse"), .{i})` — direct call here let count = RepeatCount::parse(i)?; i.expect_comma()?; - // TODO: this code will not compile if used - // TODO(port): Zig calls `bun.Vec(T).init(i.arena)` — using default + push(alloc, ..) here let mut line_names = Vec::::default(); let mut track_sizes = Vec::::default(); @@ -305,7 +299,8 @@ impl TrackRepeat { .unwrap_or_else(|_| CustomIdentList::default()); line_names.push(line_name); - // TODO(port): Zig original references outer `input` here (likely a bug); mirroring with `i` + // The Zig original referenced the outer `input` here (likely a + // bug); use the nested parser `i`. if let Ok(track_size) = i.try_parse(TrackSize::parse) { // TODO: error handling track_sizes.push(track_size); @@ -391,7 +386,8 @@ fn parse_line_names(input: &mut Parser) -> css::Result { input.parse_nested_block(|i: &mut Parser| -> css::Result { let mut values = CustomIdentList::default(); - // TODO(port): Zig original references outer `input` here (likely a bug); mirroring with `i` + // The Zig original referenced the outer `input` here (likely a bug); + // use the nested parser `i`. while let Ok(ident) = i.try_parse(CustomIdent::parse) { values.append(ident); } @@ -404,7 +400,6 @@ fn parse_line_names(input: &mut Parser) -> css::Result { /// used in the `repeat()` function. /// /// See [TrackRepeat](TrackRepeat). -// TODO(port): css.DeriveParse / css.DeriveToCss → #[derive(Parse, ToCss)] proc-macro #[derive(PartialEq, Eq)] pub enum RepeatCount { /// The number of times to repeat. @@ -416,7 +411,7 @@ pub enum RepeatCount { } impl RepeatCount { - // PORT NOTE: `css.DeriveParse(@This()).parse` — hand-expanded in declaration + // Hand-expanded from Zig's `css.DeriveParse(@This()).parse` in declaration // order (Number → keyword `auto-fill` → keyword `auto-fit`). pub fn parse(input: &mut Parser) -> css::Result { if let Ok(n) = input.try_parse(CSSIntegerFns::parse) { @@ -433,7 +428,7 @@ impl RepeatCount { Err(location.new_unexpected_token_error(css::Token::Ident(ident))) } - // PORT NOTE: `css.DeriveToCss(@This()).toCss` — hand-expanded. + // Hand-expanded from Zig's `css.DeriveToCss(@This()).toCss`. pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { match self { RepeatCount::Number(n) => CSSIntegerFns::to_css(*n, dest), @@ -472,7 +467,7 @@ impl GridTemplateAreas { let mut row: u32 = 0; let mut columns: u32 = 0; - // PORT NOTE: `expect_string` returns a slice borrowing `&mut self`, which + // `expect_string` returns a slice borrowing `&mut self`, which // `try_parse`'s `R` type param can't carry. Erase the lifetime through a // raw pointer inside the closure; the slice lives in the input arena and // outlives this parse. @@ -483,7 +478,6 @@ impl GridTemplateAreas { let parsed_columns = match Self::parse_string(input.arena(), s, &mut tokens) { Ok(v) => v, Err(()) => { - // TODO(port): Zig uses `.{input.newError(.qualified_rule_invalid)}` — anonymous struct shorthand; mapping to Err(..) return Err(input.new_error(css::BasicParseErrorKind::qualified_rule_invalid)); } }; @@ -529,8 +523,12 @@ impl GridTemplateAreas { column += 1; if strings::starts_with_char(rest, b'.') { - // TODO(port): Zig original falls through here without `continue` — likely a bug (the `.` token - // is supposed to push None and continue). Mirroring Zig control flow exactly. + // TODO(port): the Zig original falls through here without `continue` — + // likely a bug (per upstream lightningcss, a `.` null-cell token should + // push None and continue; as written, any string containing `.` fails + // the name-codepoint check below and the whole value fails to parse). + // Mirroring the observable Zig control flow for now (Zig dead-advances + // `string = rest[idx..]`; this block is empty — same outcome). } let starts_with_name_codepoint = 'brk: { diff --git a/src/css/properties/margin_padding.rs b/src/css/properties/margin_padding.rs index d335107cb744..c15de5051926 100644 --- a/src/css/properties/margin_padding.rs +++ b/src/css/properties/margin_padding.rs @@ -83,8 +83,8 @@ impl_size_shorthand!( // // `PropertyFieldMap` (an anonymous struct mapping field-name → PropertyIdTag) // becomes an associated const slice; consumers that did `@field(map, name)` -// will look up by name. // TODO(port): if consumers need O(1) by-field access, -// switch to per-type associated consts. +// will look up by name. (If consumers ever need O(1) by-field access, this +// could switch to per-type associated consts.) define_rect_shorthand! { /// A value for the [inset](https://drafts.csswg.org/css-logical/#propdef-inset) shorthand property. @@ -375,7 +375,7 @@ pub type InsetHandler = SizeHandler; // calls through `S::*`. Each concrete handler is a zero-sized marker type // implementing the spec. // -// TODO(port): a `macro_rules! size_handler_spec!` could generate the four +// A `macro_rules! size_handler_spec!` could generate the four // `SizeHandlerSpec` impls from the same 13-argument table the Zig used, // eliminating the per-spec extract/construct boilerplate. Left explicit for // reviewability. @@ -417,7 +417,7 @@ pub trait SizeHandlerSpec { const SHORTHAND: PropertyIdTag; const BLOCK_SHORTHAND: PropertyIdTag; const INLINE_SHORTHAND: PropertyIdTag; - // PORT NOTE: `PropertyId` mirrors of TOP/BOTTOM/LEFT/RIGHT for + // `PropertyId` mirrors of TOP/BOTTOM/LEFT/RIGHT for // `UnparsedProperty::with_property_id`. All margin/padding/inset/scroll-* // `PropertyId` variants are payload-free, so these are well-formed consts. const TOP_ID: PropertyId; @@ -447,8 +447,8 @@ pub trait SizeHandlerSpec { // Each pair is the Rust spelling of: // `@field(property, @tagName(X_prop))` → extract_x // `@unionInit(Property, @tagName(X_prop), v)` → make_x - // TODO(port): these are pure mechanical pattern-matches over `Property`; - // could generate via macro. + // These are pure mechanical pattern-matches over `Property`; they could + // be generated via macro. fn extract_top(p: &Property) -> &LengthPercentageOrAuto; fn extract_bottom(p: &Property) -> &LengthPercentageOrAuto; @@ -531,7 +531,7 @@ impl Default for SizeHandler { } } -// PORT NOTE: `context.arena` was dropped from PropertyHandlerContext; the +// `context.arena` was dropped from PropertyHandlerContext; the // arena is recovered via `dest.bump()` (DeclarationList = bumpalo::Vec). impl SizeHandler { // ---- @field(this, field) replacements ---- @@ -674,7 +674,7 @@ impl SizeHandler { dest, context, ); - // PORT NOTE: Zig stored `property.deepClone(arena)`; reconstruct + // Zig stored `property.deepClone(arena)`; reconstruct // via the spec's `make_X(extract_X)` pair (same observable shape). self.logical_property_helper( LogicalSlot::BlockStart, @@ -830,7 +830,7 @@ impl SizeHandler { self.flush(dest, context); } - // PORT NOTE: reshaped — Zig's single `flushHelper` (generic over `comptime field: []const u8` + // Reshaped — Zig's single `flushHelper` (generic over `comptime field: []const u8` // via `@field(this, field)`) is split into `flush_helper_physical` + `flush_helper_logical` // because the physical slots hold `Option` and the logical slots hold // `Option`; Rust cannot express `@field` over heterogeneous Option payloads generically. @@ -1168,7 +1168,7 @@ impl SizeHandler { // Zig: `@as(css.PropertyIdTag, v.*) == logical` — raw discriminant. if v.variant_tag() == logical { // Zig moved the payload (`@field(v, @tagName(logical))`) by value. - // PORT NOTE: reshaped for borrowck — clone instead of moving out + // Reshaped for borrowck — clone instead of moving out // of `&Property`; `LengthPercentageOrAuto` is small. dest.push(make_physical(extract_logical(v).clone())); } else if let Property::Unparsed(u) = v { @@ -1188,7 +1188,7 @@ enum LogicalSidePair { // Spec instantiations // ────────────────────────────────────────────────────────────────────────── // -// PORT NOTE: the `extract_*` / `make_*` / `shorthand_*` bodies are pure +// The `extract_*` / `make_*` / `shorthand_*` bodies are pure // `@field` / `@unionInit` token-pasting in Zig (`NewSizeHandler`). // `size_handler_spec_projections!` expands them from the 11 `Property` // variant idents + 3 shorthand value-type idents that the Zig diff --git a/src/css/properties/masking.rs b/src/css/properties/masking.rs index 964188074ab8..cc9a89a25569 100644 --- a/src/css/properties/masking.rs +++ b/src/css/properties/masking.rs @@ -28,8 +28,6 @@ use crate::properties::PropertyIdTag; /// A [``](https://www.w3.org/TR/css-masking-1/#typedef-geometry-box) value /// as used in the `mask-clip` and `clip-path` properties. -// TODO(port): css.DefineEnumProperty(@This()) — comptime-generated eql/hash/parse/toCss/deepClone. -// In Rust this becomes #[derive] of the css enum-property protocol (kebab-case serialization). #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum GeometryBox { /// The painted content is clipped to the content box. @@ -107,7 +105,7 @@ pub struct Polygon { /// The fill rule used to determine the interior of the polygon. pub fill_rule: FillRule, /// The points of each vertex of the polygon. - // TODO(port): css is an AST crate (§Allocators) — if Polygon is arena-fed this must become + // If Polygon ever becomes arena-fed this must become (§Allocators: AST crates are arena-fed) // `bun_alloc::ArenaVec<'bump, Point>` and Polygon/BasicShape/ClipPath gain `<'bump>`. // No construction site exists in src/css/*.zig today, so provenance is unconfirmed; keeping // plain Vec until the arena story is verified. @@ -136,7 +134,6 @@ pub struct Point { } /// A value for the [mask-mode](https://www.w3.org/TR/css-masking-1/#the-mask-mode) property. -// TODO(port): css.DefineEnumProperty(@This()) → derive css enum-property protocol #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum MaskMode { /// The luminance values of the mask image is used. @@ -152,7 +149,6 @@ pub enum MaskMode { } /// A value for the [mask-clip](https://www.w3.org/TR/css-masking-1/#the-mask-clip) property. -// TODO(port): css.DeriveParse / css.DeriveToCss → derive css union-property protocol #[derive(Debug, Clone, Copy, PartialEq, Eq, css::Parse, css::ToCss)] pub enum MaskClip { /// A geometry box. @@ -164,7 +160,6 @@ pub enum MaskClip { } /// A value for the [mask-composite](https://www.w3.org/TR/css-masking-1/#the-mask-composite) property. -// TODO(port): css.DefineEnumProperty(@This()) → derive css enum-property protocol #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum MaskComposite { /// The source is placed over the destination. @@ -183,7 +178,6 @@ pub enum MaskComposite { } /// A value for the [mask-type](https://www.w3.org/TR/css-masking-1/#the-mask-type) property. -// TODO(port): css.DefineEnumProperty(@This()) → derive css enum-property protocol #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum MaskType { /// The luminance values of the mask is used. @@ -195,7 +189,7 @@ pub enum MaskType { } /// A value for the [mask](https://www.w3.org/TR/css-masking-1/#the-mask) shorthand property. -// PORT NOTE: Debug/Clone/PartialEq derives gated on `Image`/`Position`/ +// Debug/Clone/PartialEq derives gated on `Image`/`Position`/ // `BackgroundSize`/`BackgroundRepeat` gaining those derives upstream. #[cfg_attr(any(), derive(Debug, Clone, PartialEq))] #[derive(DeepClone, CssEql)] @@ -219,9 +213,8 @@ pub struct Mask { } impl Mask { - // TODO(port): PropertyFieldMap was a Zig anon-struct const consumed by comptime - // reflection in shorthand handlers. Represented as an assoc const slice; could - // be replaced with a trait/derive. + // Zig's PropertyFieldMap was an anon-struct const consumed by comptime + // reflection in shorthand handlers; represented here as an assoc const slice. pub const PROPERTY_FIELD_MAP: &'static [(&'static str, PropertyIdTag)] = &[ ("image", PropertyIdTag::MaskImage), ("position", PropertyIdTag::MaskPosition), @@ -233,8 +226,8 @@ impl Mask { ("mode", PropertyIdTag::MaskMode), ]; - // TODO(port): VendorPrefixMap was a Zig anon-struct const of bools consumed by - // comptime reflection. Represented as a field-name slice; could be replaced with trait/derive. + // Zig's VendorPrefixMap was an anon-struct const of bools consumed by + // comptime reflection; represented here as a field-name slice. pub const VENDOR_PREFIX_MAP: &'static [&'static str] = &["image", "position", "size", "repeat", "clip", "origin"]; @@ -373,7 +366,6 @@ impl Mask { } /// A value for the [mask-border-mode](https://www.w3.org/TR/css-masking-1/#the-mask-border-mode) property. -// TODO(port): css.DefineEnumProperty(@This()) → derive css enum-property protocol #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum MaskBorderMode { /// The luminance values of the mask image is used. @@ -386,7 +378,7 @@ pub enum MaskBorderMode { } /// A value for the [mask-border](https://www.w3.org/TR/css-masking-1/#the-mask-border) shorthand property. -// PORT NOTE: Debug/Clone/PartialEq derives gated on `Image`/`Rect<_>` gaining +// Debug/Clone/PartialEq derives gated on `Image`/`Rect<_>` gaining // those derives upstream. #[cfg_attr(any(), derive(Debug, Clone, PartialEq))] #[derive(DeepClone, CssEql)] @@ -408,7 +400,7 @@ pub struct MaskBorder { impl MaskBorder { // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"mask-border", PropertyFieldMap); - // TODO(port): PropertyFieldMap — see note on Mask::PROPERTY_FIELD_MAP + // See the PropertyFieldMap note on Mask::PROPERTY_FIELD_MAP. pub const PROPERTY_FIELD_MAP: &'static [(&'static str, PropertyIdTag)] = &[ ("source", PropertyIdTag::MaskBorderSource), ("slice", PropertyIdTag::MaskBorderSlice), @@ -474,7 +466,6 @@ impl MaskBorder { /// property. /// /// See also [MaskComposite](MaskComposite). -// TODO(port): css.DefineEnumProperty(@This()) → derive css enum-property protocol #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum WebKitMaskComposite { #[css(name = "clear")] @@ -513,7 +504,6 @@ pub enum WebKitMaskComposite { /// property. /// /// See also [MaskMode](MaskMode). -// TODO(port): css.DefineEnumProperty(@This()) → derive css enum-property protocol #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum WebKitMaskSourceType { /// Equivalent to `match-source` in the standard `mask-mode` syntax. @@ -529,8 +519,8 @@ pub enum WebKitMaskSourceType { // blocked_on: PropertyId::WebKitMaskComposite variant name (codegen spelling is `WebKitMaskComposite`) pub fn get_webkit_mask_property(property_id: &PropertyId) -> Option { - // TODO(port): PropertyId variant naming — Zig uses kebab-case @"mask-border-source" etc. - // Mapping to PascalCase variants here; verify exact PropertyId enum shape. + // Zig spells these variants kebab-case (@"mask-border-source" etc.); the + // Rust PropertyId enum uses the PascalCase spellings below. match property_id { PropertyId::MaskBorderSource => Some(PropertyId::MaskBoxImageSource(VendorPrefix::WEBKIT)), PropertyId::MaskBorderSlice => Some(PropertyId::MaskBoxImageSlice(VendorPrefix::WEBKIT)), diff --git a/src/css/properties/mod.rs b/src/css/properties/mod.rs index 85ee46adb5f2..f0f27045a89c 100644 --- a/src/css/properties/mod.rs +++ b/src/css/properties/mod.rs @@ -14,18 +14,6 @@ // `PropertyId::set_prefixes_for_targets` / `from_name_and_prefix` and the // `Property` payloads that name `css_values::*` resolve directly. -/// Declares a property-handler ZST with the `handle_property` / `finalize` -/// surface that `DeclarationHandler` (declaration.rs) composes over. The -/// real handler bodies live in the gated leaf .rs files; until those -/// un-gate, these no-op stubs keep `DeclarationHandler` compiling against -/// the now-real `Property` enum. -/// -/// PORT NOTE: Zig handlers are plain structs with `handleProperty(*Self, -/// *const Property, *DeclarationList, *PropertyHandlerContext) bool` + -/// `finalize(*Self, *DeclarationList, *PropertyHandlerContext) void`. Same -/// shape here; lifetimes on `DeclarationList<'bump>` / context are erased -/// behind anonymous lifetimes since the stub bodies touch neither. - // ─── Rect / Size shorthand impl + define macros ──────────────────────────── // Shared by `border.rs` and `margin_padding.rs`. These are the Rust port of // Zig's `css.DefineRectShorthand` / `css.DefineSizeShorthand` comptime mixins @@ -95,8 +83,11 @@ macro_rules! define_rect_shorthand { } impl $name { - // TODO(port): bring this back - // (old using name space) css::DefineShorthand(@This(), PropertyIdTag::$shorthand_id); + // Zig applied `css.DefineShorthand(@This(), PropertyIdTag::, + // PropertyFieldMap)` here, but every method it defines + // (`fromLonghands`/`longhands`/`longhand`) bodies out to + // `@compileError(todo_stuff.depth)` (css_parser.zig:307ff) — it is not + // callable. `PROPERTY_FIELD_MAP` preserves the map data it consumed. pub const PROPERTY_FIELD_MAP: &[(&str, $crate::properties::PropertyIdTag)] = &[ ("top", $crate::properties::PropertyIdTag::$top_id), diff --git a/src/css/properties/overflow.rs b/src/css/properties/overflow.rs index f6613e7c245e..dd03bc2f6c35 100644 --- a/src/css/properties/overflow.rs +++ b/src/css/properties/overflow.rs @@ -29,8 +29,6 @@ impl Overflow { /// An [overflow](https://www.w3.org/TR/css-overflow-3/#overflow-properties) keyword /// as used in the `overflow-x`, `overflow-y`, and `overflow` properties. -// PORT NOTE: css.DefineEnumProperty(@This()) — comptime mixin providing -// eql/hash/parse/to_css/deep_clone from @tagName. #[derive(Clone, Copy, PartialEq, Eq, Hash, crate::DefineEnumProperty)] pub enum OverflowKeyword { /// Overflowing content is visible. diff --git a/src/css/properties/prefix_handler.rs b/src/css/properties/prefix_handler.rs index e3bebada3143..fe64c146d969 100644 --- a/src/css/properties/prefix_handler.rs +++ b/src/css/properties/prefix_handler.rs @@ -9,7 +9,9 @@ use bun_alloc::ArenaVecExt as _; pub struct FallbackHandler { pub color: Option, pub text_shadow: Option, - // TODO: add these back plz + // The remaining fallback fields are not implemented yet. This mirrors the Zig + // source (prefix_handler.zig), which has the same fields commented out; add them + // here when they are added there. // filter: Option, // backdrop_filter: Option, // fill: Option, @@ -28,12 +30,10 @@ impl FallbackHandler { // The Zig source does `inline for (std.meta.fields(FallbackHandler))` and uses // `@field` / `@unionInit` keyed on the field name. Rust has no field reflection, // so we expand each (field, Property variant, has_vendor_prefix) pair via macro. - // TODO(port): proc-macro — if the field list grows, generate these arms from a - // single source of truth shared with `Property`/`PropertyIdTag`. let arena = dest.bump(); - // PORT NOTE: Zig's `inline for` over `std.meta.fields(FallbackHandler)` dispatched + // Zig's `inline for` over `std.meta.fields(FallbackHandler)` dispatched // each (field, Property variant) pair via a single generic body using `@field` / // `@unionInit` + `css.generic.{deepClone,isCompatible,hasGetFallbacks}`. Rust has // no field reflection and the generic-trait surface (`DeepClone`/`IsCompatible`/ @@ -53,7 +53,7 @@ impl FallbackHandler { let mut val = ($dc)(payload, arena); if $self_field.is_none() { - // PORT NOTE: `has_fallbacks` only used in the vendor-prefixed branch in Zig. + // Zig only consulted `has_fallbacks` in the vendor-prefixed branch. ($fb)(&mut val, arena, &context.targets, dest); } @@ -75,7 +75,7 @@ impl FallbackHandler { }; } - // PORT NOTE: reshaped for borrowck — pre-borrow each self. as &mut so the + // Reshaped for borrowck — pre-borrow each self. as &mut so the // macro body can both read and assign it without re-borrowing `self`. let this = &mut *self; let color = &mut this.color; @@ -138,11 +138,7 @@ impl FallbackHandler { return false; }; - // TODO(port): re-enable once `PropertyHandlerContext::add_unparsed_fallbacks` - // un-gates (blocked on `SupportsCondition::eql` in context.rs). - context.add_unparsed_fallbacks(arena, &mut unparsed); - let _ = &mut unparsed; if let Some(i) = *index { dest[i] = Property::Unparsed(unparsed); } else { diff --git a/src/css/properties/properties_generated.rs b/src/css/properties/properties_generated.rs index 9777c271783a..745eee8b5844 100644 --- a/src/css/properties/properties_generated.rs +++ b/src/css/properties/properties_generated.rs @@ -1,7 +1,9 @@ -// This file is autogenerated by generate_properties.ts. DO NOT EDIT! -// (Rust emitter: generated from src/css/properties/properties_generated.zig) +// Hand-maintained Rust port, originally derived from +// src/css/properties/properties_generated.zig. Note: generate_properties.ts +// (`bun run css-properties`) regenerates only the .zig reference file, never +// this .rs file — edits here are safe and will not be clobbered. // -// PORT NOTE: type paths below resolve against `super::*` (the leaf property +// Type paths below resolve against `super::*` (the leaf property // modules — currently data-only stub bodies in `mod.rs`) and // `crate::css_values`, matching the Zig prelude in properties_generated.zig. @@ -682,7 +684,7 @@ impl PropertyIdTag { /// A known CSS property name + (for prefixable properties) the vendor /// prefix it was parsed with. Variants without payload are unprefixed. // -// PORT NOTE: do NOT `#[derive(PartialEq, Eq)]` here — the spec-correct +// Do NOT `#[derive(PartialEq, Eq)]` here — the spec-correct // equality (Zig `PropertyId.eql`, properties_generated.zig:9195) ignores the // `Custom(CustomPropertyName)` payload and is hand-written below. A derived // impl would (a) conflict (E0119) and (b) diverge by comparing custom-name @@ -940,7 +942,7 @@ pub enum PropertyId { Custom(CustomPropertyName), } -// PORT NOTE: Zig `PropertyId.eql()` (properties_generated.zig:9195) compares the +// Zig `PropertyId.eql()` (properties_generated.zig:9195) compares the // tag, then *only* compares the payload when its type is `VendorPrefix` — for // `.custom` (whose payload is `CustomPropertyName`) and all unit/void variants // it returns `true` on tag match alone. A derived `PartialEq` would compare the @@ -958,7 +960,7 @@ impl PartialEq for PropertyId { impl Eq for PropertyId {} -// PORT NOTE: Zig `PropertyId.hash()` (properties_generated.zig:9209) hashes only +// Zig `PropertyId.hash()` (properties_generated.zig:9209) hashes only // the tag discriminant so PropertyId can key a hash map consistent with `eql`. impl core::hash::Hash for PropertyId { #[inline] @@ -1347,8 +1349,8 @@ impl PropertyId { /// isn't allowed for that property. pub fn from_name_and_prefix(name: &[u8], pre: VendorPrefix) -> Option { use bun_core::strings; - // PORT NOTE: Zig used a comptime perfect-hash map; linear scan here is - // a placeholder — replace with a phf / match-on-len when un-gated. + // Zig used a comptime perfect-hash map; the linear scan here is + // correct but slower — a phf / match-on-len would be an optimization. if strings::eql_case_insensitive_ascii_check_length(name, b"background-color") { let allowed: VendorPrefix = VendorPrefix::NONE; if allowed.intersects(pre) { @@ -3133,7 +3135,7 @@ impl PropertyId { /// A parsed CSS declaration value, tagged by [`PropertyIdTag`]. Prefixed /// properties carry `(value, VendorPrefix)`. -// PORT NOTE: no `#[derive(Clone)]` — several `css_values::*` payloads +// No `#[derive(Clone)]` — several `css_values::*` payloads // (Image, Size2D, Rect, SmallList) intentionally lack `Clone` and use // `deep_clone(&Arena)` instead. `Property::deep_clone` is the public API. pub enum Property { @@ -6194,7 +6196,7 @@ impl Property { /// Returns the given longhand property for a shorthand. /// - /// PORT NOTE: in Zig (`properties_generated.zig:7087-7160`) each arm + /// In Zig (`properties_generated.zig:7087-7160`) each arm /// dispatches to `v.longhand(property_id)` where the per-type `longhand` /// is provided by `DefineShorthand`, whose body is /// `@compileError(todo_stuff.depth)`. Zig only instantiates @@ -6207,7 +6209,7 @@ impl Property { pub fn longhand(&self, property_id: &PropertyId) -> Option { #[inline(always)] fn lh(_v: &T, _id: &PropertyId) -> Option { - // PORT NOTE: per-type `v.longhand(property_id)` is + // Per-type `v.longhand(property_id)` is // `@compileError(todo_stuff.depth)` in Zig and never instantiated. // Trip in debug so callers can't accidentally rely on the // always-`None` placeholder before `DefineShorthand::longhand` @@ -7411,7 +7413,7 @@ impl Property { } } -// PORT NOTE: `declaration::placeholder_property()` (the moved-out slot +// `declaration::placeholder_property()` (the moved-out slot // sentinel in `DeclarationBlock::minify`) is `Property{ .all = .revert-layer }` // in Zig. Expose it via `Default` so the un-gated stub branch in // `declaration.rs` keeps compiling against the real enum. diff --git a/src/css/properties/properties_impl.rs b/src/css/properties/properties_impl.rs index aa1295dd1a5f..f835462aeb30 100644 --- a/src/css/properties/properties_impl.rs +++ b/src/css/properties/properties_impl.rs @@ -33,9 +33,10 @@ impl Property { } /// Ordered single-bit prefix flags for the `inline for (VendorPrefix.FIELDS)` -/// Zig idiom. The crate-root `VendorPrefix::FIELDS` is a `&[&str]` name list; -/// the to_css loops here need the bitflag values directly, in Zig declaration -/// order (webkit, moz, ms, o, none). +/// Zig idiom. The crate-root `VendorPrefix::FIELDS` is a +/// `&'static [VendorPrefix]` with the same values in the same declaration +/// order (webkit, moz, ms, o, none); kept duplicated here as a fixed-size +/// array for the to_css loops. pub(super) const PREFIX_FLAGS: [VendorPrefix; 5] = [ VendorPrefix::WEBKIT, VendorPrefix::MOZ, @@ -51,9 +52,8 @@ pub(super) mod property_id_mixin { let name = this.name(); let prefix_value = this.prefix().or_none(); - // PORT NOTE: Zig `inline for (VendorPrefix.FIELDS) |field|` + `@field` iterates each - // bitflag field and tests it. `PREFIX_FLAGS` is the same set in the same order; - // `contains` replaces the `@field` test. + // `PREFIX_FLAGS` mirrors Zig's `VendorPrefix.FIELDS` — same set, same + // order; serialization order of prefixed names depends on it. dest.write_comma_separated( PREFIX_FLAGS .iter() @@ -67,8 +67,6 @@ pub(super) mod property_id_mixin { } pub(crate) fn parse(input: &mut css::Parser) -> css::Result { - // PORT NOTE: `css::Result` is assumed to alias `Result`; - // the Zig `.result`/`.err` switch collapses to `?`. let name = input.expect_ident()?; Ok(from_string(name)) } @@ -101,7 +99,6 @@ pub(super) mod property_mixin { } let (name, prefix) = this.__to_css_helper(); - // PORT NOTE: see property_id_mixin::to_css for the `inline for` + `@field` mapping. dest.write_separated( PREFIX_FLAGS.iter().copied().filter(|p| prefix.contains(*p)), |d| { diff --git a/src/css/properties/shape.rs b/src/css/properties/shape.rs index 17615dce199d..f8aa42c6cba2 100644 --- a/src/css/properties/shape.rs +++ b/src/css/properties/shape.rs @@ -4,9 +4,9 @@ pub use crate::css_parser as css; /// determine the interior of a `polygon()` shape. /// /// See [Polygon](Polygon). -// TODO(port): Zig source is `css.DefineEnumProperty(@compileError(css.todo_stuff.depth))` — -// a placeholder that compile-errors on use. Left as an uninhabited stub until the -// CSS shapes module is actually implemented. +// Zig source is `css.DefineEnumProperty(@compileError(css.todo_stuff.depth))` — +// a placeholder that compile-errors on use. Mirrored as an uninhabited enum +// until the CSS shapes module is actually implemented. pub enum FillRule {} /// A CSS [``](https://www.w3.org/TR/css-color-4/#typedef-alpha-value), diff --git a/src/css/properties/size.rs b/src/css/properties/size.rs index 70fa24529e18..4dc92e7d093a 100644 --- a/src/css/properties/size.rs +++ b/src/css/properties/size.rs @@ -25,7 +25,7 @@ pub enum BoxSizing { /// Include the padding and border (but not the margin) in the width and height. BorderBox, } -// PORT NOTE: css::DefineEnumProperty(@This()) — provided eql/hash/parse/toCss/deepClone via +// Zig: css::DefineEnumProperty(@This()) — provided eql/hash/parse/toCss/deepClone via // comptime reflection over @tagName. Hand-written here (only two variants) so the inherent // `parse`/`to_css` participate in `impl_parse_tocss_via_inherent!` without a derive-coherence clash. impl BoxSizing { @@ -70,7 +70,7 @@ pub enum Size { } /// Case-insensitive keyword dispatch for `Size`/`MaxSize` parse bodies. -/// PORT NOTE: Zig used `bun.ComptimeStringMap(..).getASCIIICaseInsensitive` — +/// Zig used `bun.ComptimeStringMap(..).getASCIIICaseInsensitive` — /// expanded as an `if`-chain over `eql_case_insensitive_ascii::` (≤14 keys; /// per PORTING.md a phf table is overkill at this size). macro_rules! size_ident_match { @@ -153,7 +153,7 @@ impl Size { } } -// PORT NOTE: split out of `impl Size` above — these don't depend on +// Split out of `impl Size` above — these don't depend on // `parse`/`to_css` surface and are needed by `SizeHandler`. impl Size { pub(crate) fn is_compatible(&self, browsers: &css::targets::Browsers) -> bool { @@ -184,13 +184,12 @@ impl Size { } pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // TODO(port): css.implementDeepClone — comptime field-walk; `Size` carries - // only `LengthPercentage`/`VendorPrefix` payloads, both `Clone`-via-derive. + // Zig: css.implementDeepClone (comptime field-walk). `Size` carries only + // `LengthPercentage`/`VendorPrefix` payloads, both `Clone`-via-derive. self.clone() } pub(crate) fn eql(lhs: &Self, rhs: &Self) -> bool { - // TODO(port): css.implementEql — comptime field-walk; #[derive(PartialEq)] above covers it. lhs == rhs } } @@ -291,7 +290,7 @@ impl MaxSize { } } -// PORT NOTE: split out of `impl MaxSize` above — these don't depend on +// Split out of `impl MaxSize` above — these don't depend on // `parse`/`to_css` surface and are needed by `SizeHandler`. impl MaxSize { pub(crate) fn is_compatible(&self, browsers: &css::targets::Browsers) -> bool { @@ -322,8 +321,8 @@ impl MaxSize { } pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // TODO(port): css.implementDeepClone — comptime field-walk; `MaxSize` carries - // only `LengthPercentage`/`VendorPrefix` payloads, both `Clone`-via-derive. + // Zig: css.implementDeepClone (comptime field-walk). `MaxSize` carries only + // `LengthPercentage`/`VendorPrefix` payloads, both `Clone`-via-derive. self.clone() } @@ -375,7 +374,7 @@ impl AspectRatio { } pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // PORT NOTE: css.implementDeepClone — `Ratio` is two `f32`s; #[derive(Clone)] is exact. + // Zig: css.implementDeepClone — `Ratio` is two `f32`s; #[derive(Clone)] is exact. *self } @@ -410,7 +409,7 @@ bitflags::bitflags! { impl SizeProperty { pub(crate) fn try_from_property_id_tag(property_id: PropertyIdTag) -> Option { - // TODO(port): Zig used `inline for (std.meta.fields(@This()))` to compare each + // Zig used `inline for (std.meta.fields(@This()))` to compare each // bitfield name against PropertyIdTag's @tagName. Expanded explicitly here. match property_id { PropertyIdTag::Width => Some(SizeProperty::WIDTH), @@ -449,13 +448,13 @@ pub struct SizeHandler { pub category: PropertyCategory, } -// PORT NOTE: `context.arena` was dropped from PropertyHandlerContext; the +// `context.arena` was dropped from PropertyHandlerContext; the // arena is recovered via `dest.bump()`. use css::compat::Feature; // ─── helper macros (Zig used `inline fn` + `comptime []const u8` field names + @field/@unionInit) ─── // -// TODO(port): the following four macros replace Zig's `propertyHelper`, `logicalUnparsedHelper`, +// The following four macros replace Zig's `propertyHelper`, `logicalUnparsedHelper`, // `flushPrefixHelper`, `flushPropertyHelper`, `flushLogicalHelper`. The Zig code passes field // names as comptime strings and uses @field/@unionInit/@tagName to splice them into struct/enum // accesses. Rust has no equivalent reflection — macro_rules! is the closest 1:1 mapping. @@ -487,7 +486,7 @@ macro_rules! logical_unparsed_helper { $this.flushed_properties.insert( SizeProperty::try_from_property_id_tag($unparsed.property_id.tag()).unwrap(), ); - // PORT NOTE: Zig pushed `property.deepClone(arena)`; the matched + // Zig pushed `property.deepClone(arena)`; the matched // payload is `Unparsed`, so reconstruct directly. $dest.push(Property::Unparsed($unparsed.deep_clone(bump))); } else { @@ -506,7 +505,7 @@ macro_rules! flush_prefix_helper { .targets .prefixes(VendorPrefix::NONE, css::prefixes::Feature::$feature) .difference(VendorPrefix::NONE); - // TODO(port): `inline for (css.VendorPrefix.FIELDS)` — iterate set bits. + // Zig: `inline for (css.VendorPrefix.FIELDS)` — iterate set bits. for prefix in prefixes.iter() { $dest.push(Property::$prop_variant($size_ty::$size_variant(prefix))); } diff --git a/src/css/properties/text.rs b/src/css/properties/text.rs index 0e737247e745..7ca3ba236e0c 100644 --- a/src/css/properties/text.rs +++ b/src/css/properties/text.rs @@ -130,9 +130,11 @@ impl TextShadow { // Ported as `#[derive(PartialEq)]` above; callers use `==`. pub(crate) fn deep_clone(&self, alloc: &bun_alloc::Arena) -> Self { - // TODO(port): Zig used reflection-based `css.implementDeepClone`. Fields here - // are value types, so a plain Clone is equivalent; arena param retained for - // signature compatibility with the CSS deep_clone protocol. + // Zig used reflection-based `css.implementDeepClone`. Fields deep-clone + // via `#[derive(Clone)]` (`CssColor`/`Length` `Box`-carrying variants + // clone deeply onto the global heap), so a plain Clone is equivalent; + // arena param retained for signature compatibility with the CSS + // deep_clone protocol. let _ = alloc; self.clone() } diff --git a/src/css/properties/transform.rs b/src/css/properties/transform.rs index 0ddb169af138..9945125b0433 100644 --- a/src/css/properties/transform.rs +++ b/src/css/properties/transform.rs @@ -13,21 +13,22 @@ use crate::{ }; /// A value for the [transform](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#propdef-transform) property. -// PORT NOTE: was `BumpVec<'bump, Transform>`; downgraded to `Vec` so the -// `Property` enum (properties_generated.rs) stays lifetime-free. Re-thread -// `'bump` through `Property<'a>` crate-wide in a later pass (see line :1096). +// Was `BumpVec<'bump, Transform>` in an earlier port iteration; uses `Vec` so +// the `Property` enum (properties_generated.rs) stays lifetime-free. +// Re-threading `'bump` through `Property<'a>` crate-wide is deferred work +// (see /tmp/todo-fix/complex/mk04-repair.md; also TransformHandler below). #[derive(Clone, PartialEq, Default)] pub struct TransformList { pub v: Vec, } -// PORT NOTE: split out of the parse/to_css `impl TransformList` below — +// Split out of the parse/to_css `impl TransformList` below — // `TransformHandler` only needs deep_clone/eql. impl TransformList { pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // TODO(port): css.implementDeepClone reflection — `Transform`/`TransformList` - // are `Clone`-via-derive (Vec + POD payloads); an arena-aware DeepClone - // trait should land crate-wide. + // `Transform`/`TransformList` deep-clone via `#[derive(Clone)]`: payloads + // with heap variants (`LengthPercentage`/`Length` `Calc(Box<..>)`) clone + // deeply onto the global heap, matching Zig's `css.implementDeepClone`. self.clone() } } @@ -63,7 +64,7 @@ impl TransformList { // TODO: Re-enable with a better solution // See: https://github.com/parcel-bundler/lightningcss/issues/288 - // PORT NOTE: Zig's minify branch built a sub-`Printer` writing into a temp + // Zig's minify branch built a sub-`Printer` writing into a temp // buffer then `dest.writeStr(base)` — observably identical to writing // directly into `dest` while `dest.minify` is set (the original // lightningcss size-comparison was already disabled upstream). Collapsed. @@ -148,8 +149,6 @@ impl Transform { pub(crate) fn parse(input: &mut Parser) -> Result { let function = input.expect_function_cloned()?; - // PORT NOTE: Zig used a Closure struct + nested anon-struct fn passed to - // parseNestedBlock; Rust closures capture `function` directly. input.parse_nested_block(|i| -> Result { let location = i.current_source_location(); crate::match_ignore_ascii_case! { function, { @@ -241,8 +240,8 @@ impl Transform { let y = NumberOrPercentage::parse(i)?; Ok(Transform::Scale { x, y }) } else { - // PORT NOTE: Zig `x.deepClone(arena)` — `NumberOrPercentage` - // is POD; `clone()` is exact. + // `NumberOrPercentage` is POD; `clone()` matches Zig's + // `x.deepClone(arena)` exactly. let y = x.clone(); Ok(Transform::Scale { x, y }) } @@ -574,7 +573,9 @@ impl Transform { } pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // TODO(port): css.implementDeepClone reflection — payload types may need bump-aware clone + // All payload types deep-clone via `#[derive(Clone)]` — `Calc` variants + // of `LengthPercentage`/`Length` are `Box`ed and clone deeply onto the + // global heap — matching Zig's `css.implementDeepClone`. self.clone() } } @@ -612,8 +613,6 @@ pub struct Matrix3d { } /// A value for the [transform-style](https://drafts.csswg.org/css-transforms-2/#transform-style-property) property. -// TODO(port): css.DefineEnumProperty reflection → crate-wide #[derive(EnumProperty)] providing -// parse/to_css/eql/hash/deep_clone from kebab-case variant names. #[derive(Clone, Copy, PartialEq, Eq, Hash, crate::DefineEnumProperty)] pub enum TransformStyle { #[css("flat")] @@ -726,8 +725,8 @@ impl Translate { } } -// PORT NOTE: split out of the parse/to_css `impl Translate` above — these -// don't depend on Parser/Printer surface and are needed by `TransformHandler`. +// Split out of the parse/to_css `impl Translate` above — these don't depend +// on Parser/Printer surface and are needed by `TransformHandler`. impl Translate { pub(crate) fn to_transform(&self, _bump: &Bump) -> Transform { match self { @@ -745,7 +744,8 @@ impl Translate { } pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // TODO(port): css.implementDeepClone — arena-aware clone for LengthPercentage + // `LengthPercentage` is `Clone`-via-derive; a plain clone matches + // Zig's `css.implementDeepClone`. self.clone() } } @@ -846,7 +846,7 @@ impl Rotate { } } -// PORT NOTE: split out of the parse/to_css `impl Rotate` above — needed by +// Split out of the parse/to_css `impl Rotate` above — needed by // `TransformHandler`. impl Rotate { /// Converts the rotation to a transform function. @@ -930,7 +930,7 @@ impl Scale { } } -// PORT NOTE: split out of the parse/to_css `impl Scale` above — needed by +// Split out of the parse/to_css `impl Scale` above — needed by // `TransformHandler`. impl Scale { pub(crate) fn to_transform(&self, _bump: &Bump) -> Transform { @@ -962,9 +962,10 @@ crate::css_eql_partialeq!( Scale ); -// PORT NOTE: was `TransformHandler<'bump>` holding `TransformList<'bump>`; the -// `Property` enum is lifetime-free (see TransformList above), so the handler -// is too. Re-thread `'bump` crate-wide in a later pass. +// Was `TransformHandler<'bump>` holding `TransformList<'bump>` in an earlier +// port iteration; the `Property` enum is lifetime-free (see TransformList +// above), so the handler is too. Re-threading `'bump` crate-wide is deferred +// work (see /tmp/todo-fix/complex/mk04-repair.md). #[derive(Default)] pub struct TransformHandler { pub transform: Option<(TransformList, VendorPrefix)>, @@ -974,8 +975,8 @@ pub struct TransformHandler { pub has_any: bool, } -// PORT NOTE: `context.arena` was dropped from PropertyHandlerContext, so the -// arena is recovered via `dest.bump()` (DeclarationList = bumpalo::Vec). +// `context.arena` does not exist on PropertyHandlerContext; the arena is +// recovered via `dest.bump()` (DeclarationList = bumpalo::Vec). impl TransformHandler { pub(crate) fn handle_property( &mut self, @@ -984,7 +985,7 @@ impl TransformHandler { context: &mut PropertyHandlerContext, ) -> bool { let bump = dest.bump(); - // PORT NOTE: Zig used a local fn with `comptime field: []const u8` + `@field(self, field)`. + // Zig used a local fn with `comptime field: []const u8` + `@field(self, field)`. // Rust cannot index struct fields by string at runtime; use a macro to paste the ident. macro_rules! individual_property { ($field:ident, $val:expr) => {{ @@ -1004,7 +1005,7 @@ impl TransformHandler { // If two vendor prefixes for the same property have different // values, we need to flush what we have immediately to preserve order. - // PORT NOTE: reshaped for borrowck — Zig held &self.transform across + // Reshaped for borrowck — Zig held &self.transform across // self.flush(); compute the predicate first, then act. let needs_flush = if let Some(current) = &self.transform { current.0 != *transform_val && !current.1.contains(vp) @@ -1046,8 +1047,8 @@ impl TransformHandler { prefixes::Feature::Transform, )) } else { - // PORT NOTE: Zig pushed `property.deepClone(arena)`; the - // matched payload is `Unparsed`, so reconstruct directly. + // Zig pushed `property.deepClone(arena)`; the matched + // payload is `Unparsed`, so reconstruct directly. Property::Unparsed(unparsed.deep_clone(bump)) }; dest.push(prop); diff --git a/src/css/properties/transition.rs b/src/css/properties/transition.rs index 10e61d8d0121..9c21020aebc4 100644 --- a/src/css/properties/transition.rs +++ b/src/css/properties/transition.rs @@ -39,8 +39,9 @@ impl Transition { pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { // Zig: css.implementDeepClone(@This(), this, arena) — field-by-field - // reflection. All four fields are POD/Copy or `Clone`-via-derive with no - // arena indirections; #[derive(Clone)] is exact. + // reflection. All four fields deep-clone via `#[derive(Clone)]` with no + // arena indirections (any heap-carrying variants of `PropertyId`/ + // `EasingFunction` clone deeply onto the global heap), so Clone is exact. self.clone() } @@ -119,7 +120,7 @@ pub struct TransitionHandler { pub has_any: bool, } -// PORT NOTE: Zig's `property`/`maybeFlush` took `comptime prop: []const u8` and used +// Zig's `property`/`maybeFlush` took `comptime prop: []const u8` and used // `@field(this, prop)` + `val: anytype` for comptime field dispatch. Rust has no // `@field`, and passing both `&mut self` and `&mut self.` to a generic fn // trips borrowck (because `flush` needs `&mut self`). Macros expand at the call @@ -166,8 +167,8 @@ mod transition_handler_body { dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) -> bool { - // PORT NOTE: `arena` field dropped from PropertyHandlerContext; the - // arena is recovered via `dest.bump()` (DeclarationList = bumpalo::Vec). + // PropertyHandlerContext carries no arena; it is recovered via + // `dest.bump()` (DeclarationList = bumpalo::Vec). let arena = dest.bump(); match prop { Property::TransitionProperty(x) => { @@ -480,29 +481,21 @@ mod transition_handler_body { delay, timing_function, }; - let mut cloned = false; - let prefix_to_iter = property_id.prefix().or_none(); // Expand vendor prefixes into multiple transitions. - // PORT NOTE: Zig used `inline for (VendorPrefix.FIELDS)` over packed-struct - // bool fields. With bitflags, iterate the individual flag bits. + // Zig used `inline for (VendorPrefix.FIELDS)` over packed-struct + // bool fields; with bitflags, iterate the individual flag bits. // PERF(port): was comptime-unrolled inline-for — profile if it shows up on a hot path. + // Zig moved `transition` into the first matching iteration and + // deep-cloned for the rest; cloning for every iteration is + // observably identical (one extra clone of a small value). for &prefix_flag in VendorPrefix::FIELDS { if prefix_to_iter.contains(prefix_flag) { - let mut t = if cloned { - transition.deep_clone(arena) - } else { - // TODO(port): Zig moved `transition` here on first iteration; Rust - // can't move out of a value that may be reused next iteration. - // Clone unconditionally for now. - transition.deep_clone(arena) - }; - cloned = true; + let mut t = transition.deep_clone(arena); t.property = property_id.with_prefix(prefix_flag); transitions.append(t); } } - let _ = cloned; } transitions } diff --git a/src/css/properties/ui.rs b/src/css/properties/ui.rs index 900222e96177..6501fe1d7805 100644 --- a/src/css/properties/ui.rs +++ b/src/css/properties/ui.rs @@ -104,15 +104,15 @@ fn color_scheme_map_get(ident: &[u8]) -> Option { } /// A value for the [resize](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#resize) property. -// TODO(port): Zig source is `css.DefineEnumProperty(@compileError(css.todo_stuff.depth))` — intentionally unimplemented upstream. +// Zig source is `css.DefineEnumProperty(@compileError(css.todo_stuff.depth))` +// — intentionally unimplemented upstream, so this stays a unit placeholder. pub struct Resize; #[derive(Default)] pub struct ColorSchemeHandler; -// PORT NOTE: `context.arena` was dropped from PropertyHandlerContext; -// `define_var` no longer needs an arena because `TokenList.v` is a std -// `Vec` (LIFETIMES.tsv classification). +// `define_var` needs no arena because `TokenList.v` is a std +// `Vec` (unlike Zig, which threaded `context.arena` through). impl ColorSchemeHandler { pub(crate) fn handle_property( &mut self, @@ -146,7 +146,7 @@ impl ColorSchemeHandler { dest.push(define_var(b"--buncss-dark", css::Token::Ident(b"initial"))); } } - // PORT NOTE: Zig pushed `property.deepClone(arena)`; ColorScheme is + // Zig pushed `property.deepClone(arena)`; ColorScheme is // `Copy` (bitflags u8), so reconstruct the variant directly. dest.push(Property::ColorScheme(color_scheme)); true @@ -164,7 +164,7 @@ impl ColorSchemeHandler { } fn define_var(name: &'static [u8], value: css::Token) -> Property { - // PORT NOTE: `name` is `&'static [u8]` because all call sites pass byte-string literals. + // `name` is `&'static [u8]` because all call sites pass byte-string literals. // `TokenList.v` is `Vec` (std Vec — see custom.rs:320), so no arena // threading is needed here despite Zig's `ArrayList(TokenOrValue)`. Property::Custom(css::css_properties::custom::CustomProperty { diff --git a/src/css/rules/container.rs b/src/css/rules/container.rs index f97c5e1d3f49..bc3a12292f5c 100644 --- a/src/css/rules/container.rs +++ b/src/css/rules/container.rs @@ -19,8 +19,6 @@ impl ContainerName { impl ContainerName { #[inline] pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk — `CustomIdent` - // identity-copy (arena-owned slice pointer). Self { v: self.v.deep_clone(bump), } @@ -71,7 +69,7 @@ pub enum ContainerSizeFeatureId { // delegate to `enum_property_util` (driven by the `EnumProperty` derive). impl crate::media_query::FeatureIdTrait for ContainerSizeFeatureId { // Zig: pub const valueType = css.DeriveValueType(@This(), ValueTypeMap).valueType; - // PORT NOTE: DeriveValueType is comptime reflection over ValueTypeMap; expanded inline. + // `DeriveValueType` is comptime reflection over `ValueTypeMap`; expanded inline. fn value_type(&self) -> MediaFeatureType { match self { Self::Width => MediaFeatureType::Length, @@ -153,8 +151,8 @@ impl QueryCondition for StyleQuery { let property_id = crate::properties::PropertyId::parse(input)?; input.expect_colon()?; input.skip_whitespace(); - // PORT NOTE: Zig threaded `(input.arena(), null)` here; re-thread - // `&Bump` once `ParserOptions` carries the arena. + // Zig threaded `(input.arena(), null)` here; the arena gets re-threaded + // as part of the crate-wide `'bump` lifetime work (see css_parser.rs). let opts = css::ParserOptions::default(None); let feature = StyleQuery::Feature(Box::new(Property::parse(property_id, input, &opts)?)); let _ = input.try_parse(css::css_parser::parse_important); @@ -184,9 +182,8 @@ impl QueryCondition for StyleQuery { impl StyleQuery { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` variant-walk. `Operator` is `Copy`; - // `Property` routes through `dc::property` until the per-variant - // `DeepClone` derives land in `properties_generated.rs`. + // `Operator` is `Copy`; `Property` routes through `dc::property` until + // the per-variant `DeepClone` derives land in `properties_generated.rs`. match self { Self::Feature(p) => Self::Feature(Box::new(super::dc::property(p, bump))), Self::Not(c) => Self::Not(Box::new(c.deep_clone(bump))), @@ -305,9 +302,8 @@ impl QueryCondition for ContainerCondition { impl ContainerCondition { pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` variant-walk. `QueryFeature` - // routes through `dc::query_feature` (Clone is faithful — see note - // there); `Operator` is `Copy`. + // `QueryFeature` routes through `dc::query_feature` (Clone is + // faithful — see note there); `Operator` is `Copy`. match self { Self::Feature(f) => Self::Feature(Box::new(super::dc::query_feature(f, bump))), Self::Not(c) => Self::Not(Box::new(c.deep_clone(bump))), @@ -376,7 +372,6 @@ impl ContainerRule { where R: css::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { name: self.name.as_ref().map(|n| n.deep_clone(bump)), condition: self.condition.deep_clone(bump), diff --git a/src/css/rules/counter_style.rs b/src/css/rules/counter_style.rs index 9f4ea2e2a77a..99fb0b5b6186 100644 --- a/src/css/rules/counter_style.rs +++ b/src/css/rules/counter_style.rs @@ -7,8 +7,8 @@ pub struct CounterStyleRule { /// The name of the counter style to declare. pub name: CustomIdent, /// Declarations in the `@counter-style` rule. - // PORT NOTE: `DeclarationBlock<'bump>` borrows the parser arena; lifetime - // erased to `'static` here per the rules/mod.rs `CssRule` PORT NOTE. + // `DeclarationBlock<'bump>` borrows the parser arena; lifetime erased to + // `'static` here per the rules/mod.rs `CssRule` lifetime-erasure note. pub declarations: DeclarationBlock<'static>, /// The location of the rule in the source file. pub loc: Location, @@ -27,7 +27,6 @@ impl CounterStyleRule { impl CounterStyleRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { name: self.name.deep_clone(bump), declarations: super::dc::decl_block_static(&self.declarations, bump), diff --git a/src/css/rules/document.rs b/src/css/rules/document.rs index b29497931012..9ecafc0dc40a 100644 --- a/src/css/rules/document.rs +++ b/src/css/rules/document.rs @@ -29,7 +29,6 @@ impl MozDocumentRule { where R: crate::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { rules: self.rules.deep_clone(bump), loc: self.loc, diff --git a/src/css/rules/font_face.rs b/src/css/rules/font_face.rs index f02006358452..ea6460115fe6 100644 --- a/src/css/rules/font_face.rs +++ b/src/css/rules/font_face.rs @@ -89,7 +89,7 @@ impl FontFaceProperty { } pub(crate) fn deep_clone(&self, arena: &bun_alloc::Arena) -> Self { - // PORT NOTE: Zig `css.implementDeepClone` field-walk, hand-expanded. + // Zig `css.implementDeepClone` field-walk, hand-expanded. match self { FontFaceProperty::Source(v) => { FontFaceProperty::Source(v.iter().map(|s| s.deep_clone(arena)).collect()) @@ -228,7 +228,8 @@ impl UnicodeRange { fn parse_tokens(input: &mut css::Parser) -> css::Result<()> { let tok = input.next_including_whitespace()?.clone(); - // TODO(port): verify exact `Token` variant shapes (Dimension/Number payloads). + // Matches Zig `parseTokens` (font_face.zig): tag-only matches on + // `.dimension`/`.number` — payloads are never inspected. match tok { css::Token::Dimension { .. } => return Self::parse_question_marks(input), css::Token::Number { .. } => { @@ -279,7 +280,7 @@ impl UnicodeRange { } } - // PORT NOTE: Zig `css.Maybe(UnicodeRange, void)` carries no error payload → `Option`. + // Zig `css.Maybe(UnicodeRange, void)` carries no error payload → `Option`. fn parse_concatenated(text_: &[u8]) -> Option { let mut text = if !text_.is_empty() && text_[0] == b'+' { &text_[1..] @@ -426,7 +427,7 @@ pub enum FontFormat { /// An SVG font. Svg, /// An unknown format. - // PORT NOTE: arena-owned slice from parser input; TODO(refactor): thread `'i`. + // Arena-owned slice from parser input; TODO(refactor): thread `'i`. String(&'static [u8]), } @@ -464,7 +465,7 @@ impl FontFormat { } pub(crate) fn deep_clone(&self, _arena: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` variant-walk. All payloads are + // `css.implementDeepClone` variant-walk. All payloads are // `Copy` / arena-slice idents → identity copy. match self { FontFormat::Woff => FontFormat::Woff, @@ -529,7 +530,7 @@ impl Source { } pub fn deep_clone(&self, arena: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` variant-walk, hand-expanded. + // `css.implementDeepClone` variant-walk, hand-expanded. match self { Source::Url(u) => Source::Url(u.deep_clone(arena)), Source::Local(l) => Source::Local(l.deep_clone(arena)), @@ -638,7 +639,7 @@ impl UrlSource { } pub(crate) fn deep_clone(&self, arena: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk, hand-expanded. + // `css.implementDeepClone` field-walk, hand-expanded. Self { url: self.url.deep_clone(arena), format: self.format.as_ref().map(|f| f.deep_clone(arena)), @@ -684,7 +685,7 @@ impl FontFaceRule { impl FontFaceRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `FontFaceProperty`'s + // `css.implementDeepClone` field-walk. `FontFaceProperty`'s // variant-walk lands when its enum body un-gates (properties::{font, // custom}); the gated stub above panics with the blocker named. Self { @@ -700,7 +701,7 @@ impl FontFaceRule { pub(crate) struct FontFaceDeclarationParser; -// PORT NOTE: Zig modeled `AtRuleParser` / `QualifiedRuleParser` / +// Zig modeled `AtRuleParser` / `QualifiedRuleParser` / // `DeclarationParser` / `RuleBodyItemParser` as nested namespaces with // associated consts + fns. In Rust these are trait impls on // `FontFaceDeclarationParser`. diff --git a/src/css/rules/font_palette_values.rs b/src/css/rules/font_palette_values.rs index 47093fb15f50..510f5df01738 100644 --- a/src/css/rules/font_palette_values.rs +++ b/src/css/rules/font_palette_values.rs @@ -81,9 +81,9 @@ pub enum FontPaletteValuesProperty { impl FontPaletteValuesRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `FontPaletteValuesProperty`'s - // variant-walk lands when its enum body un-gates (properties::{font, - // custom}); the gated stub above panics with the blocker named. + // `FontPaletteValuesProperty`'s variant-walk lands when its enum body + // un-gates (properties::{font, custom}); the gated stub above panics + // with the blocker named. Self { name: self.name.deep_clone(bump), properties: self.properties.iter().map(|p| p.deep_clone(bump)).collect(), @@ -119,7 +119,6 @@ impl FontPaletteValuesProperty { } pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` variant-walk. match self { Self::FontFamily(f) => Self::FontFamily(f.deep_clone(bump)), Self::BasePalette(b) => Self::BasePalette(b.deep_clone(bump)), @@ -222,7 +221,6 @@ impl BasePalette { } pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` — `Copy` payload (u16). match self { Self::Light => Self::Light, Self::Dark => Self::Dark, @@ -233,7 +231,7 @@ impl BasePalette { pub(crate) struct FontPaletteValuesDeclarationParser {} -// PORT NOTE: Zig models these as nested namespace structs (`DeclarationParser`, +// Zig models these as nested namespace structs (`DeclarationParser`, // `RuleBodyItemParser`, `AtRuleParser`, `QualifiedRuleParser`) duck-typed by // `RuleBodyParser`. In Rust these are trait impls. const _: () = { diff --git a/src/css/rules/import.rs b/src/css/rules/import.rs index d25a5e8b81a4..b7750095c423 100644 --- a/src/css/rules/import.rs +++ b/src/css/rules/import.rs @@ -231,7 +231,11 @@ impl ImportRule { // The pointer is derived from `self` (not `&self.layer`) so its provenance // covers all three fields — going through a field reference would narrow // provenance to just `layer` and make sibling-field reads UB under SB. - // TODO(port): replace with an actual `conditions: ImportConditions` field on ImportRule + // + // Long-term, the pun should be replaced with an actual + // `conditions: ImportConditions` field on `ImportRule` (see the struct + // doc on `ImportConditions`); that touches every `.layer`/`.supports`/ + // `.media` access crate-wide, so it is deferred. unsafe { &*std::ptr::from_ref(self) .byte_add(core::mem::offset_of!(Self, layer)) @@ -278,7 +282,7 @@ impl ImportRule { } pub fn deep_clone(&self, bump: &Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `url: &'static [u8]` + // `css.implementDeepClone` field-walk. `url: &'static [u8]` // is an arena-owned slice → identity copy (generics.zig "const // strings" rule); `media` routes through `dc::media_list` until // `MediaList` gains an arena-aware `deep_clone`. diff --git a/src/css/rules/keyframes.rs b/src/css/rules/keyframes.rs index 0f75617789d2..01cf23f8f45d 100644 --- a/src/css/rules/keyframes.rs +++ b/src/css/rules/keyframes.rs @@ -13,9 +13,9 @@ use super::ArrayList; // ────────────────────────────────────────────────────────────────────────── /// ` = | ` -// PORT NOTE: Zig threaded the parser-input lifetime; this stores -// `&'static [u8]` per PORTING.md §AST crates and the rules/mod.rs -// `CssRule` lifetime-erasure note. +// Zig threaded the parser-input lifetime; this stores +// `&'static [u8]` per the rules/mod.rs `CssRule` lifetime-erasure +// note (mod.rs:37-41). // TODO(refactor): re-thread `'bump` here. pub enum KeyframesName { /// `` of a `@keyframes` name. @@ -94,8 +94,8 @@ impl KeyframesName { impl KeyframesName { pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` variant-walk. `Custom(&'static [u8])` - // is an arena-owned slice → identity copy (generics.zig "const strings"). + // `Custom(&'static [u8])` is an arena-owned slice → identity copy + // (generics.zig "const strings"). match self { Self::Ident(i) => Self::Ident(i.deep_clone(bump)), Self::Custom(s) => Self::Custom(s), @@ -167,8 +167,6 @@ impl KeyframeSelector { impl KeyframeSelector { pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` variant-walk. `Percentage` is - // `Copy` (`{ v: f32 }`) → identity. match self { Self::Percentage(p) => Self::Percentage(*p), Self::From => Self::From, @@ -182,7 +180,7 @@ impl KeyframeSelector { impl KeyframeSelector { // Zig: `pub const parse = css.DeriveParse(@This()).parse;` - // PORT NOTE: `DeriveParse` is a comptime type-generator producing `parse` from + // `DeriveParse` is a comptime type-generator producing `parse` from // variant introspection. Expanded by hand here: try the tuple variant // (`Percentage`) first, then fall back to keyword idents (`from`/`to`). pub(crate) fn parse(input: &mut css::Parser) -> css::Result { @@ -212,7 +210,7 @@ pub struct Keyframe { /// A list of keyframe selectors to associate with the declarations in this keyframe. pub selectors: ArrayList, /// The declarations for this keyframe. - // PORT NOTE: lifetime erased to `'static` per rules/mod.rs `CssRule` note. + // Lifetime erased to `'static` per rules/mod.rs `CssRule` note. pub declarations: DeclarationBlock<'static>, } @@ -225,7 +223,6 @@ impl Keyframe { impl Keyframe { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { selectors: self.selectors.iter().map(|s| s.deep_clone(bump)).collect(), declarations: super::dc::decl_block_static(&self.declarations, bump), @@ -311,8 +308,6 @@ impl KeyframesRule { impl KeyframesRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `VendorPrefix` is a - // `Copy` bitflag (generics.zig "simple copy types" → identity). Self { name: self.name.deep_clone(bump), keyframes: self.keyframes.iter().map(|k| k.deep_clone(bump)).collect(), @@ -328,7 +323,7 @@ impl KeyframesRule { pub(crate) struct KeyframesListParser; -// PORT NOTE: in Zig these are nested `pub const DeclarationParser = struct { ... }` +// In Zig these are nested `pub const DeclarationParser = struct { ... }` // namespaces that the css parser duck-types via `@hasDecl`. In Rust they become // trait impls on `KeyframesListParser`. // diff --git a/src/css/rules/layer.rs b/src/css/rules/layer.rs index 023179aa803f..9ff64e94215c 100644 --- a/src/css/rules/layer.rs +++ b/src/css/rules/layer.rs @@ -20,7 +20,9 @@ pub struct LayerName { } // The inline hash/eql context is replaced by `Hash`/`PartialEq` impls on `LayerName` below. -// TODO(port): ArrayHashMap must use wyhash (u32-truncated) to match Zig iteration order. +// Iteration order is insertion order in both Zig's `ArrayHashMap` and this +// port (collections/array_hash_map.rs) regardless of hash function, so the +// hasher does not need to match Zig's wyhash. pub type LayerNameHashMap = ArrayHashMap; impl core::hash::Hash for LayerName { @@ -39,7 +41,7 @@ impl PartialEq for LayerName { } impl Eq for LayerName {} -// PORT NOTE: trait `Clone` (not just inherent `deep_clone`) so the bundler's +// Trait `Clone` (not just inherent `deep_clone`) so the bundler's // `Chunk::Layers::to_owned` can `deep_clone_with(|l| l.clone())`. Segments are // arena-borrowed `&'static [u8]` (Copy), so this is the same shallow // `SmallList` copy as `deep_clone` / `clone_with_import_records`. @@ -116,7 +118,7 @@ impl LayerName { } pub fn deep_clone(&self, _bump: &Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` — `[]const u8` segments are + // `css.implementDeepClone` — `[]const u8` segments are // arena-owned (identity copy per generics.zig "const strings"). Same // body as `clone_with_import_records` above. LayerName { v: self.v.clone() } @@ -163,7 +165,7 @@ impl LayerBlockRule { where R: css::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. + // `css.implementDeepClone` field-walk. Self { name: self.name.as_ref().map(|n| n.deep_clone(bump)), rules: self.rules.deep_clone(bump), @@ -200,7 +202,7 @@ pub struct LayerStatementRule { impl LayerStatementRule { pub fn deep_clone(&self, bump: &Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. + // `css.implementDeepClone` field-walk. let mut names = SmallList::::default(); for n in self.names.slice() { names.append(n.deep_clone(bump)); diff --git a/src/css/rules/media.rs b/src/css/rules/media.rs index 29ec17f97ae6..d7bc933f3497 100644 --- a/src/css/rules/media.rs +++ b/src/css/rules/media.rs @@ -13,10 +13,8 @@ pub struct MediaRule { } // ─── behavior bodies ────────────────────────────────────────────────────── -// PORT NOTE: `minify` lives in `rules/mod.rs` (hoisted next to `CssRuleList:: -// minify` so the dispatch can call it without re-exporting `MinifyContext` -// here). `to_css` un-gated this round — `MediaList::{always_matches,to_css}` -// and `CssRuleList::to_css` are both real now. +// `minify` lives in `rules/mod.rs` (hoisted next to `CssRuleList::minify` so +// the dispatch can call it without re-exporting `MinifyContext` here). impl MediaRule { pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { if dest.minify && self.query.always_matches() { @@ -40,7 +38,6 @@ impl MediaRule { where R: crate::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { query: super::dc::media_list(&self.query, bump), rules: self.rules.deep_clone(bump), diff --git a/src/css/rules/mod.rs b/src/css/rules/mod.rs index 5780f930222a..9c67fcf19855 100644 --- a/src/css/rules/mod.rs +++ b/src/css/rules/mod.rs @@ -34,7 +34,7 @@ pub mod viewport; // ─── CssRule / CssRuleList ───────────────────────────────────────────────── // Zig: pub fn CssRule(comptime Rule: type) type { return union(enum) { ... } } // -// PORT NOTE: the original port threaded a `'bump` arena lifetime through every +// The original port threaded a `'bump` arena lifetime through every // rule (matching Zig's `ArrayListUnmanaged`-backed AST). That cascades into // every leaf module signature; while those leaves are gated, `CssRule` is // kept lifetime-free here (the gated bodies re-introduce `'bump` when they @@ -44,7 +44,7 @@ pub mod viewport; // Single source of truth for the 20 typed at-rule payloads. Adding a new // at-rule = one line here; the enum variant + `to_css` arm + `deep_clone` // arm are generated. `Unknown`/`Custom`/`Ignored` stay a fixed tail because -// their `to_css` arms are special-cased (see PORT NOTE on `Custom`). +// their `to_css` arms are special-cased (see the note on `Custom`). macro_rules! css_rule_variants { ( $( $(#[$doc:meta])* $Variant:ident($Payload:ty) ),+ $(,)? ) => { /// A single CSS rule (at-rule or style rule). @@ -65,24 +65,20 @@ macro_rules! css_rule_variants { CssRule::Unknown(x) => x.to_css(dest), // Zig: `.custom => |x| x.toCss(dest) catch return dest.addFmtError()`. // - // PORT NOTE (incomplete): the spec has TWO concrete `R` types — - // `DefaultAtRule` (whose `toCss` errors unconditionally) and - // `TailwindAtRule` (src/css/rules/tailwind.zig:14-19, used via - // `BundlerAtRule` when `ENABLE_TAILWIND_PARSING`), whose `toCss` - // SUCCEEDS and writes `@tailwind ;`. This arm therefore - // diverges from the spec for `R = TailwindAtRule`: it fails - // serialization where the spec round-trips. - // - // The correct port threads a `ToCss`-style bound (or per-`R` - // vtable) so `Custom(x)` dispatches to `x.to_css(dest)` and only - // maps the error path via `add_fmt_error()`. That bound cascades - // through every nested `CssRuleList` printer (media, supports, - // layer, document, nesting, starting_style, style, scope, - // container) — deferred to the patch that un-gates - // `BundlerAtRule = TailwindAtRule`. - // TODO(port): dispatch to `x.to_css(dest)` once `R: ToCss` (or - // equivalent) is threaded; current behavior is only spec-correct - // for `R = DefaultAtRule`. + // The spec has TWO concrete `R` types — `DefaultAtRule` (whose + // `toCss` errors unconditionally, so Zig also lands in + // `addFmtError`) and `TailwindAtRule` (src/css/rules/tailwind.zig: + // 14-19), whose `toCss` succeeds and writes `@tailwind ;`. + // Tailwind parsing is comptime-disabled in both the spec and this + // port (`ENABLE_TAILWIND_PARSING = false`, `BundlerAtRule = + // DefaultAtRule` in css_parser.rs), so erroring here matches the + // spec for every `R` that is actually instantiated. If + // `TailwindAtRule` is ever un-gated, thread a `ToCss`-style bound + // (or per-`R` vtable) so `Custom(x)` dispatches to + // `x.to_css(dest)` and only the error path maps through + // `add_fmt_error()`; that bound cascades through every nested + // `CssRuleList` printer (media, supports, layer, document, + // nesting, starting_style, style, scope, container). CssRule::Custom(_x) => Err(dest.add_fmt_error()), CssRule::Ignored => Ok(()), } @@ -191,7 +187,7 @@ impl Default for CssRuleList { // wise / variant-wise port of `css.implementDeepClone`. `CssRule::deep_clone` // (below) dispatches via method-syntax so it picks up the inherent impl. // -// PORT NOTE: most leaf rules can't use `#[derive(DeepClone)]` directly yet +// Most leaf rules can't use `#[derive(DeepClone)]` directly yet // because two field types still lack an arena-aware `deep_clone(&self, // &Arena) -> Self`: `SelectorList` (selectors/parser.rs uses no-arg // `deep_clone()`) and `Property` (properties_generated.rs — per-variant body @@ -210,7 +206,7 @@ pub(super) mod dc { /// through `dc::property` so the only remaining bottleneck is the /// per-variant `Property::deep_clone` body. /// - /// PORT NOTE: threads the real `'bump` lifetime instead of fabricating + /// Threads the real `'bump` lifetime instead of fabricating /// `'static` (PORTING.md §Forbidden: `unsafe { &*(p as *const _) }` to /// extend a lifetime). Callers whose storage is still pinned to /// `DeclarationBlock<'static>` must fix that storage type — the lie @@ -239,7 +235,7 @@ pub(super) mod dc { /// /// SAFETY: `DeclarationBlock<'static>` is the crate-wide `'bump`-erasure /// placeholder until `CssRule<'bump, R>` re-threads the arena lifetime - /// (see `style.rs` struct PORT NOTE). `bumpalo::Vec` is invariant in + /// (see the note on `StyleRule.declarations` in `style.rs`). `bumpalo::Vec` is invariant in /// `'bump`, so any `DeclarationBlock<'static>` constructor must observe a /// `&'static Arena`. The arena outlives every rule that borrows it (it /// owns them); lifetimes re-thread together when the rule structs grow a @@ -275,8 +271,8 @@ pub(super) mod dc { /// `'bump`-erasure adaptor for `&mut DeclarationHandler<'_>`. /// - /// SAFETY: `DeclarationBlock<'static>` on `StyleRule` (see style.rs struct - /// PORT NOTE) forces `DeclarationBlock::minify` to expect + /// SAFETY: `DeclarationBlock<'static>` on `StyleRule` (see the note on + /// `StyleRule.declarations` in style.rs) forces `DeclarationBlock::minify` to expect /// `DeclarationHandler<'static>`; the handlers in `MinifyContext` carry the /// real `'bump`. Both reference the same arena. Centralized here so the /// erasure lives in ONE place; collapses together with `decl_block_static` @@ -570,9 +566,11 @@ impl CssRuleList { 'arm: { match rule { CssRule::Keyframes(_keyframez) => { - // TODO(port): KeyframesRule minify (unused-symbol drop + - // same-name merge + vendor-prefix downlevel + fallbacks). - // Zig leaves this as a debug-TODO fallthrough today. + // KeyframesRule minify (unused-symbol drop + same-name + // merge + vendor-prefix downlevel + fallbacks) is + // unimplemented in the Zig spec too — rules.zig leaves + // this arm as a debug-TODO fallthrough, so falling + // through here matches it. } CssRule::CustomMedia(_) => { if context.custom_media.is_some() { @@ -623,7 +621,8 @@ impl CssRuleList { } } CssRule::LayerStatement(_lay) => { - // TODO(port): LayerStatementRule minify — Zig fallthrough. + // LayerStatementRule minify — Zig debug-TODO fallthrough + // (rules.zig); falling through matches it. } CssRule::MozDocument(doc) => { // See `Container` above: recurse so nested style rules @@ -648,7 +647,7 @@ impl CssRuleList { break 'arm; } } - CssRule::CounterStyle(_) => { /* TODO(port): Zig fallthrough */ } + CssRule::CounterStyle(_) => { /* Zig debug-TODO fallthrough */ } CssRule::Scope(scpe) => { // See `Container` above: recurse so nested style rules // count against the selector-expansion cap. @@ -676,8 +675,8 @@ impl CssRuleList { // count against the selector-expansion cap. rl.rules.minify(context, parent_is_unused)?; } - CssRule::FontPaletteValues(_) => { /* TODO(port): Zig fallthrough */ } - CssRule::Property(_) => { /* TODO(port): Zig fallthrough */ } + CssRule::FontPaletteValues(_) => { /* Zig debug-TODO fallthrough */ } + CssRule::Property(_) => { /* Zig debug-TODO fallthrough */ } _ => {} } @@ -914,7 +913,7 @@ fn minify_style_arm css::generics::DeepClone<'b>>( /// duplicates. It stores an index into the live `rules` Vec plus a /// pre-computed hash for fast lookups. /// -/// PORT NOTE: the Zig spec (`rules.zig:StyleRuleKey`) additionally stores +/// NOTE: the Zig spec (`rules.zig:StyleRuleKey`) additionally stores /// `list: *const ArrayList(CssRule(R))` and dereferences it inside `eql()`. /// That pattern is unsound in Rust under Stacked/Tree Borrows — keys persist /// in the dedup map across iterations of `minify_style_arm`, and between @@ -945,7 +944,7 @@ impl StyleRuleKey { /// Buckets keyed by the pre-computed `StyleRule::hash_key()` hold indices into /// the caller's `rules` Vec; equality (`StyleRule::is_duplicate`) is evaluated /// against an explicitly-passed `&[CssRule]` so we never smuggle a stale -/// raw pointer across `&mut rules` writes (see PORT NOTE on `StyleRuleKey`). +/// raw pointer across `&mut rules` writes (see the note on `StyleRuleKey`). #[derive(Default)] pub(crate) struct StyleRuleKeyMap { buckets: bun_collections::HashMap>, @@ -1094,10 +1093,10 @@ pub const MAX_SELECTOR_EXPANSION: u32 = 65_536; /// Per-stylesheet minification state threaded through `CssRuleList::minify` /// and every leaf rule's `minify`. /// -/// PORT NOTE: Zig carried `arena: std.mem.Allocator` for the AST arena; +/// Zig carried `arena: std.mem.Allocator` for the AST arena; /// here that is `&'a Arena` (bumpalo). All sub-allocations during minify go /// through it so the whole transformed tree is bulk-freed with the arena. -// PORT NOTE: split lifetimes — `'bump` is the parser arena (long), `'a` is the +// Split lifetimes — `'bump` is the parser arena (long), `'a` is the // per-minify borrow scope (short). `&'a mut DeclarationHandler<'a>` would force // the handler borrow to outlive the arena (invariance via `bumpalo::Vec`), // making `Stylesheet::minify`'s stack-local handlers unusable. @@ -1109,7 +1108,7 @@ pub struct MinifyContext<'a, 'bump> { pub important_handler: &'a mut css::DeclarationHandler<'bump>, pub handler_context: css::PropertyHandlerContext<'bump>, /// Class/id names known to be unused (tree-shaking input). - // PORT NOTE: Zig `*const std.StringArrayHashMapUnmanaged(void)`. + // Zig: `*const std.StringArrayHashMapUnmanaged(void)`. // `selector::is_unused` currently borrows `&ArrayHashMap<&[u8], ()>`; the // owning `MinifyOptions` stores `Box<[u8]>` keys — reconcile when // `style.rs::minify` un-gates (single key type, `Borrow<[u8]>` lookup). diff --git a/src/css/rules/namespace.rs b/src/css/rules/namespace.rs index babca2d32a86..a9501acf6855 100644 --- a/src/css/rules/namespace.rs +++ b/src/css/rules/namespace.rs @@ -29,9 +29,9 @@ impl NamespaceRule { } pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `CssString` is - // `*const [u8]` (arena-owned slice → identity copy per generics.zig - // "const strings" rule); `Ident::deep_clone` is the same identity copy. + // `CssString` is `*const [u8]` (arena-owned slice → identity copy per + // generics.zig "const strings" rule); `Ident::deep_clone` is the same + // identity copy. Self { prefix: self.prefix.as_ref().map(|p| p.deep_clone(bump)), url: self.url, diff --git a/src/css/rules/nesting.rs b/src/css/rules/nesting.rs index 2617e20dfa65..8e7dab0a20c1 100644 --- a/src/css/rules/nesting.rs +++ b/src/css/rules/nesting.rs @@ -28,7 +28,6 @@ impl NestingRule { where R: crate::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { style: self.style.deep_clone(bump), loc: self.loc, diff --git a/src/css/rules/page.rs b/src/css/rules/page.rs index 275c7e3b802b..d3a9321ccea5 100644 --- a/src/css/rules/page.rs +++ b/src/css/rules/page.rs @@ -10,8 +10,8 @@ use super::ArrayList; /// Either a name or at least one pseudo class is required. pub struct PageSelector { /// An optional named page type. - // PORT NOTE: arena-owned slice borrowed from parser input; `&'static` per - // PORTING.md §AST crates / rules/mod.rs lifetime-erasure note. + // Arena-owned slice borrowed from parser input; `&'static` per the + // rules/mod.rs lifetime-erasure note. // TODO(port): re-thread `'bump`. pub name: Option<&'static [u8]>, /// A list of page pseudo classes. @@ -34,9 +34,7 @@ impl PageSelector { impl PageSelector { pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `name: Option<&'static - // [u8]>` is an arena-owned slice → identity copy; `PagePseudoClass` is - // `Copy`. + // `name` is an arena-owned slice → identity copy; `PagePseudoClass` is `Copy`. Self { name: self.name, pseudo_classes: self @@ -82,7 +80,7 @@ pub struct PageMarginRule { /// The margin box identifier for this rule. pub margin_box: PageMarginBox, /// The declarations within the rule. - // PORT NOTE: lifetime erased to `'static` per rules/mod.rs `CssRule` note. + // Lifetime erased to `'static` per the rules/mod.rs `CssRule` note. pub declarations: DeclarationBlock<'static>, /// The location of the rule in the source file. pub loc: Location, @@ -101,7 +99,6 @@ impl PageMarginRule { impl PageMarginRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `PageMarginBox` is `Copy`. Self { margin_box: self.margin_box, declarations: super::dc::decl_block_static(&self.declarations, bump), @@ -115,7 +112,7 @@ pub struct PageRule { /// A list of page selectors. pub selectors: ArrayList, /// The declarations within the `@page` rule. - // PORT NOTE: lifetime erased to `'static` per rules/mod.rs `CssRule` note. + // Lifetime erased to `'static` per the rules/mod.rs `CssRule` note. pub declarations: DeclarationBlock<'static>, /// The nested margin rules. pub rules: ArrayList, @@ -144,8 +141,7 @@ impl PageRule { let mut i: usize = 0; let len = self.declarations.len() + self.rules.len(); - // PORT NOTE: Zig used `inline for` over field-name tuple + @field reflection. - // Unrolled to a 2-tuple of (slice, important) since both fields are property lists. + // Both declaration fields are property lists; iterate as (slice, important) pairs. let decls_groups: [(&[crate::css_parser::Property], bool); 2] = [ (self.declarations.declarations.as_slice(), false), (self.declarations.important_declarations.as_slice(), true), @@ -187,7 +183,6 @@ impl PageRule { impl PageRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { selectors: self.selectors.iter().map(|s| s.deep_clone(bump)).collect(), declarations: super::dc::decl_block_static(&self.declarations, bump), @@ -310,10 +305,7 @@ pub(crate) struct PageRuleParser<'a> { pub options: &'a css::ParserOptions<'a>, } -// PORT NOTE: Zig modeled DeclarationParser/AtRuleParser/QualifiedRuleParser/ -// RuleBodyItemParser as nested `pub const Foo = struct { ... }` namespaces with -// methods taking `*This`. In Rust these become trait impls on PageRuleParser; -// associated `pub const X = T` → `type X = T`. +// Parser trait impls for `@page` rule bodies. const _: () = { use css::css_parser::{ AtRuleParser, DeclarationParser, QualifiedRuleParser, RuleBodyItemParser, diff --git a/src/css/rules/property.rs b/src/css/rules/property.rs index 8388e17eb413..cb554c99b837 100644 --- a/src/css/rules/property.rs +++ b/src/css/rules/property.rs @@ -61,12 +61,8 @@ impl PropertyRule { impl PropertyRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `SyntaxString` has an - // inherent `deep_clone(&self, &Arena)`. While `ParsedComponent` is - // ``-gated to `()`, `Option<()>` is `Copy` → identity; - // once it un-gates, swap to `self.initial_value.as_ref().map(|v| - // v.deep_clone(bump))` (values/syntax.rs already provides the - // inherent impl). + // `SyntaxString` and `ParsedComponent` provide inherent + // `deep_clone(&self, &Arena)` impls. Self { name: self.name.deep_clone(bump), syntax: self.syntax.deep_clone(bump), @@ -92,8 +88,8 @@ impl PropertyRule { initial_value: None, }; - // PORT NOTE: split the borrows — `RuleBodyParser` borrows `input`+`p`; - // we re-borrow `input` after dropping `decl_parser`. + // `RuleBodyParser` borrows `input`+`p`; re-borrow `input` after + // dropping `decl_parser`. { let mut decl_parser = RuleBodyParser::new(input, &mut p); while let Some(decl) = decl_parser.next() { @@ -156,18 +152,16 @@ impl PropertyRule { } } -// PORT NOTE: borrows the parser input buffer for `initial_value` (arena-backed -// in CSS crate). Kept as `&'static [u8]` per PORTING.md §AST crates; -// TODO(refactor): re-thread `'i`. +// Borrows the parser input buffer for `initial_value` (arena-backed in the +// CSS crate). Kept as `&'static [u8]` per the rules/mod.rs lifetime-erasure +// note; TODO(refactor): re-thread `'i`. pub(crate) struct PropertyRuleDeclarationParser { pub syntax: Option, pub inherits: Option, pub initial_value: Option<&'static [u8]>, } -// PORT NOTE: Zig's nested `pub const DeclarationParser = struct { ... }` -// namespaces are structural duck-typing for RuleBodyParser; in Rust these -// become trait impls. +// Parser trait impls for `@property` rule bodies. const _: () = { use css::css_parser::{ AtRuleParser, DeclarationParser, QualifiedRuleParser, RuleBodyItemParser, @@ -177,8 +171,6 @@ const _: () = { impl DeclarationParser for PropertyRuleDeclarationParser { type Declaration = (); - // TODO(port): the Zig defines a ComptimeStringMap over FieldEnum but never uses it - // (usage is commented out). Preserved the active if/else-if chain instead. fn parse_value( this: &mut Self, name: &[u8], diff --git a/src/css/rules/scope.rs b/src/css/rules/scope.rs index 63f9fc7d35b3..d46de1d92e1c 100644 --- a/src/css/rules/scope.rs +++ b/src/css/rules/scope.rs @@ -33,7 +33,7 @@ impl ScopeRule { if let Some(scope_start) = &self.scope_start { dest.write_char(b'(')?; // scope_start.to_css(dest)?; - // PORT NOTE: read `dest.ctx` directly (Copy) — `Printer::context()` + // Read `dest.ctx` directly (Copy) — `Printer::context()` // ties the borrow to `&self`, which conflicts with `&mut dest`. let ctx = dest.ctx; serialize_selector_list(scope_start.v.slice(), dest, ctx, false)?; @@ -48,7 +48,7 @@ impl ScopeRule { // is treated as an ancestor of scope end. // https://drafts.csswg.org/css-nesting/#nesting-at-scope if let Some(scope_start) = &self.scope_start { - // PORT NOTE: Zig passed an anon-struct fn pointer; the Rust + // Zig passed an anon-struct fn pointer; the Rust // `Printer::with_context` carries the captured state as the // first closure arg (no `&self` capture across `&mut dest`). dest.with_context( @@ -85,9 +85,9 @@ impl ScopeRule { where R: crate::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. `SelectorList:: - // deep_clone()` intentionally drops the `&Arena` (selectors/parser.rs - // — every payload is arena-static); routed via `dc::selector_list`. + // `SelectorList::deep_clone()` intentionally drops the `&Arena` + // (selectors/parser.rs — every payload is arena-static); routed via + // `dc::selector_list`. Self { scope_start: self .scope_start diff --git a/src/css/rules/starting_style.rs b/src/css/rules/starting_style.rs index ef368fd10dbf..7f93a4af8159 100644 --- a/src/css/rules/starting_style.rs +++ b/src/css/rules/starting_style.rs @@ -27,7 +27,6 @@ impl StartingStyleRule { where R: crate::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { rules: self.rules.deep_clone(bump), loc: self.loc, diff --git a/src/css/rules/style.rs b/src/css/rules/style.rs index f507372492e0..72541aa5d72a 100644 --- a/src/css/rules/style.rs +++ b/src/css/rules/style.rs @@ -7,10 +7,10 @@ use crate::{PrintErr, Printer, VendorPrefix}; // `fn StyleRule(comptime R: type) type { return struct {...} }` → generic struct. // -// PORT NOTE: `DeclarationBlock<'bump>` borrows the parser arena (bumpalo Vecs). +// `DeclarationBlock<'bump>` borrows the parser arena (bumpalo Vecs). // Threading `'bump` here cascades into `CssRule<'bump, R>` / `CssRuleList<'bump, R>` -// (rules/mod.rs PORT NOTE) which is deferred until the leaf rules un-gate -// together; for now the lifetime is erased to `'static`. +// (see the rules/mod.rs lifetime-erasure note); for now the lifetime is erased +// to `'static`. pub struct StyleRule { /// The selectors for the style rule. pub selectors: selector::parser::SelectorList, @@ -39,10 +39,8 @@ impl StyleRule { // std.hash.Wyhash.init(0) — same algorithm as bun.hash let mut hasher = bun_wyhash::Wyhash::init(0); self.selectors.hash(&mut hasher); - // PORT NOTE: `DeclarationBlock::hash_property_ids` is still - // ``-gated in declaration.rs; inline its body here. The - // Zig `PropertyId.hash` is `hasher.update(asBytes(&@intFromEnum(self)))` - // — i.e. just the u16 tag bytes. + // Inlined `DeclarationBlock::hash_property_ids`: hash just the u16 + // property-id tag bytes. for decl in self.declarations.declarations.iter() { let tag = decl.property_id().tag() as u16; hasher.update(&tag.to_ne_bytes()); @@ -175,7 +173,7 @@ impl StyleRule { // #[cfg(feature = "sourcemap")] // dest.add_mapping(self.loc); - // PORT NOTE: `dest.context()` borrows `dest`; copy the (Copy) raw + // `dest.context()` borrows `dest`; copy the (Copy) raw // ctx field out so it doesn't conflict with the `&mut *dest` below. let ctx = dest.ctx; // Each rule prelude gets its own budget for `&` substitutions when @@ -211,7 +209,7 @@ impl StyleRule { } if dest.css_module.is_some() { - // PORT NOTE: reshaped for borrowck — Zig + // Reshaped for borrowck — Zig // `if (dest.css_module) |*css_module| // css_module.handleComposes(dest, ...)` overlaps // `&mut dest.css_module` with `&mut *dest`. Move the @@ -327,13 +325,6 @@ impl StyleRule { use css::context::DeclarationContext; let mut unused = false; - // TODO(port): blocked_on key-type mismatch — `selector::is_unused` takes - // `&ArrayHashMap<&[u8], ()>` but `MinifyContext.unused_symbols` is - // `&ArrayHashMap, ()>` (rules/mod.rs PORT NOTE: "reconcile when - // style.rs::minify un-gates — single key type, Borrow<[u8]> lookup"). - // The reconciliation lives in rules/mod.rs + selectors/selector.rs, not - // here; gate the body until those agree. - if context.unused_symbols.count() > 0 { if selector::is_unused( self.selectors.v.slice(), @@ -368,7 +359,7 @@ impl StyleRule { // } context.handler_context.context = DeclarationContext::StyleRule; - // PORT NOTE: `DeclarationBlock<'static>` (struct PORT NOTE above) forces + // `DeclarationBlock<'static>` (see the struct-level note above) forces // `minify` to want `DeclarationHandler<'static>`; route through the // single centralized `'bump`-erasure helper instead of open-coding the // lifetime cast. Collapses when `CssRule<'bump, R>` @@ -489,8 +480,8 @@ impl StyleRule { .iter() .zip(&other.declarations.declarations[..len]) { - // PORT NOTE: Zig `PropertyId.eql` == tag+prefix compare; - // that's exactly the `PartialEq` impl on `PropertyId`. + // Zig `PropertyId.eql` == tag+prefix compare; that's + // exactly the `PartialEq` impl on `PropertyId`. if a.property_id() != b.property_id() { break 'brk false; } @@ -520,9 +511,9 @@ impl StyleRule { R: crate::generics::DeepClone<'bump>, { // css is an AST crate (PORTING.md §Allocators): std.mem.Allocator → &'bump Bump, threaded. - // PORT NOTE: `css.implementDeepClone` field-walk. `declarations` routes - // through `dc::decl_block` until `DeclarationBlock::deep_clone` un-gates - // (declaration.rs — bottoms out on `Property: DeepClone`). + // `declarations` routes through `dc::decl_block` until + // `DeclarationBlock::deep_clone` un-gates (declaration.rs — bottoms out + // on `Property: DeepClone`). Self { selectors: self.selectors.deep_clone(), vendor_prefix: self.vendor_prefix, diff --git a/src/css/rules/supports.rs b/src/css/rules/supports.rs index c4c5c7cf50fb..51cbfa44ab3c 100644 --- a/src/css/rules/supports.rs +++ b/src/css/rules/supports.rs @@ -7,8 +7,8 @@ use bun_alloc::ArenaPtr; /// A [``](https://drafts.csswg.org/css-conditional-3/#typedef-supports-condition), /// as used in the `@supports` and `@import` rules. -// PORT NOTE: Zig threaded the parser-input lifetime (`[]const u8` slices borrow -// the source). Currently uses `&'static [u8]` per PORTING.md §AST crates; +// String payloads borrow the parser input/arena; currently `&'static [u8]` +// per the rules/mod.rs lifetime-erasure note. // TODO(refactor): re-thread `'i` once `PropertyId<'i>` and the parser arena are real. pub enum SupportsCondition { /// A `not` expression. @@ -30,8 +30,8 @@ pub enum SupportsCondition { Unknown(&'static [u8]), } -// PORT NOTE: Zig used an anonymous inline struct for the `.declaration` payload; -// hoisted to a named type because Rust enum variants cannot carry inherent methods. +// Named payload type for `SupportsCondition::Declaration` (enum variants +// cannot carry inherent methods). pub struct Declaration { /// The property id for the declaration. pub property_id: PropertyId, @@ -44,9 +44,7 @@ pub struct Declaration { impl Declaration { pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `PropertyId` is `Copy`; - // `value: &'static [u8]` is an arena-owned slice → identity copy - // (generics.zig "const strings" rule). + // `PropertyId` is `Copy`; `value` is an arena-owned slice → identity copy. Self { property_id: self.property_id, value: self.value, @@ -56,7 +54,6 @@ impl Declaration { impl Declaration { pub(crate) fn eql(&self, other: &Self) -> bool { - // PORT NOTE: Zig `css.implementEql` field-walk, hand-expanded. // `PropertyId` carries its own tag+prefix `PartialEq` (see // properties_generated.rs `impl PartialEq for PropertyId`); `value` is // byte-slice equality. @@ -74,7 +71,7 @@ impl SupportsCondition { } pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> SupportsCondition { - // PORT NOTE: `css.implementDeepClone` variant-walk (hand-rolled — + // Hand-rolled variant-walk — // `#[derive(DeepClone)]` can't be used while `Selector`/`Unknown` // carry `&'static [u8]`; the blanket `&'bump [u8]` impl doesn't unify // with a fresh `'__bump`). @@ -107,10 +104,9 @@ impl SupportsCondition { // `impl CssHash for PropertyId` then swap to `#[derive(CssHash)]`. pub fn hash(&self, hasher: &mut bun_wyhash::Wyhash) { - // PORT NOTE: Zig `css.implementHash` variant-walk, hand-expanded because - // `#[derive(CssHash)]` would require `PropertyId: CssHash` (it only - // provides `core::hash::Hash`). Semantics match the Zig reflection: - // hash the discriminant, then field-wise structural hash. + // Hand-expanded because `#[derive(CssHash)]` would require + // `PropertyId: CssHash` (it only provides `core::hash::Hash`). + // Hash the discriminant, then field-wise structural hash. use core::hash::{Hash, Hasher}; core::mem::discriminant(self).hash(hasher); match self { @@ -130,10 +126,9 @@ impl SupportsCondition { } pub fn eql(&self, other: &SupportsCondition) -> bool { - // PORT NOTE: Zig `css.implementEql` variant-walk, hand-expanded because - // `#[derive(CssEql)]` would require `PropertyId: CssEql` (it only - // provides the custom tag+prefix `PartialEq`). Semantics match the Zig - // reflection: tag mismatch → false, then field-wise structural eq. + // Hand-expanded because `#[derive(CssEql)]` would require + // `PropertyId: CssEql` (it only provides the custom tag+prefix + // `PartialEq`). Tag mismatch → false, then field-wise structural eq. match (self, other) { (Self::Not(a), Self::Not(b)) => a.eql(b), (Self::And(a), Self::And(b)) | (Self::Or(a), Self::Or(b)) => { @@ -155,10 +150,6 @@ impl crate::generics::CssEql for SupportsCondition { } impl SupportsCondition { - // PORT NOTE: `pub fn deinit` dropped — body only freed Box/Vec payloads which Rust - // drops automatically. Input-slice variants (`Declaration`/`Selector`/`Unknown`) - // were no-ops in Zig as well (arena/input-owned). - fn needs_parens(&self, parent: &SupportsCondition) -> bool { match self { SupportsCondition::Not(_) => true, @@ -233,11 +224,8 @@ impl SupportsCondition { } let name = property_id.name(); - // PORT NOTE: `inline for (css.VendorPrefix.FIELDS) |field| { if @field(prefix, field) ... }` - // iterates the packed-struct bool fields at comptime. VendorPrefix ports to - // bitflags!; iterate the ordered single-bit table directly (same pattern as - // rules/style.rs). The Zig also builds `var p = VendorPrefix{}; @field(p, field) = true;` - // but never reads it — dead store dropped. + // Iterate the ordered single-bit VendorPrefix table directly (same + // pattern as rules/style.rs). dest.write_separated( css::VendorPrefix::FIELDS .iter() @@ -283,12 +271,11 @@ impl SupportsCondition { let mut expected_type: Option = None; let mut conditions: Vec = Vec::new_in(ArenaPtr::new(input.arena())); - // PORT NOTE: Zig used std.ArrayHashMap with an inline custom hash/eql context; - // SeenDeclKey below carries equivalent Hash/Eq impls. + // `SeenDeclKey` below carries the custom Hash/Eq impls for this map. let mut seen_declarations: ArrayHashMap = ArrayHashMap::new(); loop { - // PORT NOTE: reshaped for borrowck — Zig threaded `*?i32` through a + // Reshaped for borrowck — Zig threaded `*?i32` through a // local `Closure` struct (LIFETIMES.tsv: BORROW_PARAM); a Rust closure // capturing `&mut expected_type` is the direct equivalent. let _condition = @@ -421,16 +408,14 @@ impl SupportsCondition { } } -// PORT NOTE: Zig `SeenDeclKey` was a tuple struct with an inline hash-map context -// providing custom hash/eql. Ported as a tuple struct with manual Hash/PartialEq -// matching the Zig context exactly (wrapping_add of string hash and enum int). +// Dedup key for `@supports` declaration conditions; the manual Hash/PartialEq +// below match the Zig hash-map context (wrapping_add of string hash and enum int). struct SeenDeclKey(PropertyId, &'static [u8]); impl core::hash::Hash for SeenDeclKey { fn hash(&self, state: &mut H) { - // TODO(port): Zig used std.array_hash_map.hashString (wyhash, 32-bit) +% @intFromEnum. - // bun_collections::ArrayHashMap is wyhash-backed; confirm hasher parity. - // PORT NOTE: hash_string returns u32 directly (mirrors Zig hashString) — no narrowing cast. + // wyhash of the value bytes +% the property-id tag, mirroring the Zig + // hash-map context. `hash_string` returns u32 directly — no narrowing cast. let h: u32 = bun_collections::array_hash_map::hash_string(self.1); state.write_u32(h.wrapping_add(self.0.tag() as u32)); } @@ -494,7 +479,6 @@ impl SupportsRule { where R: css::generics::DeepClone<'bump>, { - // PORT NOTE: `css.implementDeepClone` field-walk. Self { condition: self.condition.deep_clone(bump), rules: self.rules.deep_clone(bump), diff --git a/src/css/rules/unknown.rs b/src/css/rules/unknown.rs index a8a934d54145..cccc86d93d49 100644 --- a/src/css/rules/unknown.rs +++ b/src/css/rules/unknown.rs @@ -40,9 +40,8 @@ impl UnknownAtRule { pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { use crate::generics::DeepClone as _; - // PORT NOTE: `css.implementDeepClone` field-walk. `name: &'static [u8]` - // is an arena-owned slice → identity copy (generics.zig "const strings" - // rule); `TokenList` carries `#[derive(DeepClone)]`. + // `name` is an arena-owned slice → identity copy (generics.zig + // "const strings" rule); `TokenList` carries `#[derive(DeepClone)]`. Self { name: self.name, prelude: self.prelude.deep_clone(bump), diff --git a/src/css/rules/viewport.rs b/src/css/rules/viewport.rs index 445fe43d4dce..9a3137fefb2c 100644 --- a/src/css/rules/viewport.rs +++ b/src/css/rules/viewport.rs @@ -6,8 +6,8 @@ pub struct ViewportRule { /// The vendor prefix for this rule, e.g. `@-ms-viewport`. pub vendor_prefix: VendorPrefix, /// The declarations within the `@viewport` rule. - // PORT NOTE: `DeclarationBlock<'bump>` borrows the parser arena; lifetime - // erased to `'static` here per the rules/mod.rs `CssRule` PORT NOTE + // `DeclarationBlock<'bump>` borrows the parser arena; the lifetime is + // erased to `'static` here, matching `CssRule` in rules/mod.rs // (the `'bump` arena lifetime is re-threaded crate-wide in one pass). pub declarations: DeclarationBlock<'static>, /// The location of the rule in the source file. @@ -27,8 +27,6 @@ impl ViewportRule { impl ViewportRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `VendorPrefix` is a - // `Copy` bitflag (generics.zig "simple copy types" → identity). Self { vendor_prefix: self.vendor_prefix, declarations: super::dc::decl_block_static(&self.declarations, bump), diff --git a/src/css/selectors/builder.rs b/src/css/selectors/builder.rs index 35969127a188..8b7b7ba645fc 100644 --- a/src/css/selectors/builder.rs +++ b/src/css/selectors/builder.rs @@ -86,9 +86,8 @@ impl SelectorBuilder { /// by the given combinator. #[inline] pub(crate) fn push_combinator(&mut self, combinator: Combinator) { - // PORT NOTE: `SmallList::append/insert` no longer take an arena — - // it owns its spill buffer (global arena). The `bump` field is - // retained for `BuildResult.components` (BumpVec) only. + // `SmallList` owns its spill buffer (global arena); the `bump` field + // is retained for `BuildResult.components` (BumpVec) only. self.combinators.append((combinator, self.current_len)); self.current_len = 0; } @@ -105,9 +104,8 @@ impl SelectorBuilder { self.simple_selectors.insert(0, GenericComponent::Nesting); } - // PORT NOTE: Zig `deinit` only freed `simple_selectors` and `combinators`. - // In Rust, `SmallList` owns its spill buffer and frees on `Drop`, so no - // explicit `Drop` impl is needed here. + // `SmallList` owns its spill buffer and frees on `Drop`, so no explicit + // `Drop` impl is needed here. /// Consumes the builder, producing a Selector. /// @@ -129,10 +127,9 @@ impl SelectorBuilder { if parsed_part { flags |= SelectorFlags::HAS_PART; } - // `build_with_specificity_and_flags()` will - // PORT NOTE: Zig had `defer this.deinit()` here to free SmallList capacity - // after building. In Rust, `Drop` on `SelectorBuilder` handles this when the - // builder goes out of scope; the call below already drains the contents. + // `build_with_specificity_and_flags()` drains the contents; `Drop` on + // `SelectorBuilder` frees the SmallList capacity when the builder goes + // out of scope. self.build_with_specificity_and_flags(SpecificityAndFlags { specificity, flags }) } @@ -151,8 +148,7 @@ impl SelectorBuilder { &mut self, spec: SpecificityAndFlags, ) -> BuildResult { - // PORT NOTE: reshaped for borrowck — capture combinators.len() - // before borrowing simple_selectors.slice(). + // Capture combinators.len() before borrowing simple_selectors.slice(). let combinators_len = self.combinators.len(); let (rest, current) = split_from_end::>( @@ -170,7 +166,6 @@ impl SelectorBuilder { loop { if current_simple_selectors_i < current_simple_selectors.len() { - // PORT NOTE: Zig copies the component by value here (struct copy). // `GenericComponent` is not `Copy`; we bitwise-move it out // via `ptr::read` — sound because every element of // `simple_selectors` is consumed exactly once across the loop, diff --git a/src/css/selectors/parser.rs b/src/css/selectors/parser.rs index 49bffbe820d8..931e8dcf6702 100644 --- a/src/css/selectors/parser.rs +++ b/src/css/selectors/parser.rs @@ -23,11 +23,11 @@ pub use bun_css::Printer as PrinterRe; // re-export parity (Printer/PrintErr wer /// `css::Result` — the CSS parser result type (`Ok(T)` / `Err(css::ParseError)`). type CResult = css::Result; -// TODO(port): arena lifetimes. The Zig code threads `parser.arena` / `input.arena()` -// (a bump arena) through every allocation. The Rust port uses `Vec`/`Box` and a `Str` alias for -// source-borrowed byte slices; re-thread `'bump` and switch to -// `bun_alloc::ArenaVec<'bump, T>` / `&'bump [u8]` per PORTING.md §Allocators (AST crates). -// PERF(port): was arena bulk-free — profile if it shows up on a hot path. +// Arena lifetimes: the Zig code threads `parser.arena` / `input.arena()` (a bump +// arena) through every allocation; the Rust port uses `Vec`/`Box` and a `Str` +// alias for source-borrowed byte slices until the crate-wide `'bump` re-threading +// (`bun_alloc::ArenaVec<'bump, T>` / `&'bump [u8]` per PORTING.md §Allocators +// (AST crates)) lands. // // NOTE: `Str` is `&'static [u8]` here (not `crate::Str = *const [u8]`) to match // `crate::Token`'s payload shape (`Token::Ident(&'static [u8])` etc.) — every @@ -129,7 +129,7 @@ pub fn valid_selector_impl() { /// The `SelectorImpl` shape (Zig validated via `ValidSelectorImpl`). Implemented /// by `impl_::Selectors` in `bun_css::selector::impl_`. -// PORT NOTE: `PartialEq + Clone` bounds dropped — the concrete assoc types +// `PartialEq + Clone` bounds dropped — the concrete assoc types // (`values::ident::{Ident,IdentOrRef}`, `*const [u8]`) implement structural // equality via the `CssEql` protocol (`generics::implement_eql`), not // `core::cmp::PartialEq`. Every `eql`/`deep_clone`/`hash` callsite in this @@ -152,7 +152,7 @@ pub trait SelectorImpl: Sized { /// Constrained `SelectorImpl` with the concrete assoc-type bundle Bun uses. /// -/// PORT NOTE: in Zig the `parse_*` functions were `comptime Impl: type` generics +/// In Zig the `parse_*` functions were `comptime Impl: type` generics /// but every body assumed the concrete `selector.impl.Selectors` shapes (it was /// the only instantiation). Rust can't see through the open `Impl::LocalName` /// to `Ident`, so the parse functions bound on this sub-trait instead — the @@ -328,7 +328,7 @@ pub mod attrs { }, } - // PORT NOTE: implemented for the concrete `AttrValue = css::CSSString` + // Implemented for the concrete `AttrValue = css::CSSString` // (= `*const [u8]`) only — the sole `BunSelectorImpl` instantiation. impl ParsedAttrSelectorOperation { pub fn deep_clone(&self) -> Self { @@ -559,7 +559,11 @@ fn compute_simple_selector_specificity( // Does not affect specificity } C::Nesting => { - // TODO + // No specificity contribution here, matching the Zig source — + // which leaves this as a bare open TODO upstream. Whether `&` + // should contribute specificity at this point is unresolved + // there; nesting substitution happens at print time, after + // specificity is computed. } } } @@ -623,7 +627,7 @@ fn parse_selector( any_whitespace = true; continue; } - // PORT NOTE: `Token::Delim` carries `u32` codepoint; cast to + // `Token::Delim` carries `u32` codepoint; cast to // `u8` for ASCII match (all CSS combinator delims are ASCII). Token::Delim(d) => match u8::try_from(*d).ok() { Some(b'>') => { @@ -897,7 +901,7 @@ pub fn valid_selector_parser() { pub use css::css_properties::text::Direction; /// A pseudo class. -// PORT NOTE: `PartialEq` derive dropped — `Local`/`Global` carry +// `PartialEq` derive dropped — `Local`/`Global` carry // `Box` and `CustomFunction` carries `TokenList`, neither of which // implements `PartialEq`. Equality goes through `eql()` (CssEql protocol). #[derive(Clone, CssEql, CssHash)] @@ -1078,12 +1082,11 @@ impl PseudoClass { } pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // PERF(alloc): I don't like making these little allocations - // PORT NOTE: Zig builds a fresh `Printer` over an allocating writer, - // calls `serialize::serializePseudoClass`, then writes the buffer to - // `dest`. The buffered indirection only matters for length-dependent + // Zig builds a fresh `Printer` over an allocating writer, calls + // `serialize::serializePseudoClass`, then writes the buffer to `dest`. + // The buffered indirection only matters for length-dependent // minification decisions made by callers (none here), so write - // directly to `dest` until `Printer::new_buffered` lands. + // directly to `dest`. serialize::serialize_pseudo_class(self, dest, None) } @@ -1351,9 +1354,6 @@ impl<'a> SelectorParser<'a> { ) -> CResult { // @compileError(css.todo_stuff.match_ignore_ascii_case); let pseudo_class: PseudoClass = 'pseudo_class: { - // TODO(port): phf custom hasher — Zig used `ComptimeStringMap.getAnyCase` - // (ASCII case-insensitive). Generate a case-folded phf or use a - // `match` over the lowercased name. if let Some(pseudo) = lookup_non_ts_pseudo_class(name) { break 'pseudo_class pseudo; } @@ -1386,7 +1386,7 @@ impl<'a> SelectorParser<'a> { ) -> CResult { let pseudo_class = crate::match_ignore_ascii_case! { name, { b"lang" => { - // PORT NOTE: `expect_ident_or_string` returns `&'_ [u8]` + // `expect_ident_or_string` returns `&'_ [u8]` // (lifetime-tied to `&mut self`), which can't satisfy // `parse_comma_separated`'s HRTB. Clone the token to extract // the underlying `&'static [u8]` payload directly. @@ -1466,7 +1466,6 @@ impl<'a> SelectorParser<'a> { loc: css::SourceLocation, name: Str, ) -> CResult { - // TODO(port): phf custom hasher — Zig used `ComptimeStringMap.getCaseInsensitiveWithEql`. let pseudo_element = lookup_pseudo_element(name).unwrap_or_else(|| { if !strings::starts_with_char(name, b'-') { self.options.warn(&loc.new_custom_error( @@ -1649,7 +1648,9 @@ impl GenericSelectorList { /// `DebugFmt` wrapper — implements `Display` over a borrowed list (debug builds only). pub struct SelectorListDebugFmt<'a, Impl: SelectorImpl>(pub &'a GenericSelectorList); -impl<'a, Impl: BunSelectorImpl> fmt::Display for SelectorListDebugFmt<'a, Impl> { +// Concrete `impl_::Selectors` only — `sel.debug()` formatting requires the +// concrete `Display` impl on `SelectorDebugFmt` (see the comment there). +impl<'a> fmt::Display for SelectorListDebugFmt<'a, impl_::Selectors> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if !cfg!(debug_assertions) { return Ok(()); @@ -1757,11 +1758,10 @@ impl GenericSelectorList { nesting_requirement: NestingRequirement, ) -> CResult { let original_state = *state; - // TODO: Think about deinitialization in error cases let mut values: SmallList, 1> = SmallList::default(); loop { - // PORT NOTE: reshaped for borrowck — Zig used a `Closure` struct capturing + // Reshaped for borrowck — Zig used a `Closure` struct capturing // `&mut state` and `&mut parser`; Rust captures a local `saw_nesting` flag // and applies it to `state` after the closure returns (no raw `*mut`). let mut saw_nesting = false; @@ -1806,8 +1806,8 @@ impl GenericSelectorList { } } - // TODO: this looks exactly the same as `parse_with_state()` except it uses - // `parse_relative_selector()` instead of `parse_selector()` + // Same shape as `parse_with_state()` but parses each item with + // `parse_relative_selector()` instead of `parse_selector()`. pub fn parse_relative_with_state( parser: &mut SelectorParser, input: &mut CssParser, @@ -1816,11 +1816,10 @@ impl GenericSelectorList { nesting_requirement: NestingRequirement, ) -> CResult { let original_state = *state; - // TODO: Think about deinitialization in error cases let mut values: SmallList, 1> = SmallList::default(); loop { - // PORT NOTE: reshaped for borrowck — capture a local flag instead of a + // Reshaped for borrowck — capture a local flag instead of a // raw `*mut SelectorParsingState`, then fold into `state` after return. let mut saw_nesting = false; let selector = @@ -1926,16 +1925,47 @@ pub struct GenericSelector { pub struct SelectorDebugFmt<'a, Impl: SelectorImpl>(pub &'a GenericSelector); -impl<'a, Impl: SelectorImpl> fmt::Display for SelectorDebugFmt<'a, Impl> { +// Implemented for the concrete `impl_::Selectors` only (the crate's sole +// `SelectorImpl`): `tocss_servo::to_css_selector` serializes the concrete +// `Selector`, not a generic `GenericSelector`. +impl<'a> fmt::Display for SelectorDebugFmt<'a, impl_::Selectors> { + // `IN_DEBUG_FMT` only exists under `#[cfg(debug_assertions)]` + // (printer.rs), so the whole serialization body is compiled out of + // release builds rather than gated at runtime. + #[cfg(not(debug_assertions))] + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + Ok(()) + } + + #[cfg(debug_assertions)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if !cfg!(debug_assertions) { - return Ok(()); + // Mirrors the Zig `DebugFmt.format`: serialize through a buffered + // `Printer` over an empty symbol map. `IN_DEBUG_FMT` makes + // `lookup_ident_or_ref` fall back to `debug_ident` instead of + // consulting the (empty) symbol table. + let arena = Bump::new(); + let mut buf: Vec = Vec::new(); + let symbols = bun_ast::symbol::Map::default(); + // Unwind-safe equivalent of the Zig `defer P.in_debug_fmt = false`: + // Debug formatting commonly runs while building panic messages, so a + // panic inside `to_css_selector` must not leak the flag thread-wide. + struct InDebugFmtGuard; + impl Drop for InDebugFmtGuard { + fn drop(&mut self) { + crate::printer::IN_DEBUG_FMT.with(|flag| flag.set(false)); + } + } + crate::printer::IN_DEBUG_FMT.with(|flag| flag.set(true)); + let result = { + let _guard = InDebugFmtGuard; + let mut printer = Printer::new_buffered(&arena, &mut buf, None, None, &symbols); + css::selector::tocss_servo::to_css_selector(self.0, &mut printer) + }; + write!(f, "Selector(")?; + match result { + Ok(()) => write!(f, "{}", bstr::BStr::new(&buf)), + Err(e) => writeln!(f, "", e.name()), } - // TODO(port): the Zig builds a fresh `Printer` and calls - // `tocss_servo::to_css_selector` into a buffer, then writes the buffer. - // blocked_on: `Printer::new_buffered` + `SymbolMap::default` (debug- - // only path; serialization body lives in `selector::tocss_servo`). - write!(f, "Selector(<{} components>)", self.0.components.len()) } } @@ -2233,7 +2263,7 @@ impl GenericComponent { } pub fn deep_clone(&self) -> Self { - // PORT NOTE: hand-written variant-walk (Zig `implementDeepClone`). + // Hand-written variant-walk (Zig `implementDeepClone`). // Every borrowed payload (`Str`, `Ident.v`, `IdentOrRef`) is an // arena-static identity copy; owning containers (`Vec`/`Box`) recurse. use GenericComponent as C; @@ -2567,14 +2597,41 @@ impl CssHash for GenericComponent { impl fmt::Display for GenericComponent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // TODO(port): Zig matches on a few variants and falls through to `@tagName`. - // Rust enums need `strum::IntoStaticStr` for the tag name. + // Mirrors the Zig `format`: a few variants get detail, the rest print + // their tag name (Zig `@tagName`, hand-expanded). `BunSelectorImpl` + // pins `PseudoElement`/`LocalIdentifier` to the concrete types, whose + // `Display` impls stand in for the Zig payload `format`s. match self { Self::LocalName(ln) => write!(f, "local_name={}", bstr::BStr::new(ln.name.v())), Self::Combinator(c) => write!(f, "combinator='{}'", c), - Self::PseudoElement(_) => write!(f, "pseudo_element=<..>"), - Self::Class(_) => write!(f, "class=<..>"), - _ => write!(f, ""), + Self::PseudoElement(pe) => write!(f, "pseudo_element={}", pe), + Self::Class(c) => write!(f, "class={}", c), + Self::ExplicitAnyNamespace => f.write_str("explicit_any_namespace"), + Self::ExplicitNoNamespace => f.write_str("explicit_no_namespace"), + Self::DefaultNamespace(_) => f.write_str("default_namespace"), + Self::Namespace { .. } => f.write_str("namespace"), + Self::ExplicitUniversalType => f.write_str("explicit_universal_type"), + Self::Id(_) => f.write_str("id"), + Self::AttributeInNoNamespaceExists { .. } => { + f.write_str("attribute_in_no_namespace_exists") + } + Self::AttributeInNoNamespace { .. } => f.write_str("attribute_in_no_namespace"), + Self::AttributeOther(_) => f.write_str("attribute_other"), + Self::Negation(_) => f.write_str("negation"), + Self::Root => f.write_str("root"), + Self::Empty => f.write_str("empty"), + Self::Scope => f.write_str("scope"), + Self::Nth(_) => f.write_str("nth"), + Self::NthOf(_) => f.write_str("nth_of"), + Self::NonTsPseudoClass(_) => f.write_str("non_ts_pseudo_class"), + Self::Slotted(_) => f.write_str("slotted"), + Self::Part(_) => f.write_str("part"), + Self::Host(_) => f.write_str("host"), + Self::Where(_) => f.write_str("where"), + Self::Is(_) => f.write_str("is"), + Self::Any { .. } => f.write_str("any"), + Self::Has(_) => f.write_str("has"), + Self::Nesting => f.write_str("nesting"), } } } @@ -2984,7 +3041,7 @@ impl SelectorParseErrorKind { } pub fn into_selector_error(self) -> css::SelectorError { - // PORT NOTE: `error.rs::SelectorError` variants are snake_case + // `error.rs::SelectorError` variants are snake_case // (`#[allow(non_camel_case_types)]` Zig-tagName parity). use SelectorParseErrorKind as K; use css::SelectorError as S; @@ -3044,7 +3101,7 @@ pub enum SimpleSelectorParseResult { } /// A pseudo element. -// PORT NOTE: see PseudoClass — `PartialEq` derive dropped (Box/TokenList). +// See PseudoClass — `PartialEq` derive dropped (Box/TokenList). #[derive(Clone, CssEql, CssHash)] pub enum PseudoElement { /// The [::after](https://drafts.csswg.org/css-pseudo-4/#selectordef-after) pseudo element. @@ -3201,17 +3258,38 @@ impl PseudoElement { } pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // PERF(alloc): I don't like making small allocations here for the string. - // PORT NOTE: see PseudoClass::to_css — write directly until - // `Printer::new_buffered` lands. + // See PseudoClass::to_css — write directly to `dest`; no caller makes + // length-dependent minification decisions here. serialize::serialize_pseudo_element(self, dest, None) } } impl fmt::Display for PseudoElement { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // TODO(port): @tagName — needs strum::IntoStaticStr. - write!(f, "") + // Zig `@tagName`, hand-expanded. + f.write_str(match self { + Self::After => "after", + Self::Before => "before", + Self::FirstLine => "first_line", + Self::FirstLetter => "first_letter", + Self::Selection(_) => "selection", + Self::Placeholder(_) => "placeholder", + Self::Marker => "marker", + Self::Backdrop(_) => "backdrop", + Self::FileSelectorButton(_) => "file_selector_button", + Self::WebkitScrollbar(_) => "webkit_scrollbar", + Self::Cue => "cue", + Self::CueRegion => "cue_region", + Self::CueFunction { .. } => "cue_function", + Self::CueRegionFunction { .. } => "cue_region_function", + Self::ViewTransition => "view_transition", + Self::ViewTransitionGroup { .. } => "view_transition_group", + Self::ViewTransitionImagePair { .. } => "view_transition_image_pair", + Self::ViewTransitionOld { .. } => "view_transition_old", + Self::ViewTransitionNew { .. } => "view_transition_new", + Self::Custom { .. } => "custom", + Self::CustomFunction { .. } => "custom_function", + }) } } @@ -3464,8 +3542,6 @@ pub fn parse_one_simple_selector( input.parse_nested_block(|i: &mut CssParser| { parser.parse_functional_pseudo_element(name, i) })? - // TODO(port): `Impl::PseudoElement` is `PseudoElement` for the concrete - // `impl_::Selectors`; the generic path would need a `From`/trait bound. } else { parser.parse_pseudo_element(location, name)? }; @@ -3618,7 +3694,7 @@ pub fn parse_attribute_selector( )); }; - // PORT NOTE: `expect_ident_or_string` returns `&'_ [u8]` (lifetime-tied to + // `expect_ident_or_string` returns `&'_ [u8]` (lifetime-tied to // `&mut *input`); `parse_attribute_flags(input)` below needs `input` again. // Clone the token so the borrow is released before we re-borrow. let value_str: Str = { @@ -3776,8 +3852,6 @@ pub fn parse_functional_pseudo_class( let result = parser.parse_non_ts_functional_pseudo_class(name, input)?; Ok(GenericComponent::NonTsPseudoClass(result)) - // TODO(port): `Impl::NonTSPseudoClass` is `PseudoClass` for the concrete impl; - // generic path would need a `From` bound. } pub fn parse_simple_pseudo_class( @@ -3930,7 +4004,7 @@ where let selector_slice = inner.into_boxed_selectors(); - // PORT NOTE: Zig threaded extra `args_` through an ArgsTuple to `func`; in Rust + // Zig threaded extra `args_` through an ArgsTuple to `func`; in Rust // the closure captures extras directly (e.g. `prefix` for `:any()`). let result = func(selector_slice); @@ -4069,7 +4143,7 @@ pub fn parse_qualified_name( ); } } - // PORT NOTE: reshaped for borrowck — clone token before reset. + // Reshaped for borrowck — clone token before reset. let result_cloned = result.cloned(); input.reset(&after_star); if in_attr_selector { @@ -4286,7 +4360,7 @@ pub enum ViewTransitionPartName { impl ViewTransitionPartName { pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // PORT NOTE: `CustomIdentFns::to_css` is ``-gated on + // `CustomIdentFns::to_css` is CSS-modules-gated via // `Printer::{css_module,write_ident}`; inline the // `write_ident(v, false)` body (CSS-modules custom-ident scoping is a // serializer concern, not a grammar concern — the gated impl just diff --git a/src/css/selectors/selector.rs b/src/css/selectors/selector.rs index 1f5068221a90..aee0feb611b1 100644 --- a/src/css/selectors/selector.rs +++ b/src/css/selectors/selector.rs @@ -230,9 +230,8 @@ fn downlevel_dir<'bump>(bump: &'bump Bump, dir: parser::Direction, targets: &Tar } fn lang_list_to_selectors<'bump>(_bump: &'bump Bump, langs: &[&'static [u8]]) -> Box<[Selector]> { - // PORT NOTE: Zig returned `[]Selector` (mutable arena slice). Here - // `Component::Is`/`Negation` carry `Box<[Selector]>`; could re-thread - // `&'bump [Selector]` once the arena lifetime is plumbed. + // `Component::Is`/`Negation` carry `Box<[Selector]>`; this could become + // `&'bump [Selector]` once the arena lifetime is plumbed through. let mut selectors: Vec = Vec::with_capacity(langs.len()); for lang in langs { selectors.push(Selector::from_component(Component::NonTsPseudoClass( @@ -545,8 +544,8 @@ fn is_selector_unused( for component in selector.components.iter() { match component { Component::Class(ident) | Component::Id(ident) => { - // PORT NOTE: `IdentOrRef::as_original_string` is - // ``-gated (blocked_on bun_ast::symbol::List::at + // `IdentOrRef::as_original_string` is + // gated (blocked_on bun_ast::symbol::List::at // + Symbol.original_name). Inline the ident arm; the ref arm // (CSS-modules symbol-table lookup) is unreachable until // `Parser::add_symbol_for_name` un-gates (see @@ -559,9 +558,8 @@ fn is_selector_unused( continue; // blocked_on: as_original_string ref arm } }; - // PORT NOTE: Zig `unused_symbols.contains(actual_ident)` — - // adapted lookup to compare the borrowed `&[u8]` against - // owned `Box<[u8]>` keys without allocating. + // Look up the borrowed `&[u8]` against the map's owned + // `Box<[u8]>` keys without allocating. struct SliceAdapter; impl bun_collections::array_hash_map::ArrayHashAdapter<[u8], Box<[u8]>> for SliceAdapter { #[inline] @@ -865,9 +863,8 @@ pub mod serialize { let mut id: Vec = Vec::new(); let _ = css::serializer::serialize_identifier(value_bytes, &mut id); - // PORT NOTE: Zig routed through `css.to_css.string(CSSString, ...)`, which - // dispatches to `CSSStringFns.toCss` → `serialize_string`. Inline that here - // since `CssString` (`*const [u8]`) does not implement `generic::ToCss`. + // `serialize_string` is called directly here since `CssString` + // (`*const [u8]`) does not implement `generic::ToCss`. let mut s: Vec = Vec::new(); let _ = css::serializer::serialize_string(value_bytes, &mut s); diff --git a/src/css/small_list.rs b/src/css/small_list.rs index f1b3c57201d2..d4fbf7989211 100644 --- a/src/css/small_list.rs +++ b/src/css/small_list.rs @@ -75,7 +75,6 @@ pub mod fallbacks_gated { use crate::css_parser as css; use crate::properties::text::TextShadow; - // TODO(port): trait bound placeholder — any T with getImage()/withImage()/getFallback()/getNecessaryFallbacks() pub fn get_fallbacks_image( this: &mut SmallList, arena: &bun_alloc::Arena, diff --git a/src/css/sourcemap.rs b/src/css/sourcemap.rs index 1f217c1c8585..88e391dfa8e0 100644 --- a/src/css/sourcemap.rs +++ b/src/css/sourcemap.rs @@ -1,11 +1,17 @@ pub struct SourceMap { - // TODO(port): []const u8 struct field in CSS (arena crate) — using raw slice ptr per PORTING.md; revisit ownership + // Arena-owned `[]const u8` erased to a raw slice pointer per PORTING.md + // §Allocators (AST crates); becomes `&'bump [u8]` once the CSS crate + // threads the arena lifetime. pub project_root: *const [u8], pub inner: SourceMapInner, } pub struct SourceMapInner { // PERF(port): ArrayListUnmanaged in CSS arena crate — using Vec; may want bun_alloc::ArenaVec<'bump, T> if hot + // The `*const [u8]` elements of `sources`/`sources_content`/`names` are + // arena-owned slices erased to raw pointers per PORTING.md §Allocators + // (AST crates) — never dereferenced after the arena resets; they become + // `&'bump [u8]` once the CSS crate threads the arena lifetime. pub sources: Vec<*const [u8]>, pub sources_content: Vec<*const [u8]>, pub names: Vec<*const [u8]>, diff --git a/src/css/targets.rs b/src/css/targets.rs index 15a73a4404f4..221cadd0c343 100644 --- a/src/css/targets.rs +++ b/src/css/targets.rs @@ -77,8 +77,8 @@ impl Targets { { let mut browsers = Browsers::default(); let mut has_any = false; - // PORT NOTE: Zig used `inline for (std.meta.fields(Browsers))` reflection. - // Expanded manually per field. + // Zig used `inline for (std.meta.fields(Browsers))` reflection; + // expanded manually per field. macro_rules! check_field { ($field:ident, $env:literal) => { if let Some(val) = bun_core::getenv_z_any_case(bun_core::zstr!($env)) { @@ -216,10 +216,13 @@ impl Browsers { // Zig: `try std.fmt.parseInt(u16, number_part, 10)` — propagates // error.InvalidCharacter / error.Overflow. Preserve the tag for // @errorName snapshot compat (do NOT collapse to UnsupportedCSSTarget). - // TODO(port): narrow error set (InvalidCharacter | Overflow) - let year = strings::parse_int::(number_part, 10) - .ok() - .ok_or_else(|| bun_core::err!("InvalidCharacter"))?; + let year = + strings::parse_int::(number_part, 10).map_err(|e| match e { + strings::ParseIntError::Overflow => bun_core::err!("Overflow"), + strings::ParseIntError::InvalidCharacter => { + bun_core::err!("InvalidCharacter") + } + })?; match year { // https://caniuse.com/?search=es2015 2015 => { @@ -391,7 +394,7 @@ impl Browsers { let version: u32 = ((major as u32) << 16) | ((minor as u32) << 8); // Zig: `switch (browser.?) { inline else => |b| @field(browsers, @tagName(b)) ... }` - // PORT NOTE: reflection expanded into a direct match yielding a field ref. + // — reflection expanded into a direct match yielding a field ref. let slot: &mut Option = match browser { Browser::Chrome => &mut browsers.chrome, Browser::Edge => &mut browsers.edge, diff --git a/src/css/values/alpha.rs b/src/css/values/alpha.rs index 2113b195d0fa..9d17a4d5c825 100644 --- a/src/css/values/alpha.rs +++ b/src/css/values/alpha.rs @@ -13,8 +13,6 @@ pub struct AlphaValue { impl AlphaValue { pub(crate) fn parse(input: &mut Parser) -> Result { - // For some reason NumberOrPercentage.parse makes zls crash, using this instead. - // PORT NOTE: the Zig used `@call(.auto, @field(...))` as a zls workaround; direct call in Rust. let val: NumberOrPercentage = NumberOrPercentage::parse(input)?; let final_ = match val { NumberOrPercentage::Percentage(percent) => AlphaValue { v: percent.v }, @@ -26,9 +24,14 @@ impl AlphaValue { pub(crate) fn to_css(self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { CSSNumberFns::to_css(self.v, dest) } +} - // TODO(port): css.implementHash (comptime field reflection) — wires once - // generics::CssHash blanket impl covers f32-payload structs. +impl crate::generics::CssHash for AlphaValue { + /// Zig `css.implementHash` — field-wise: hash the single `f32` payload. + #[inline] + fn hash(&self, hasher: &mut crate::generics::Wyhash) { + crate::generics::CssHash::hash(&self.v, hasher); + } } // ported from: src/css/values/alpha.zig diff --git a/src/css/values/angle.rs b/src/css/values/angle.rs index d200a55940f1..2ce1655092de 100644 --- a/src/css/values/angle.rs +++ b/src/css/values/angle.rs @@ -198,7 +198,6 @@ impl Angle { pub(crate) fn op(self, other: Angle, ctx: C, op_fn: fn(C, f32, f32) -> f32) -> Angle { // PERF: not sure if this is faster - // PORT NOTE: reshaped for borrowck — Zig used packed-tag bit-twiddling switch; Rust match on (tag, tag) is equivalent. match (self, other) { (Angle::Deg(a), Angle::Deg(b)) => Angle::Deg(op_fn(ctx, a, b)), (Angle::Rad(a), Angle::Rad(b)) => Angle::Rad(op_fn(ctx, a, b)), diff --git a/src/css/values/calc.rs b/src/css/values/calc.rs index 3c15de0172e4..820a44a36e24 100644 --- a/src/css/values/calc.rs +++ b/src/css/values/calc.rs @@ -40,10 +40,9 @@ pub enum CalcUnit { impl CalcUnit { /// Zig: `bun.ComptimeEnumMap(CalcUnit).getAnyCase(f)` - // TODO(port): phf custom hasher — case-insensitive lookup over &[u8]. pub fn get_any_case(f: &[u8]) -> Option { // PERF(port): Zig used a comptime perfect hash; this is a linear match on a - // stack-lowercased byte slice. TODO(perf): use a phf_map! over &[u8]. + // stack-lowercased byte slice. Profile before swapping in a phf. // §Strings: source bytes are &[u8], never &str/String — no from_utf8/to_ascii_lowercase(). let (buf, len) = bun_core::strings::ascii_lowercase_buf::<5>(f)?; match &buf[..len] { @@ -263,13 +262,11 @@ impl Calc { lhs.mul_f32(rhs) } - // TODO: addValueOwned pub fn add_value(lhs: V, rhs: V) -> V { // Zig: `f32 => lhs + rhs, else => lhs.addInternal(...)` — folded into trait. lhs.add_internal(rhs) } - // TODO: intoValueOwned pub fn into_value(self, input: &mut css::Parser) -> CssResult { // Zig comptime type switch on V → trait method `V::from_calc`. V::from_calc(self, input) @@ -280,11 +277,10 @@ impl Calc { val.into_calc() } - // TODO: change to addOwned() pub fn add(self, rhs: Self, input: &mut css::Parser) -> CssResult { if let (Calc::Value(_), Calc::Value(_)) = (&self, &rhs) { // PERF: we can reuse the allocation here - // PORT NOTE: reshaped for borrowck — clone out of boxes then drop originals. + // Reshaped for borrowck — clone out of boxes then drop originals. let (a, b) = match (self, rhs) { (Calc::Value(a), Calc::Value(b)) => (*a, *b), _ => unreachable!(), @@ -323,8 +319,6 @@ impl Calc { Ok(Self::into_calc(Self::add_value(this_value, rhs_value))) } - // TODO: users of this and `parseWith` don't need the pointer and often throwaway heap allocated values immediately - // use temp arena or something? pub fn parse(input: &mut css::Parser) -> CssResult { fn parse_with_fn(_: (), _: &[u8]) -> Option> { None @@ -338,7 +332,7 @@ impl Calc { parse_ident: F, ) -> CssResult { let location = input.current_source_location(); - // PORT NOTE: clone the token before reborrowing `input` so the function + // Clone the token before reborrowing `input` so the function // name slice is owned by the cloned `Token` (whose payload already // carries the parser's arena lifetime) instead of being laundered to // `'static` here. @@ -353,7 +347,7 @@ impl Calc { other => return Err(location.new_unexpected_token_error(other)), }; - // PORT NOTE: Zig used explicit `Closure` structs because Zig lacks closures. + // Zig used explicit `Closure` structs because Zig lacks closures. // Rust closures capture `ctx` + `parse_ident` directly. match unit { CalcUnit::Calc => { @@ -476,9 +470,14 @@ impl Calc { i, (), |_, a, b| { - // return ((a % b) + b) % b; - // TODO(port): Zig used nested `@mod`; using Rust `%` per the commented - // formula. Verify edge cases (negative b, NaN). + // Zig: `@mod((@mod(a, b) + b), b)` — floored modulo (result takes + // the sign of the divisor). `((a % b) + b) % b` over Rust's + // truncated `%` agrees in sign and value for NaN/inf and ordinary + // finite inputs, but the two formulas apply different rounding + // sequences, so results can differ near exact multiples of the + // divisor (e.g. f32 `mod(-3e-8, 1)`: this yields 0.99999994 where + // Zig's double-wrap rounds `0.99999994 + 1.0` to 2.0 and returns + // 0.0 — a divergence of ~|b| at the wrap-around boundary). ((a % b) + b) % b }, |_, a, b| MathFunction::Mod { @@ -724,7 +723,7 @@ impl Calc { } let location = input.current_source_location(); - // PORT NOTE: reshaped for borrowck — clone the next token inside the + // Reshaped for borrowck — clone the next token inside the // try-parse so the ident slice is owned by the cloned `Token` rather // than laundered to `'static` from the `&mut Parser` borrow. if let Ok(ident) = input.try_parse(|p| { @@ -764,7 +763,7 @@ impl Calc { }; input.parse_nested_block(|i| { - // PORT NOTE: Zig wrapped `parse_ident` to project `Calc::Number` into + // Zig wrapped `parse_ident` to project `Calc::Number` into // `Calc::Number`. Rust closure does the same. let parse_ident_fn = |_self: (), ident: &[u8]| -> Option> { let v = parse_ident(ctx, ident)?; @@ -774,8 +773,9 @@ impl Calc { None } }; - // TODO(port): Zig passed `&closure` (a *@This()) as ctx; here we use `()` and - // capture `ctx` via the outer closure. Verify `Calc::parse_sum` signature. + // Zig passed `&closure` (a *@This()) as ctx; here ctx is `()` and the + // outer closure captures `ctx`/`parse_ident` (both `Copy`, satisfying + // `parse_sum`'s `C: Copy` / `F: Fn(C, &[u8]) -> Option + Copy`). let v = Calc::::parse_sum(i, (), parse_ident_fn)?; let rad: f32 = 'rad: { @@ -1129,7 +1129,7 @@ impl Calc { } } - // PORT NOTE: reshaped for borrowck — Zig stored `?*This`; Rust stores index. + // Reshaped for borrowck — Zig stored `?*This`; Rust stores index. if let Some(maybe_idx) = found { if let Some(idx) = maybe_idx { reduced[idx] = core::mem::replace(arg, Calc::Number(420.0)); diff --git a/src/css/values/color.rs b/src/css/values/color.rs index ba92a1ae9ecc..b5886a9b926a 100644 --- a/src/css/values/color.rs +++ b/src/css/values/color.rs @@ -403,8 +403,6 @@ impl Default for CssColor { } impl CssColor { - // TODO(port): move to *_jsc — `pub const jsFunctionColor = @import("../../css_jsc/color_js.zig").jsFunctionColor;` - /// Parse a CSS `` from the parser cursor. pub fn parse(input: &mut css::Parser) -> CssResult { let location = input.current_source_location(); @@ -881,7 +879,7 @@ impl CssColor { } pub fn hash(&self, hasher: &mut bun_wyhash::Wyhash) { - // PORT NOTE: Zig `css.implementHash` — variant-tag prefix + payload fields. + // Zig `css.implementHash` — variant-tag prefix + payload fields. // Hash the discriminant + the active variant's f32 components explicitly; // never reinterpret a `repr(Rust)` enum as raw bytes (unspecified layout / // padding → UB and non-deterministic hashes). @@ -1005,7 +1003,6 @@ impl ColorFallbackKind { /// Trait every colorspace implements. The Zig used `@field(this, "x")` over the /// first three struct fields plus `alpha`; here we expose them by index. -/// `// TODO(port): could be derived with a proc-macro.` pub trait Colorspace: Copy + Sized + FromAnyColorspace { const CHANNEL_NAMES: (&'static [u8], &'static [u8], &'static [u8]); const CHANNEL_TYPES: (ChannelType, ChannelType, ChannelType); @@ -1508,18 +1505,18 @@ impl LABColor { } pub fn new_oklab(l: f32, a: f32, b: f32, alpha: f32) -> LABColor { - // PORT NOTE: Zig had `LABColor{ .lab = OKLAB.new(...) }` which looks like a bug; + // Zig had `LABColor{ .lab = OKLAB.new(...) }` which looks like a bug; // mirrored as Lab variant for behavioral parity. LABColor::Lab(LAB { l, a, b, alpha }) } pub fn new_lch(l: f32, a: f32, b: f32, alpha: f32) -> LABColor { - // PORT NOTE: Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored. + // Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored. LABColor::Lab(LAB { l, a, b, alpha }) } pub fn new_oklch(l: f32, a: f32, b: f32, alpha: f32) -> LABColor { - // PORT NOTE: Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored. + // Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored. LABColor::Lab(LAB { l, a, b, alpha }) } @@ -1777,8 +1774,8 @@ define_colorspace! { premultiply = rectangular; powerless = none; into_css = |srgb: &SRGB| { - // TODO: should we serialize as color(srgb, ...)? - // would be more precise than 8-bit color. + // Serializes through 8-bit RGBA, matching upstream lightningcss + // (`color(srgb, ...)` would be more precise but would change output). CssColor::Rgba(RGBA::from(*srgb)) }; } @@ -1813,7 +1810,7 @@ define_colorspace! { define_colorspace! { /// A color in the [`sRGB-linear`](https://www.w3.org/TR/css-color-4/#predefined-sRGB-linear) color space. SRGBLinear { r, g, b } - // PORT NOTE: Zig had `.r = ChannelType{ .angle = true }` for SRGBLinear which looks like a bug; + // Zig had `.r = ChannelType{ .angle = true }` for SRGBLinear which looks like a bug; // mirrored for parity. types = (CT_ANG, CT_PCT, CT_PCT); gamut = bounded; @@ -2141,7 +2138,7 @@ impl RelativeComponentParser { return Ok(css::color::AngleOrNumber::Number { value }); } - // PORT NOTE: Zig threads a stack `Angle` through `Calc(Angle).parseWith` + // Zig threads a stack `Angle` through `Calc(Angle).parseWith` // via a closure that returns `Calc{ .value = &t.angle }` (raw stack // pointer). Rust `Calc::Value` is `Box`, so box the temporary. if let Ok(value) = input.try_parse(|i| { @@ -2309,7 +2306,6 @@ impl RelativeComponentParser { bitflags::bitflags! { /// A channel type for a color space. - /// TODO(zack): why tf is this bitflags? #[derive(Clone, Copy, PartialEq, Eq)] pub struct ChannelType: u8 { /// Channel represents a percentage. @@ -2337,7 +2333,7 @@ pub fn parse_predefined( None }; - // PORT NOTE: reshaped for borrowck — detach the slice from the + // Reshaped for borrowck — detach the slice from the // `&mut self` borrow so `i` is reusable below. let colorspace = i.expect_ident_cloned()?; @@ -2402,7 +2398,7 @@ pub fn parse_predefined_relative( b"srgb" => PredefinedColor::Srgb(SRGB { r: a, g: b, b: c, alpha }), b"srgb-linear" => PredefinedColor::SrgbLinear(SRGBLinear { r: a, g: b, b: c, alpha }), b"display-p3" => PredefinedColor::DisplayP3(P3 { r: a, g: b, b: c, alpha }), - // PORT NOTE: Zig has "a99-rgb" here (typo?); mirrored for behavioral parity. + // Zig has "a99-rgb" here (typo?); mirrored for behavioral parity. b"a99-rgb" => PredefinedColor::A98(A98 { r: a, g: b, b: c, alpha }), b"prophoto-rgb" => PredefinedColor::Prophoto(ProPhoto { r: a, g: b, b: c, alpha }), b"rec2020" => PredefinedColor::Rec2020(Rec2020 { r: a, g: b, b: c, alpha }), @@ -2501,7 +2497,7 @@ pub fn parse_color_mix(input: &mut css::Parser) -> CssResult { ColorSpaceName::Xyz | ColorSpaceName::XyzD65 => { first_color.interpolate::(p1, &second_color, p2, hue_method) } - // PORT NOTE: Zig used XYZd65 for xyz-d50 too (likely bug); mirrored for parity. + // Zig used XYZd65 for xyz-d50 too (likely bug); mirrored for parity. ColorSpaceName::XyzD50 => { first_color.interpolate::(p1, &second_color, p2, hue_method) } @@ -3332,7 +3328,8 @@ impl From for ProPhoto { // convert linear-light prophoto-rgb in the range 0.0-1.0 // to gamma corrected form // Transfer curve is gamma 1.8 with a small linear portion - // TODO for negative values, extend linear portion on reflection of axis, then add pow below that + // For negative values, the linear portion is extended on + // reflection of axis, with pow applied beyond it (csswg conversions.js). const ET: f32 = 1.0 / 512.0; let abs = c.abs(); if abs >= ET { @@ -3641,7 +3638,7 @@ impl From for OKLAB { // `ColorIntoMixin(T, .Space).into(target)`). // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: `ColorIntoMixin` resolved conversions at comptime via @hasDecl +// `ColorIntoMixin` resolved conversions at comptime via @hasDecl // across handwritten + generated tables. In Rust this is the union of the // `impl From for Dst` blocks above plus `color_generated.rs`; the // generated file fills the transitive gaps the macro requires. diff --git a/src/css/values/easing.rs b/src/css/values/easing.rs index 30981e81a0d5..699a59df0a0c 100644 --- a/src/css/values/easing.rs +++ b/src/css/values/easing.rs @@ -85,7 +85,7 @@ fn easing_map_get_any_case(ident: &[u8]) -> Option { impl EasingFunction { pub fn parse(input: &mut css::Parser) -> Result { - // PORT NOTE: reshaped for borrowck — `try_parse(|i| i.expect_ident())` + // Reshaped for borrowck — `try_parse(|i| i.expect_ident())` // ties the returned slice to the closure's `&mut Parser` borrow, so the // ident can't escape. Read the next token by value (Token slices are // `'static` placeholders for the not-yet-threaded `'bump`) and dispatch @@ -285,8 +285,8 @@ fn step_position_map_get_any_case(ident: &[u8]) -> Option { } impl StepPosition { - // TODO(port): Zig used `css.DeriveToCss(@This()).toCss` — reflection-derived serializer. - // Replace with `#[derive(ToCss)]` once the trait/derive exists. + /// Zig used `css.DeriveToCss(@This()).toCss` (reflection-derived + /// keyword serializer); hand-written equivalent. pub fn to_css(self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { dest.write_str(<&'static str>::from(self)) } diff --git a/src/css/values/gradient.rs b/src/css/values/gradient.rs index 17631e823811..e47b8637aa04 100644 --- a/src/css/values/gradient.rs +++ b/src/css/values/gradient.rs @@ -1455,8 +1455,6 @@ pub fn parse_items(input: &mut css::Parser) -> Result Result<()> { diff --git a/src/css/values/ident.rs b/src/css/values/ident.rs index 21108d8cf376..a1198c6676de 100644 --- a/src/css/values/ident.rs +++ b/src/css/values/ident.rs @@ -20,8 +20,9 @@ macro_rules! arena_slice_newtype { $(#[$meta])* #[derive(Debug, Clone, Copy)] pub struct $name { - // TODO(port): arena lifetime — CSS parser slices are arena-owned; thread a `'bump` - // lifetime through instead of erasing to a raw pointer. + // CSS parser slices are arena-owned; the borrow is erased to a raw + // pointer until the crate threads a `'bump` lifetime (see + // PORTING.md §Allocators). Never dereferenced after arena reset. pub v: *const [u8], } @@ -41,12 +42,12 @@ macro_rules! arena_slice_newtype { #[inline] pub fn v(&self) -> &[u8] { // SAFETY: arena-owned, never null, immutable for the parse session - // (see field-level TODO(port) on `'bump` threading). + // (see the field-level comment on `'bump` threading). unsafe { crate::arena_str(self.v) } } pub fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: Zig `css.implementDeepClone` — field-wise. The + // Zig `css.implementDeepClone` — field-wise. The // `*const [u8]` slice is arena-owned (never mutated, freed on // arena reset), so identity copy is correct (matches generics.zig // "const strings" fast-path). @@ -54,7 +55,7 @@ macro_rules! arena_slice_newtype { } pub fn hash(&self, hasher: &mut Wyhash) { - // PORT NOTE: Zig `css.implementHash` (comptime field-walk) → arena slice bytes. + // Zig `css.implementHash` (comptime field-walk) → arena slice bytes. hasher.update(self.v()); } @@ -73,7 +74,7 @@ macro_rules! arena_slice_newtype { // `from` field below uses it directly. `parse_with_options` honors // `ParserOptions.css_modules.dashed_idents`. `to_css` resolves the // import-record path up front and hands it to `CssModule::reference_dashed` -// (borrowck — see PORT NOTE on that method). +// (borrowck — see the comment on that method). /// A CSS [``](https://www.w3.org/TR/css-values-4/#dashed-idents) reference. /// @@ -93,9 +94,7 @@ pub struct DashedIdentReference { impl DashedIdentReference { pub(crate) fn eql(&self, rhs: &Self) -> bool { - // PORT NOTE: Zig `css.implementEql` — field-wise. `from` is a CSS-modules - // resolution hint, not part of value identity, so compare on `ident` only - // (matches Zig `Specifier`-less comparison in the dashed-ident dedup path). + // Zig `css.implementEql` — field-wise over `ident` and `from`. use crate::generics::CssEql; self.ident.eql(&rhs.ident) && self.from.eql(&rhs.from) } @@ -149,7 +148,7 @@ impl DashedIdentReference { let ident_v = unsafe { crate::arena_str(self.ident.v) }; let source_index = dest.loc.source_index; let bump = dest.arena; - // PORT NOTE: Zig `referenceDashed` took `*Printer` and called + // Zig `referenceDashed` took `*Printer` and called // `dest.importRecord()` internally. Rust borrowck forbids handing // `dest` to a method on `dest.css_module`, so resolve the path // here and pass the slice down. The `?` preserves the Zig @@ -187,13 +186,29 @@ arena_slice_newtype! { DashedIdent } -// TODO(port): Zig `pub fn HashMap(comptime V: type) type` returned an -// ArrayHashMapUnmanaged with a custom string-hash context. Inherent assoc -// type aliases are unstable in Rust; expose as a free type alias instead. -// bun_collections::ArrayHashMap is wyhash-keyed; verify the hasher matches -// std.array_hash_map.hashString or supply a custom Hash impl. -// blocked_on: bun_collections::ArrayHashMap surface -pub type DashedIdentHashMap = bun_collections::ArrayHashMap; +/// Hash/eql context for [`DashedIdentHashMap`] — port of the anonymous context +/// struct in Zig `DashedIdent.HashMap(V)`: keys hash their string bytes via +/// `std.array_hash_map.hashString` (wyhash seed 0, truncated to u32) and +/// compare by byte equality. +#[derive(Default, Clone, Copy)] +pub struct DashedIdentContext; + +impl bun_collections::array_hash_map::ArrayHashContext for DashedIdentContext { + #[inline] + fn hash(&self, key: &DashedIdent) -> u32 { + bun_collections::array_hash_map::hash_string(key.v()) + } + + #[inline] + fn eql(&self, a: &DashedIdent, b: &DashedIdent, _b_index: usize) -> bool { + a.v() == b.v() + } +} + +/// Zig `pub fn HashMap(comptime V: type) type` — inherent assoc type aliases +/// are unstable in Rust, so this is a free type alias instead. +pub type DashedIdentHashMap = + bun_collections::ArrayHashMap; impl DashedIdent { pub fn parse(input: &mut Parser) -> CssResult { @@ -303,7 +318,8 @@ impl IdentOrRef { #[cfg(debug_assertions)] pub fn debug_ident(self) -> &'static [u8] { - // TODO(port): lifetime — returns arena-borrowed slice; `'static` is a placeholder. + // Returns an arena-borrowed slice; `'static` is a placeholder for the + // not-yet-threaded `'bump` lifetime. if self.ref_bit() { let ptr = self.ptrbits() as usize as *const *const [u8]; // SAFETY: in debug builds, `ptrbits` stores a valid arena-allocated `*const *const [u8]` @@ -387,7 +403,8 @@ impl IdentOrRef { map: &bun_ast::symbol::Map, local_names: Option<&css::LocalsResultsMap>, ) -> Option<&'static [u8]> { - // TODO(port): lifetime — returns arena/symbol-table borrow; `'static` is a placeholder. + // Returns an arena/symbol-table borrow; `'static` is a placeholder for + // the not-yet-threaded `'bump` lifetime. if self.is_ident() { // SAFETY: arena slice reconstructed from packed ptr/len return Some(unsafe { crate::arena_str(self.as_ident().unwrap().v) }); @@ -418,7 +435,7 @@ impl IdentOrRef { } else { // SAFETY: self is #[repr(transparent)] u128; reading first 2 bytes matches Zig's // `slice_u8[0..2]` (which is almost certainly a Zig bug — hashes 2 bytes, not 16). - // TODO(port): verify upstream intent; preserving behavior verbatim. + // Preserved verbatim for behavioral parity; PR #30784 hashes the full identity. let bytes = unsafe { core::slice::from_raw_parts(std::ptr::from_ref::(self).cast::(), 2) }; diff --git a/src/css/values/image.rs b/src/css/values/image.rs index 719c062717e3..d14791972f57 100644 --- a/src/css/values/image.rs +++ b/src/css/values/image.rs @@ -11,8 +11,6 @@ use bun_alloc::Arena; use bun_ast::ImportKind; /// A CSS [``](https://www.w3.org/TR/css-images-3/#image-values) value. -// TODO(port): `parse`/`to_css` were `css.DeriveParse(@This()).parse` / `css.DeriveToCss(@This()).toCss` -// — comptime-reflection derives. Hand-expanded below until the proc-macro lands. #[derive(Default)] pub enum Image { /// The `none` keyword. @@ -109,8 +107,6 @@ impl Image { #[inline] pub fn eql(&self, other: &Image) -> bool { - // TODO(port): was `css.implementEql(@This(), this, other)` (comptime field-walk). - // Hand-expanded; replace with `#[derive(PartialEq)]` once `Url: PartialEq`. match (self, other) { (Image::None, Image::None) => true, (Image::Url(a), Image::Url(b)) => a.import_record_idx == b.import_record_idx, @@ -121,7 +117,6 @@ impl Image { } pub fn deep_clone(&self, arena: &Arena) -> Self { - // TODO(port): was `css.implementDeepClone(@This(), this, arena)` (comptime field-walk). match self { Image::None => Image::None, Image::Url(u) => Image::Url(Url { @@ -169,7 +164,7 @@ impl Image { let prefix_image: &Image = if let Some(r) = &rgb { r } else { &*self }; // Legacy -webkit-gradient() - // PORT NOTE: Zig's `and`/`if-else` precedence here is preserved verbatim: + // Zig's `and`/`if-else` precedence here is preserved verbatim: // `if (targets.browsers) |b| isWebkitGradient(b) else (false and prefix_image.* == .gradient)` if prefixes.contains(VendorPrefix::WEBKIT) && if let Some(browsers) = targets.browsers { @@ -236,11 +231,7 @@ impl Image { } } - // TODO(port): `css.DeriveParse(@This()).parse` — hand-expanded: try each - // variant in Zig field order (none/url/gradient/image-set). - // blocked_on: `Url::parse` (gated on `Parser::add_import_record`). The - // gradient/image-set arms are real; the url arm un-gates with url.rs. - + // Variants are tried in declaration order: none, url, gradient, image-set. pub fn parse(input: &mut css::Parser) -> Result { if input .try_parse(|i| i.expect_ident_matching(b"none")) @@ -257,7 +248,6 @@ impl Image { ImageSet::parse(input).map(Image::ImageSet) } - // PORT: `css.DeriveToCss(@This()).toCss` — hand-expanded over enum variants. pub fn to_css(&self, dest: &mut css::Printer) -> core::result::Result<(), css::PrintErr> { match self { Image::None => dest.write_str(b"none"), @@ -345,14 +335,12 @@ impl ImageSet { /// Returns the `image-set()` value with the given vendor prefix. pub(crate) fn get_prefixed(&self, arena: &Arena, prefix: css::VendorPrefix) -> ImageSet { ImageSet { - // TODO(port): was `css.deepClone(ImageSetOption, arena, &this.options)` (comptime helper) options: self.options.iter().map(|o| o.deep_clone(arena)).collect(), vendor_prefix: prefix, } } pub(crate) fn eql(&self, other: &ImageSet) -> bool { - // TODO(port): was `css.implementEql(@This(), this, other)` — derive PartialEq instead. self.vendor_prefix == other.vendor_prefix && self.options.len() == other.options.len() && self @@ -363,7 +351,6 @@ impl ImageSet { } pub(crate) fn deep_clone(&self, arena: &Arena) -> Self { - // TODO(port): was `css.implementDeepClone(@This(), this, arena)` — derive Clone instead. ImageSet { options: self.options.iter().map(|o| o.deep_clone(arena)).collect(), vendor_prefix: self.vendor_prefix, @@ -390,7 +377,8 @@ pub struct ImageSetOption { /// The resolution of the image. pub resolution: Resolution, /// The mime type of the image. - // TODO(port): arena-borrowed slice from tokenizer input; revisit ownership. + // Arena-borrowed slice from the tokenizer input; the parser arena outlives + // this value (see SAFETY notes at the use sites). pub file_type: Option<*const [u8]>, } @@ -398,7 +386,7 @@ impl ImageSetOption { pub(crate) fn parse(input: &mut css::Parser) -> Result { let start_position = input.input.tokenizer.get_position(); let loc = input.current_source_location(); - // PORT NOTE: `expect_url_or_string` returns a borrow of the parser, so + // `expect_url_or_string` returns a borrow of the parser, so // it can't be used as a `try_parse` callback directly (the result type // `R` may not borrow the closure arg). Erase the borrow via `*const` // — token slices are arena-static (see `css_parser::src_str`). @@ -446,7 +434,7 @@ impl ImageSetOption { unreachable!() }; let dep_: Option = if dest.dependencies.is_some() { - // PORT NOTE: hoist `get_import_records` (mut borrow) out of the + // Hoist `get_import_records` (mut borrow) out of the // arg list so `filename()` (shared borrow) can run; result is `&'a _`. let import_records = dest.get_import_records()?; Some(UrlDependency::new( @@ -497,7 +485,6 @@ impl ImageSetOption { if let Some(file_type) = self.file_type { dest.write_str(" type(")?; // SAFETY: file_type points into the arena-owned parser input which outlives printing. - // TODO(port): replace raw slice with proper arena-lifetime borrow. let file_type_slice = unsafe { crate::arena_str(file_type) }; dest.serialize_string(file_type_slice)?; dest.write_char(b')')?; @@ -507,7 +494,6 @@ impl ImageSetOption { } pub(crate) fn deep_clone(&self, arena: &Arena) -> Self { - // TODO(port): was `css.implementDeepClone(@This(), this, arena)` — derive Clone instead. ImageSetOption { image: self.image.deep_clone(arena), resolution: self.resolution, @@ -516,7 +502,6 @@ impl ImageSetOption { } pub(crate) fn eql(&self, rhs: &ImageSetOption) -> bool { - // TODO(port): was `css.implementEql(@This(), lhs, rhs)` — derive PartialEq instead. self.image.eql(&rhs.image) && self.resolution == rhs.resolution && match (self.file_type, rhs.file_type) { @@ -531,7 +516,8 @@ impl ImageSetOption { fn parse_file_type(input: &mut css::Parser) -> Result<*const [u8]> { input.expect_function_matching(b"type")?; input.parse_nested_block(|i: &mut css::Parser| { - // TODO(port): expect_string returns arena-borrowed &[u8]; coerced to raw ptr to avoid struct lifetime + // expect_string returns an arena-borrowed &[u8]; coerced to a raw ptr to + // avoid a struct lifetime (token slices outlive the parse session). i.expect_string().map(std::ptr::from_ref::<[u8]>) }) } diff --git a/src/css/values/length.rs b/src/css/values/length.rs index 504bf05ed396..b2c60105164d 100644 --- a/src/css/values/length.rs +++ b/src/css/values/length.rs @@ -526,7 +526,7 @@ impl Length { right: Box::new(b.into_calc()), })), } - // PORT NOTE: reshaped for borrowck — Zig matched on tags then accessed + // Reshaped for borrowck — Zig matched on tags then accessed // `a.calc.value` / `b.calc.value` while both were still bound; Rust needs // to move out of the Box, so the conditions are folded into match guards. } @@ -557,9 +557,8 @@ impl Length { } _ => return None, } - // TODO(port): the Zig builds `Length{ .calc = s.left }` without - // cloning (alias of the same heap node). With `Box` ownership we - // must clone here; revisit if Calc nodes become arena-backed refs. + // The Zig builds `Length{ .calc = s.left }` without cloning (alias + // of the same heap node); with `Box` ownership we must clone here. } if let Self::Calc(c) = other { @@ -596,7 +595,7 @@ impl Length { }, _ => length, } - // PORT NOTE: reshaped for borrowck — Zig rebinds `c` while reading `c.*`; + // Reshaped for borrowck — Zig rebinds `c` while reading `c.*`; // Rust moves out of the Box once and rebuilds. } @@ -699,7 +698,7 @@ impl protocol::TryMap for LengthValue { impl protocol::TryOp for LengthValue { #[inline] fn try_op(&self, rhs: &Self, ctx: C, f: impl Fn(C, f32, f32) -> f32) -> Option { - // PORT NOTE: `LengthValue::try_op` takes a 2-arg closure (ctx folded + // `LengthValue::try_op` takes a 2-arg closure (ctx folded // in by caller); the `protocol::TryOp` shape passes `C` by-value with // no `Copy` bound, so we can't capture `ctx` in an `Fn` closure that // might be called more than once. Inline the same-unit + px-convert @@ -708,7 +707,7 @@ impl protocol::TryOp for LengthValue { let v = f(ctx, self.value(), rhs.value()); return Some(self.map_value(|_| v)); } - // PORT NOTE: Zig calls `this.toPx()` for BOTH operands here (length.zig:447) — + // Zig calls `this.toPx()` for BOTH operands here (length.zig:447) — // preserving that behavior verbatim; likely an upstream bug. if let (Some(a), Some(b)) = (self.to_px(), self.to_px()) { return Some(LengthValue::Px(f(ctx, a, b))); @@ -722,7 +721,7 @@ impl protocol::TryOpTo for LengthValue { if core::mem::discriminant(self) == core::mem::discriminant(rhs) { return Some(f(ctx, self.value(), rhs.value())); } - // PORT NOTE: Zig calls `this.toPx()` for BOTH operands here (length.zig:473) — + // Zig calls `this.toPx()` for BOTH operands here (length.zig:473) — // preserving that behavior verbatim; likely an upstream bug. if let (Some(a), Some(b)) = (self.to_px(), self.to_px()) { return Some(f(ctx, a, b)); @@ -799,7 +798,7 @@ impl protocol::TryMap for Angle { impl protocol::TryOp for Angle { #[inline] fn try_op(&self, rhs: &Self, ctx: C, f: impl Fn(C, f32, f32) -> f32) -> Option { - // PORT NOTE: `Angle::op` takes a `fn(C, f32, f32)` pointer (Zig + // `Angle::op` takes a `fn(C, f32, f32)` pointer (Zig // `comptime`-monomorphized fn arg), so we can't pass the generic // closure through it. Inline the per-variant dispatch here instead. Some(match (self, rhs) { diff --git a/src/css/values/percentage.rs b/src/css/values/percentage.rs index 9bcaa8eac5df..21bd7aaa556b 100644 --- a/src/css/values/percentage.rs +++ b/src/css/values/percentage.rs @@ -30,7 +30,7 @@ impl Percentage { pub(crate) fn to_css(self, dest: &mut Printer) -> Result<(), PrintErr> { let x = self.v * 100.0; let int_value: Option = if (x - x.trunc()) == 0.0 { - // PORT NOTE: Rust `as` saturates on overflow/NaN where Zig is UB. + // Rust `as` saturates on overflow/NaN where Zig is UB. Some(self.v as i32) } else { None @@ -152,15 +152,10 @@ impl PartialEq for DimensionPercentage { } // `Zero`/`MulF32`/`TryAdd`/`Parse` protocol traits live in -// `crate::values::protocol` until `generics::parse_tocss_numeric_gated` -// un-gates. The bound set below mirrors the full Zig comptime-method surface -// on `D`; per-method `where` clauses narrow further so plain -// `DimensionPercentage` (no behavior) needs only `D: Clone`. -impl DimensionPercentage -where - // TODO(port): narrow these bounds; mirroring methods called on D below. - D: Clone, -{ +// `crate::values::protocol`. The Zig comptime-method surface on `D` is +// expressed via per-method `where` clauses, so plain +// `DimensionPercentage` (no behavior) needs no bounds at all. +impl DimensionPercentage { pub(crate) fn parse(input: &mut css::Parser) -> CssResult where Self: crate::values::calc::CalcValue, @@ -209,9 +204,12 @@ where } } - pub(crate) fn deep_clone(&self) -> Self { + pub(crate) fn deep_clone(&self) -> Self + where + D: Clone, + { match self { - // PORT NOTE: Zig branched on `comptime needs_deepclone` to avoid cloning POD types. + // Zig branched on `comptime needs_deepclone` to avoid cloning POD types. // In Rust, D: Clone covers both — Copy types' clone is a bitwise copy. Self::Dimension(d) => Self::Dimension(d.clone()), Self::Percentage(p) => Self::Percentage(*p), @@ -220,7 +218,7 @@ where } } - // PORT NOTE: `deinit` dropped — Box> frees via Drop; D's Drop (if any) runs + // `deinit` dropped — Box> frees via Drop; D's Drop (if any) runs // automatically. Zig body only freed owned fields, so no explicit `impl Drop` needed. pub(crate) fn zero() -> Self @@ -289,9 +287,11 @@ where (Self::Calc(this_calc), _) => match this_calc.as_ref() { Calc::Value(v) => return v.add_recursive(other), Calc::Sum { left, right } => { - // PORT NOTE: reshaped for borrowck — Zig wrapped sum.left/right (raw ptrs) - // directly in This{.calc = ...}. Here we deep_clone since Box is owning. - // TODO(port): lifetime — sum.left/right ownership semantics need review. + // Reshaped for borrowck — Zig wrapped sum.left/right (raw ptrs) + // directly in This{.calc = ...} (aliasing the same heap nodes); + // with owning Boxes we deep_clone instead. The values are only + // read during this computation, so the clone is semantically + // equivalent (just extra allocation). let left_calc = Self::Calc(left.deep_clone_boxed()); if let Some(res) = left_calc.add_recursive(other) { return Some(res.add_impl(Self::Calc(right.deep_clone_boxed()))); @@ -454,7 +454,7 @@ pub enum NumberOrPercentage { } impl NumberOrPercentage { - // PORT NOTE: Zig used `css.DeriveParse(@This()).parse` / `css.DeriveToCss(@This()).toCss` + // Zig used `css.DeriveParse(@This()).parse` / `css.DeriveToCss(@This()).toCss` // (comptime reflection derives). Hand-rolled here as the trivial two-variant // try-parse cascade so `AlphaValue::parse` doesn't panic at runtime. pub(crate) fn parse(input: &mut css::Parser) -> CssResult { diff --git a/src/css/values/position.rs b/src/css/values/position.rs index c455bc3e5795..31ecefbf448a 100644 --- a/src/css/values/position.rs +++ b/src/css/values/position.rs @@ -148,8 +148,6 @@ impl Position { } pub(crate) fn to_css(&self, dest: &mut css::Printer) -> Result<(), css::PrintErr> { - // PORT NOTE: reshaped for borrowck — Zig used tag-then-payload-access (`this.x == .side and this.x.side.side != .left`); - // Rust uses if-let pattern matching to bind payloads. if let (PositionComponent::Side(xs), PositionComponent::Length(yl)) = (&self.x, &self.y) { if xs.side != HorizontalPositionKeyword::Left { self.x.to_css(dest)?; @@ -312,9 +310,8 @@ pub enum PositionComponent { Side(PositionComponentSide), } -// PORT NOTE: Zig used duck-typed `S.parse`/`S.toCss`; Rust bounds on the -// values-local `protocol::{Parse,ToCss}` shapes until `generics::Parse` -// un-gates. +// `S` is bounded on the values-local `protocol::{Parse,ToCss}` shapes until +// `generics::Parse` un-gates. impl PositionComponent { pub(crate) fn is_zero(&self) -> bool { if let PositionComponent::Length(l) = self { diff --git a/src/css/values/ratio.rs b/src/css/values/ratio.rs index 5d15f69c4234..a705536f3579 100644 --- a/src/css/values/ratio.rs +++ b/src/css/values/ratio.rs @@ -44,7 +44,6 @@ impl Ratio { Ok(()) } - // PORT NOTE: dropped unused `std.mem.Allocator` param (was `_` in Zig). pub(crate) fn add_f32(self, other: f32) -> Ratio { Ratio { numerator: self.numerator + other, diff --git a/src/css/values/rect.rs b/src/css/values/rect.rs index 7a8c585a8ee2..70c1afe23efe 100644 --- a/src/css/values/rect.rs +++ b/src/css/values/rect.rs @@ -3,7 +3,7 @@ use crate::css_parser::{CssResult as Result, PrintErr, Printer}; use crate::targets::Browsers; use crate::values::protocol::{IsCompatible, Parse, ToCss}; -// PORT NOTE: the Zig `needsDeinit(comptime T: type) bool` switch and the +// The Zig `needsDeinit(comptime T: type) bool` switch and the // `deinit(this, arena)` method are dropped entirely. They existed to // thread per-field `arena.free` through a comptime type table; in Rust, // `T: Drop` on the four fields handles this automatically (and arena-owned @@ -40,11 +40,10 @@ impl Rect { where T: Clone, { - // PORT NOTE: Zig branched on `comptime needs_deinit` to decide between + // Zig branched on `comptime needs_deinit` to decide between // bitwise copy and per-field `.deepClone(arena)`. In Rust this is - // just the `DeepClone`/`Clone` trait on `T` — the cheap-copy types + // just the `Clone` trait on `T` — the cheap-copy types // (`f32`, `NumberOrPercentage`, `LineStyle`) impl it as a bit copy. - // TODO(port): narrow trait bound once css::generic::DeepClone lands. Self { top: self.top.clone(), right: self.right.clone(), diff --git a/src/css/values/size.rs b/src/css/values/size.rs index 6caed113cf9c..765c08fc0fa7 100644 --- a/src/css/values/size.rs +++ b/src/css/values/size.rs @@ -10,13 +10,11 @@ pub struct Size2D { pub b: T, } -// PORT NOTE: Zig's `switch (T) { f32 => ..., LengthPercentage => ..., else => T.parse }` +// Zig's `switch (T) { f32 => ..., LengthPercentage => ..., else => T.parse }` // is comptime type dispatch. In Rust this is expressed via trait bounds — `f32` and -// `LengthPercentage` must impl the same `Parse`/`ToCss`/`Eql` traits as other CSS value +// `LengthPercentage` impl the same `Parse`/`ToCss` traits as other CSS value // types (the `f32` impls delegate to `CSSNumberFns`). The per-type `switch` arms are // therefore collapsed into trait method calls below. -// TODO(port): confirm trait names match the crate API once `generics:: -// parse_tocss_numeric_gated` un-gates; for now bound on `values::protocol`. impl Size2D where T: Clone + PartialEq, @@ -25,7 +23,7 @@ where where T: Parse, { - // PORT NOTE: f32 → CSSNumberFns::parse, LengthPercentage → LengthPercentage::parse, + // f32 → CSSNumberFns::parse, LengthPercentage → LengthPercentage::parse, // else → T::parse — all unified under the `Parse` trait in Rust. T::parse(input) } @@ -60,14 +58,13 @@ where where T: ToCss, { - // PORT NOTE: f32 → CSSNumberFns::to_css, else → val.to_css — unified under `ToCss` trait. + // f32 → CSSNumberFns::to_css, else → val.to_css — unified under `ToCss` trait. val.to_css(dest) } pub(crate) fn deep_clone(&self, _bump: &Arena) -> Self { - // TODO(port): css::implement_deep_clone is @typeInfo-based reflection in Zig; - // replace with #[derive(DeepClone)] or arena-aware deep_clone. - // For now `T: Clone` covers it (Box payloads deep-clone via their Clone impls). + // Zig's `css.implementDeepClone` was @typeInfo-based reflection; + // `T: Clone` covers it (Box payloads deep-clone via their Clone impls). Size2D { a: self.a.clone(), b: self.b.clone(), @@ -76,13 +73,13 @@ where #[inline] pub(crate) fn val_eql(lhs: &T, rhs: &T) -> bool { - // PORT NOTE: f32 → `lhs.* == rhs.*`, else → `lhs.eql(rhs)` — unified under PartialEq. + // f32 → `lhs.* == rhs.*`, else → `lhs.eql(rhs)` — unified under PartialEq. lhs == rhs } #[inline] pub(crate) fn eql(lhs: &Self, rhs: &Self) -> bool { - // PORT NOTE: preserved verbatim from Zig — compares lhs.a against rhs.b only + // Preserved verbatim from Zig — compares lhs.a against rhs.b only // (not a/a && b/b). Suspect upstream bug, but ported faithfully. lhs.a == rhs.b } diff --git a/src/css/values/syntax.rs b/src/css/values/syntax.rs index 864842a06cd3..fc9cd0145824 100644 --- a/src/css/values/syntax.rs +++ b/src/css/values/syntax.rs @@ -20,10 +20,9 @@ const SPACE_CHARACTERS: &[u8] = &[0x20, 0x09]; /// A CSS [syntax string](https://drafts.css-houdini.org/css-properties-values-api/#syntax-strings) /// used to define the grammar for a registered custom property. -// PORT NOTE: the Zig source comments note "Zig doesn't have lifetimes, so 'i is omitted" — -// upstream lightningcss Rust threaded `'i`, but the port uses `&'static [u8]` / +// Upstream lightningcss Rust threaded `'i`, but the port uses `&'static [u8]` / // `*const [u8]` placeholders for arena-borrowed slices (matching `Token` / -// `ident.rs`). TODO(refactor): thread `'bump` and restore the lifetime. +// `ident.rs`); `'bump` threading would restore the lifetime. #[derive(Debug, Clone, PartialEq, Eq)] pub enum SyntaxString { /// A list of syntax components. @@ -35,8 +34,8 @@ pub enum SyntaxString { impl SyntaxString { pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // TODO(port): css.implementDeepClone is comptime field reflection — replace with - // a `DeepClone` trait/derive. For now defer to Clone. + // Zig's `css.implementDeepClone` (comptime field reflection) maps to + // `Clone` here — every payload owns its data. self.clone() } @@ -104,7 +103,7 @@ impl SyntaxString { match self { SyntaxString::Universal => Ok(ParsedComponent::TokenList(TokenList::parse( input, - // PORT NOTE: Zig passes `ParserOptions.default(input.arena(), null)`; + // Zig passes `ParserOptions.default(input.arena(), null)`; // Rust's signature drops the arena param (global-alloc port). &ParserOptions::default(None), 0, @@ -293,7 +292,7 @@ pub enum SyntaxComponentKind { /// A `` component. CustomIdent, /// A literal component. - // PORT NOTE: PORTING.md §Forbidden bans laundering a parser-borrowed slice to + // PORTING.md §Forbidden bans laundering a parser-borrowed slice to // `&'static`. Zig's arena keeps the source alive for the AST's lifetime; Rust // would need a `'bump` lifetime threaded through `SyntaxString`. The port owns // the bytes instead — `Box<[u8]>` per §Forbidden ("the field should be @@ -385,7 +384,7 @@ fn is_name_code_point(c: u8) -> bool { // ─── ParsedComponent ────────────────────────────────────────────────────── // `ParsedComponent` is the materialized form of a `SyntaxComponentKind` and // carries `Image` / `CssColor` / `Transform{,List}` / `TokenList` payloads. -// PORT NOTE: no `#[derive]` — payload types lack a common Debug/Clone/PartialEq +// No `#[derive]` — payload types lack a common Debug/Clone/PartialEq // surface (Image: none; TokenList: Default-only; Ident/CustomIdent: no Eq; // Transform: no Debug). Zig has only `deepClone` + `toCss`, mirrored below. pub enum ParsedComponent { @@ -414,9 +413,9 @@ pub enum ParsedComponent { /// A `` value. TransformFunction(Transform), /// A `` value. - // PORT NOTE: `TransformList<'bump>` borrows the parser arena. The port uses - // `'static` placeholders (matching `Token`/`Ident`). TODO(refactor): thread - // `'bump` through `ParsedComponent<'a>`. + // `TransformList<'bump>` borrows the parser arena. The port uses + // `'static` placeholders (matching `Token`/`Ident`); `'bump` threading + // through `ParsedComponent<'a>` would restore the lifetime. TransformList(TransformList), /// A `` value. CustomIdent(CustomIdent), @@ -439,7 +438,7 @@ pub struct Repeated { impl Repeated { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: hand-expanded `css.implementDeepClone` (field-wise reflection): + // Hand-expanded `css.implementDeepClone` (field-wise reflection): // ArrayList → Vec deep-cloned per element; `Multiplier` is `Copy`. Repeated { components: self.components.iter().map(|c| c.deep_clone(bump)).collect(), @@ -480,7 +479,7 @@ impl ParsedComponent { } pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: hand-expanded `css.implementDeepClone` (variant-wise reflection). + // Hand-expanded `css.implementDeepClone` (variant-wise reflection). // Payload signatures aren't yet uniform across the crate (some `deep_clone()` // take no arena, some take `&Arena`, some are `Copy`), so the `#[derive(DeepClone)]` // macro can't cover this enum until the signatures are unified. Match-arm dispatch diff --git a/src/css/values/time.rs b/src/css/values/time.rs index 1ba1b1a4e477..ac12fca1ae3f 100644 --- a/src/css/values/time.rs +++ b/src/css/values/time.rs @@ -157,8 +157,6 @@ impl Time { } pub fn add(self, other: Self) -> Time { - // Zig arena param dropped (unused). - // PORT NOTE: Zig passes `void` ctx + free fn; Rust closure captures nothing. self.op(other, |a, b| a + b) } @@ -182,10 +180,6 @@ impl Time { } pub fn op(self, other: Time, op_fn: impl Fn(f32, f32) -> f32) -> Time { - // PORT NOTE: Zig uses `ctx: anytype` + comptime fn-pointer (its closure idiom). - // Rust closures capture ctx directly, so the `ctx` param is dropped. - // PORT NOTE: reshaped bit-packed `switch_val` into an exhaustive tuple match; - // semantics are identical, `unreachable` arm is unnecessary. let _ = (self.tag(), TAG_SECONDS, TAG_MILLISECONDS); // keep tag consts referenced match (self, other) { (Time::Seconds(a), Time::Seconds(b)) => Time::Seconds(op_fn(a, b)), @@ -196,7 +190,6 @@ impl Time { } pub fn op_to(self, other: Time, op_fn: impl Fn(f32, f32) -> R) -> R { - // PORT NOTE: see `op` — ctx param folded into closure; bit-packed switch reshaped. match (self, other) { (Time::Seconds(a), Time::Seconds(b)) => op_fn(a, b), (Time::Milliseconds(a), Time::Milliseconds(b)) => op_fn(a, b), diff --git a/src/css/values/url.rs b/src/css/values/url.rs index 1c324e7af3d8..6f21b906a047 100644 --- a/src/css/values/url.rs +++ b/src/css/values/url.rs @@ -67,7 +67,7 @@ impl Url { pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { use crate::dependencies::UrlDependency; let dep: Option = if dest.dependencies.is_some() { - // PORT NOTE: hoist `get_import_records` (mut borrow) out of the arg + // `get_import_records` (mut borrow) is hoisted out of the arg // list so `filename()` (shared borrow) can run; result is `&'a _`. let import_records = dest.get_import_records()?; Some(UrlDependency::new( @@ -90,7 +90,6 @@ impl Url { dest.write_char(b')')?; if let Some(dependencies) = &mut dest.dependencies { - // PORT NOTE: bun.handleOom dropped — Vec::push aborts on OOM via global arena dependencies.push(crate::Dependency::Url(d)); } @@ -111,9 +110,9 @@ impl Url { // PERF(port): was std.Io.Writer.Allocating with dest.arena — using Vec; profile if hot let mut buf: Vec = Vec::new(); // PERF(alloc) we could use stack fallback here? - // PORT NOTE: inlined `Token::to_css_generic(UnquotedUrl(url))` — + // `Token::to_css_generic(UnquotedUrl(url))` is inlined here — // `Token` payloads are `&'static [u8]` placeholders and we only - // have `&'a [u8]` here. + // have `&'a [u8]`. use css::WriteAll; if buf .write_all(b"url(") @@ -148,8 +147,6 @@ impl Url { } pub fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: Zig `css.implementDeepClone` is field-wise reflection; both - // fields (`u32`, `dependencies::Location`) are `Copy`, so identity copy. Url { import_record_idx: self.import_record_idx, loc: self.loc, @@ -165,9 +162,8 @@ impl Url { // TODO: dedupe import records?? // This might not fucking work pub fn hash(&self, hasher: &mut bun_wyhash::Wyhash) { - // PORT NOTE: Zig `css.implementHash` is field-wise reflection. Only - // `import_record_idx` participates in identity (matches `eql` above); - // `loc` is presentation metadata. + // Only `import_record_idx` participates in identity (matches `eql` + // above); `loc` is presentation metadata. hasher.update(&self.import_record_idx.to_ne_bytes()); } } diff --git a/src/css_jsc/color_js.rs b/src/css_jsc/color_js.rs index 23a982a57d3d..4e2b02720fa7 100644 --- a/src/css_jsc/color_js.rs +++ b/src/css_jsc/color_js.rs @@ -326,10 +326,15 @@ pub fn js_function_color(global: &JSGlobalObject, frame: &CallFrame) -> JsResult return Ok(JSValue::NULL); } - // TODO(port): Zig used `@tagName(err.basic().kind)`; `BasicParseErrorKind` - // currently lacks `IntoStaticStr` in bun_css — falls back to Display until - // the derive lands. - return Err(global.throw(format_args!("color() failed to parse {}", err.basic().kind))); + // Matches Zig's `@tagName(err.basic().kind)`. + let kind_name = match err.basic().kind { + css::BasicParseErrorKind::unexpected_token(_) => "unexpected_token", + css::BasicParseErrorKind::end_of_input => "end_of_input", + css::BasicParseErrorKind::at_rule_invalid(_) => "at_rule_invalid", + css::BasicParseErrorKind::at_rule_body_invalid => "at_rule_body_invalid", + css::BasicParseErrorKind::qualified_rule_invalid => "qualified_rule_invalid", + }; + return Err(global.throw(format_args!("color() failed to parse {}", kind_name))); } Ok(result) => { let format: OutputColorFormat = if unresolved_format == OutputColorFormat::Ansi { @@ -368,12 +373,10 @@ pub fn js_function_color(global: &JSGlobalObject, frame: &CallFrame) -> JsResult let srgba: SRGB = match &result { CssColor::Float(float) => match &**float { css::FloatColor::Rgb(rgb) => *rgb, - // TODO(port): inline else over FloatColor variants → trait `IntoColor` other => other.into_srgb(), }, CssColor::Rgba(rgba) => rgba.into_srgb(), CssColor::Lab(lab) => { - // TODO(port): inline else over LabColor variants → trait `IntoColor` lab.into_srgb() } _ => break 'formatted, @@ -541,12 +544,10 @@ pub fn js_function_color(global: &JSGlobalObject, frame: &CallFrame) -> JsResult let hsl: HSL = match &result { CssColor::Float(float) => match &**float { css::FloatColor::Hsl(hsl) => *hsl, - // TODO(port): inline else over FloatColor variants → trait `IntoColor` other => other.into_hsl(), }, CssColor::Rgba(rgba) => rgba.into_hsl(), CssColor::Lab(lab) => { - // TODO(port): inline else over LabColor variants → trait `IntoColor` lab.into_hsl() } _ => break 'formatted, @@ -560,12 +561,10 @@ pub fn js_function_color(global: &JSGlobalObject, frame: &CallFrame) -> JsResult OutputColorFormat::Lab => { let lab: LAB = match &result { CssColor::Float(float) => { - // TODO(port): inline else over FloatColor variants → trait `IntoColor` float.into_lab() } CssColor::Lab(lab) => match &**lab { css::LabColor::Lab(lab_) => *lab_, - // TODO(port): inline else over LabColor variants → trait `IntoColor` other => other.into_lab(), }, CssColor::Rgba(rgba) => rgba.into_lab(), @@ -588,7 +587,8 @@ pub fn js_function_color(global: &JSGlobalObject, frame: &CallFrame) -> JsResult let mut dest: Vec = Vec::new(); let symbols = SymbolMap::init_list(Default::default()); - // TODO(port): css::Printer::new signature — Zig passes (allocator, ArrayList, writer, opts, null, null, &symbols) + // Zig passes (allocator, ArrayList, writer, opts, null, null, &symbols); + // the Rust signature maps 1:1 (arena, buffer, writer, opts, None, None, &symbols). let mut printer = css::Printer::new( &arena, bun_alloc::ArenaVec::::new_in(&arena), diff --git a/src/css_jsc/css_internals.rs b/src/css_jsc/css_internals.rs index 0b81579ae035..4418bf57101d 100644 --- a/src/css_jsc/css_internals.rs +++ b/src/css_jsc/css_internals.rs @@ -24,9 +24,9 @@ pub(crate) enum TestCategory { ParserOptions, } -// `#[bun_jsc::host_fn]` proc-macro not yet available; wrappers are plain fns -// for now and re-gain the attribute when bun_jsc::host_fn lands. -// TODO(port): bun_jsc::host_fn (proc-macro attribute) +// These test-only wrappers are consumed as plain safe fns through +// `dispatch_js2native.rs` re-exports, so they don't use the +// `#[bun_jsc::host_fn]` C-ABI shim. pub fn minify_error_test_with_options( global: &JSGlobalObject, @@ -102,8 +102,8 @@ pub(crate) fn testing_impl( // PERF(port): was arena bulk-free — CSS parser allocates into this bump // // SAFETY: `StyleSheet::parse` requires `&'static Bump` / `ParserOptions<'static>` - // because the rule tree stores lifetime-erased refs (see css_parser.rs PORT - // NOTE on `'bump` threading). The arena strictly outlives every value parsed + // because the rule tree stores lifetime-erased refs (see the css_parser.rs + // notes on `'bump` threading). The arena strictly outlives every value parsed // out of it below. let alloc: &'static Arena = unsafe { bun_ptr::detach_lifetime_ref(&arena) }; @@ -313,8 +313,8 @@ pub fn attr_test(global: &JSGlobalObject, frame: &CallFrame) -> JsResult` (lifetime - // erased crate-wide until 'bump threads through the rule tree — see - // css_parser.rs PORT NOTE). The arena strictly outlives the parsed + // erased crate-wide until 'bump threads through the rule tree — see the + // css_parser.rs notes). The arena strictly outlives the parsed // `stylesheet` below. let alloc: &'static Arena = unsafe { bun_ptr::detach_lifetime_ref(&arena) }; @@ -374,7 +374,8 @@ pub fn attr_test(global: &JSGlobalObject, frame: &CallFrame) -> JsResult r, Err(_e) => { // Zig: bun.handleErrorReturnTrace(e, @errorReturnTrace()); return .js_undefined; - // TODO(port): handleErrorReturnTrace — debug-only error trace dump; no Rust equivalent yet + // (handleErrorReturnTrace was a Zig debug-only trace dump with no + // Rust equivalent — the error is intentionally swallowed here.) return Ok(JSValue::UNDEFINED); } }; diff --git a/src/dns/lib.rs b/src/dns/lib.rs index 227e0c6e6a12..4ea99ce53694 100644 --- a/src/dns/lib.rs +++ b/src/dns/lib.rs @@ -95,11 +95,10 @@ impl GetAddrInfo { } } -// TODO(port): Zig is `packed struct(u64)` — bit layout: family:2, socktype:2, +// Zig was `packed struct(u64)` — bit layout: family:2, socktype:2, // protocol:2, backend:2, flags:32 (std.c.AI), _:24. Represented here as a plain -// struct because every use site reads fields by name; only `hash()` cared about -// the raw bytes (handled via `to_packed_bytes`). Decide if a true -// `#[repr(transparent)] u64` newtype is needed. +// struct because every use site reads fields by name; only `hash()` cares about +// the raw bytes, which `to_packed_bytes` reconstructs in that exact layout. #[derive(Clone, Copy)] pub struct Options { pub family: Family, @@ -159,24 +158,16 @@ impl Options { } } -// TODO(port): FromJSError types are only consumed by the *_jsc extension fns; -// consider moving these to bun_runtime::dns_jsc. -// TODO(port): thiserror not in deps — dropped Error derive +// Only consumed by the *_jsc extension fns; messages come from +// `strum::IntoStaticStr` (variant name == message). #[derive(Debug, strum::IntoStaticStr)] pub enum OptionsFromJsError { - // #[error("InvalidFamily")] InvalidFamily, - // #[error("InvalidSocketType")] InvalidSocketType, - // #[error("InvalidProtocol")] InvalidProtocol, - // #[error("InvalidBackend")] InvalidBackend, - // #[error("InvalidFlags")] InvalidFlags, - // #[error("InvalidOptions")] InvalidOptions, - // #[error("JSError")] JSError, } @@ -367,7 +358,7 @@ impl GetAddrInfoResult { } pub fn address_to_string(address: &Address) -> Result { - // PORT NOTE: reshaped — bun_sys::net::Address exposes family()/as_in4()/ + // Reshaped — bun_sys::net::Address exposes family()/as_in4()/ // as_in6() rather than .in/.in6/.un union views. match address.family() { sock::AF_INET => { @@ -381,7 +372,7 @@ pub fn address_to_string(address: &Address) -> Result { sock::AF_INET6 => { let v6 = address.as_in6().unwrap(); // family() just checked // PERF(port): was stack-fallback alloc — profile if it shows up on a hot path. - // PORT NOTE: Zig formatted via std.net.Address Display ("[addr%scope]:port") + // Zig formatted via std.net.Address Display ("[addr%scope]:port") // then sliced the brackets/port off ("TODO: this is a hack"). Here we // render the bare address directly via ares_inet_ntop, then re-append // the `%scope_id` suffix that std.net.Ip6Address.format emits for diff --git a/src/dotenv/env_loader.rs b/src/dotenv/env_loader.rs index c6225c7d15f5..2a1fafdd2307 100644 --- a/src/dotenv/env_loader.rs +++ b/src/dotenv/env_loader.rs @@ -98,7 +98,6 @@ impl DotEnvBehavior { /// this T2 crate names no `bun_s3_signing` types — see PORTING.md §Dispatch (cold-path, /// upward dep). The high-tier caller constructs the real refcounted `S3Credentials` from /// this POD at the call site. -// TODO(port): once bun_s3_signing TYPE_ONLY move-down lands ≤T2, re-export that struct here. #[derive(Clone, Default)] pub struct S3Credentials { pub access_key_id: Box<[u8]>, @@ -239,7 +238,7 @@ impl<'a> Loader<'a> { pub fn get_s3_credentials(&mut self) -> &S3Credentials { if self.aws_credentials.is_none() { - // PORT NOTE: reshaped for borrowck — Zig stored borrowed `[]const u8` slices into + // reshaped for borrowck — Zig stored borrowed `[]const u8` slices into // the env map; here we copy to `Box<[u8]>` so the cached struct owns its bytes and // we can release the `&self` borrow before writing `&mut self.aws_credentials`. // PERF(port): one-shot, cached — copies are negligible. @@ -473,7 +472,7 @@ impl<'a> Loader<'a> { Some(p) => p, None => return Ok(()), }; - // PORT NOTE: borrowck — `path` borrows `self.map`; `which` writes into `buf` and + // borrowck — `path` borrows `self.map`; `which` writes into `buf` and // returns a borrow of `buf`. Copy the result before mutating `self.map`. let ccache_path: Box<[u8]> = which(&mut buf, path, fs.top_level_dir(), b"ccache") .map(|z| Box::<[u8]>::from(z.as_bytes())) @@ -528,7 +527,7 @@ impl<'a> Loader<'a> { let Some(node) = self.get_node_path(fs, &mut buf) else { return Ok(false); }; - // PORT NOTE: Zig used `fs.dirname_store.append` (interning arena + // Zig used `fs.dirname_store.append` (interning arena // returning 'static slice). RwLock owns a Box; just box here. Box::from(node.as_bytes()) } @@ -647,7 +646,7 @@ impl<'a> Loader<'a> { &mut self, str: &[u8], ) -> Result<(), AllocError> { - // PORT NOTE: Zig built a `logger.Source` here; the only field `Parser` + // Zig built a `logger.Source` here; the only field `Parser` // reads is `.contents`, so go straight to `parse_bytes` and avoid the // `Source.contents: &'static [u8]` lifetime constraint (callers like // `node:util.parseEnv` pass JS-owned non-'static buffers). @@ -728,7 +727,7 @@ impl<'a> Loader<'a> { ) -> Result<(), bun_core::Error> { let dir_handle = bun_sys::Fd::cwd(); - // PORT NOTE: Zig calls `dir.hasComptimeQuery(...)` on a + // Zig calls `dir.hasComptimeQuery(...)` on a // `*FileSystem.DirEntry` (env_loader.zig). `bun_dotenv` sits below // `bun_resolver` in the crate graph, so the directory entry is taken // generically — `bun_resolver::fs::DirEntry` impls `DirEntryProbe`. @@ -834,7 +833,7 @@ impl<'a> Loader<'a> { } } - // PORT NOTE: `iterator()` requires `&mut self`; iterate `keys()` slice instead. + // `iterator()` requires `&mut self`; iterate `keys()` slice instead. for k in self.custom_files_loaded.keys() { loaded_i += 1; if count == 1 || (loaded_i >= count && count > 1) { @@ -874,7 +873,7 @@ impl<'a> Loader<'a> { return Ok(()); } - // PORT NOTE: Zig used `std.fs.Dir.openFile` whose error set names + // Zig used `std.fs.Dir.openFile` whose error set names // (`error.FileNotFound`, `error.FileBusy`, …) don't map 1:1 to errno. // `bun_sys` is errno-based, so the match arms below approximate the Zig // error groups by errno. Any errno not listed propagates (matches the @@ -925,12 +924,11 @@ impl<'a> Loader<'a> { } } - // TODO(port): Zig retained the file buffer in `Source.contents`; here we - // drop it after parsing because `bun_ast::Source.contents` is - // `&'static [u8]` and §Forbidden bans `Box::leak`. The stored `Source` - // is only ever checked for `.is_some()` / its path printed, so dropping - // the bytes is observationally identical. Revisit once `bun_logger` - // grows an owning `contents`. + // Zig retained the file buffer in `Source.contents`; here we drop it + // after parsing because `bun_ast::Source.contents` is `&'static [u8]` + // and §Forbidden bans `Box::leak`. The stored `Source` is only ever + // checked for `.is_some()` / its path printed, so dropping the bytes + // is observationally identical. *self.default_file_slot(base) = Some(bun_ast::Source::init_path_string(base, b"")); Ok(()) } @@ -948,7 +946,7 @@ impl<'a> Loader<'a> { Ok(f) => f, Err(_) => { // prevent retrying - // PORT NOTE: `Source::init_path_string` requires a `'static` path; the + // `Source::init_path_string` requires a `'static` path; the // map key already carries `file_path` (boxed), and the value is never // read for its path/contents — only `.contains()` and key iteration — // so an empty placeholder is observationally identical. @@ -974,8 +972,8 @@ impl<'a> Loader<'a> { } } - // TODO(port): see `load_env_file` — `Source.contents` not retained - // pending the `bun_logger` owning-`Str` rework. + // See `load_env_file` — `Source.contents` is not retained; only + // `.contains()` / key iteration are ever observed. self.custom_files_loaded .put(file_path, bun_ast::Source::default())?; Ok(()) @@ -1149,7 +1147,7 @@ impl<'a> Parser<'a> { return Ok(Some(self.value_buffer.as_slice())); } self.pos = start; - // PORT NOTE: fallthrough to outer loop's `end += 1` (Zig switch fallthrough) + // fallthrough to outer loop's `end += 1` (Zig switch fallthrough) } _ => {} } @@ -1165,7 +1163,7 @@ impl<'a> Parser<'a> { if end >= self.src.len() { return Ok(&self.src[self.src.len()..]); } - // PORT NOTE: reshaped for borrowck — `parse_quoted` returns a borrow of + // reshaped for borrowck — `parse_quoted` returns a borrow of // `self.value_buffer`; capture only its length, then re-borrow the buffer // after the match so the unquoted fallthrough can re-borrow `self`. let quoted_len: Option = match self.src[end] { @@ -1279,7 +1277,7 @@ impl<'a> Parser<'a> { continue; }; let value = self.parse_value::()?; - // PORT NOTE: reshaped for borrowck — value borrows self.value_buffer; copy before map mut. + // reshaped for borrowck — value borrows self.value_buffer; copy before map mut. let value_owned: Box<[u8]> = Box::from(value); let entry = map.map.get_or_put(key)?; if entry.found_existing { @@ -1298,7 +1296,7 @@ impl<'a> Parser<'a> { }; } if !IS_PROCESS && EXPAND { - // PORT NOTE: borrowck — Zig iterates `map` while calling `map.get` inside expandValue. + // borrowck — Zig iterates `map` while calling `map.get` inside expandValue. // Reshaped to index-based iteration: clone the value bytes, run expansion against an // immutable `&Map`, then write back via `values_mut()`. The clone matches the Zig: // values are dupe'd by `_parse` above, so length is bounded by file size. @@ -1323,7 +1321,7 @@ impl<'a> Parser<'a> { /// Same as [`parse`] but takes the source bytes directly. Exists so /// `load_env_file*` can parse a transient `Vec` without constructing a /// `bun_ast::Source` (whose `contents` field is currently `&'static [u8]`). - // PORT NOTE: Zig built a `logger.Source` and passed `&source` — the only + // Zig built a `logger.Source` and passed `&source` — the only // field `Parser` reads is `.contents`, so this is observationally identical. pub(crate) fn parse_bytes( src: &[u8], @@ -1347,8 +1345,9 @@ pub type Value = HashTableValue; #[derive(Default, Clone)] pub struct HashTableValue { - // TODO(port): Zig stored borrowed `[]const u8`; values are sometimes allocator.dupe'd, sometimes - // borrowed from environ. Using Box<[u8]> here for owned-by-default; may want Cow if the copies matter. + // Zig stored borrowed `[]const u8` (sometimes allocator.dupe'd, sometimes + // borrowed from environ); `Box<[u8]>` is owned-by-default, trading some + // copies for uniform ownership. pub value: Box<[u8]>, pub conditional: bool, } @@ -1405,9 +1404,9 @@ impl Map { /// Returns a wrapper around the std.process.EnvMap that does not duplicate the memory of /// the keys and values, but instead points into the memory of the bun env map. - // TODO(refactor): `bun_sys::EnvMap` is `HashMap`, which copies and is - // UTF-8-lossy. Zig's `std.process.EnvMap` stored `[]const u8` borrows. Replace - // `bun_sys::EnvMap` with a `&[u8]`-keyed map and drop the lossy round-trip here. + // `bun_sys::EnvMap` is `HashMap`, which copies and is + // UTF-8-lossy (Zig's `std.process.EnvMap` stored `[]const u8` borrows); + // the lossy round-trip is accepted here. #[allow(clippy::disallowed_methods)] // lossy round-trip documented above pub fn std_env_map(&mut self) -> Result { let mut env_map = bun_sys::EnvMap::default(); @@ -1541,9 +1540,9 @@ impl Map { #[inline] pub fn put_alloc_key(&mut self, key: &[u8], value: &[u8]) -> Result<(), AllocError> { - // TODO(port): Zig stored borrowed `value` here without dupe; Box<[u8]> forces a copy. - // If `HashTableValue.value` ever becomes `Cow`/borrowed storage, re-diverge from - // `put_alloc_key_and_value` (which dupes both key and value). + // Zig stored borrowed `value` here without dupe; `Box<[u8]>` storage + // forces a copy, making this equivalent to `put_alloc_key_and_value` + // (which dupes both key and value). self.put_alloc_key_and_value(key, value) } @@ -1565,7 +1564,7 @@ impl Map { } pub fn json_stringify(&self, writer: &mut impl core::fmt::Write) -> core::fmt::Result { - // PORT NOTE: `iterator()` requires `&mut self`; iterate parallel slices instead. + // `iterator()` requires `&mut self`; iterate parallel slices instead. let count = self.map.count(); writer.write_str("{")?; for (i, (k, v)) in self @@ -1664,7 +1663,7 @@ impl StdEnvMapWrapper { // Drop replaces deinit (only frees hash_map storage; Rust does this automatically) // Zig: `pub var instance: ?*Loader = null;` — global mutable raw pointer, freely re-assignable. -// PORT NOTE: Loader is !Sync (holds `&mut Map`); same single-thread invariant the Zig had. +// Loader is !Sync (holds `&mut Map`); same single-thread invariant the Zig had. // We store a raw `*mut` in an AtomicPtr (overwritable, matches `pub var` semantics) and hand // the raw pointer back to callers so the no-alias `&mut` proof obligation lives at the *call // site*, not here — manufacturing `&'static mut` inside an accessor is aliased-&mut UB the diff --git a/src/errno/lib.rs b/src/errno/lib.rs index 38209edd6679..ca605f1c9651 100644 --- a/src/errno/lib.rs +++ b/src/errno/lib.rs @@ -220,9 +220,8 @@ pub mod posix { /// Kernel errno enum. Zig's `std.posix.E` and Bun's `SystemErrno` share the /// exact same discriminant space on each POSIX target; alias rather than /// duplicate. Resolves to the per-OS `SystemErrno` via the glob re-export - /// above. TODO(port): Zig's `E` uses unprefixed variant names (`PERM`, - /// `NOENT`); `SystemErrno` uses `EPERM`, `ENOENT`. Callers matching on - /// `E::PERM` must migrate to `E::EPERM` (or this becomes a distinct enum). + /// above. Note: Zig's `E` uses unprefixed variant names (`PERM`, `NOENT`); + /// `SystemErrno` uses `EPERM`, `ENOENT` — callers spell `E::EPERM`. pub type E = crate::SystemErrno; /// `stat` mode-flag constants and predicates (Zig: `std.posix.S`). @@ -305,9 +304,8 @@ impl SystemErrno { #[cfg(not(windows))] impl SystemErrno { - // TODO(port): Zig `anytype` accepted any integer width (signed or unsigned). - // i64 covers every concrete call site (errno-range values); revisit if a - // caller passes u64/usize directly. + // Zig `anytype` accepted any integer width (signed or unsigned); `i64` + // covers every concrete call site (errno-range values). // // Windows defines its own `init` (typed dispatch over // DWORD/c_int/Win32Error) in windows_errno.rs, so this impl is POSIX-only. @@ -379,12 +377,34 @@ pub(crate) const fn system_errno_max_dense() -> u32 { SystemErrno::MAX as u32 } +/// Raw Win32 `GetLastError()` code → `SystemErrno` tag name, via the same +/// `Win32Error.toSystemErrno()` table Zig runs FIRST (windows_errno.zig:290), +/// before any `errno_map` lookup. Restores `error.code` fidelity (ENOENT, +/// EACCES, ...) for `?`-propagated `std::io::Error`s on Windows. Exists on all +/// platforms because the `ErrnoNames` link-interface is platform-independent; +/// always `None` off Windows. +#[inline] +pub(crate) fn win32_errno_name(code: u32) -> Option<&'static str> { + #[cfg(windows)] + { + let code = u16::try_from(code).ok()?; + SystemErrno::init_win32_error(windows_errno::Win32Error::from_raw(code)) + .map(<&'static str>::from) + } + #[cfg(not(windows))] + { + let _ = code; + None + } +} + // Wire the above into bun_core's `ErrnoNames` hook. `()` owner — pure // stateless functions; the handle is the const `ErrnoNames::SYS`. bun_core::link_impl_ErrnoNames! { Sys for () => |_this| { name(errno) => system_errno_name(errno), max_dense() => system_errno_max_dense(), + win32_name(code) => win32_errno_name(code), } } diff --git a/src/event_loop/AnyEventLoop.rs b/src/event_loop/AnyEventLoop.rs index b7a3ac617521..a67aaa9f0c8c 100644 --- a/src/event_loop/AnyEventLoop.rs +++ b/src/event_loop/AnyEventLoop.rs @@ -49,7 +49,7 @@ fn jsc_event_loop_handle(js_event_loop: *mut ()) -> JsEventLoop { /// Useful for code that may need an event loop and could be used from either JavaScript or directly without JavaScript. /// Unlike jsc.EventLoopHandle, this owns the event loop when it's not a JavaScript event loop. -// PORT NOTE: Zig `union(EventLoopKind)` — variant order/discriminant must match `crate::EventLoopKind`. +// Variant order/discriminant must match `crate::EventLoopKind`. pub enum AnyEventLoop<'a> { Js { /// Typed handle wrapping the erased `*mut jsc::EventLoop`. The @@ -60,8 +60,7 @@ pub enum AnyEventLoop<'a> { Mini(Box>), } -// PORT NOTE: Zig had `pub const Task = AnyTaskWithExtraContext;` as an associated decl. -// Inherent associated types are unstable in Rust, so expose at module level. +// Inherent associated types are unstable in Rust, so this is exposed at module level. pub type Task = AnyTaskWithExtraContext; impl<'a> Default for AnyEventLoop<'a> { @@ -92,7 +91,6 @@ impl<'a> AnyEventLoop<'a> { } pub fn init() -> AnyEventLoop<'a> { - // PORT NOTE: Zig took `std.mem.Allocator param`; dropped per §Allocators (non-AST crate). AnyEventLoop::Mini(Box::new(MiniEventLoop::init())) } @@ -121,9 +119,8 @@ impl<'a> AnyEventLoop<'a> { } } - // PORT NOTE: Zig `context: anytype` + `@ptrCast(isDone)` erases the fn-ptr - // type at the call into `mini.tick(ctx, *const fn(*anyopaque) bool)`. All - // callers pass a pointer, so we take the erased form directly; callers cast. + // All callers pass a pointer, so we take the erased fn-ptr form directly; + // callers cast. pub fn tick( &mut self, context: *mut core::ffi::c_void, @@ -245,7 +242,6 @@ impl<'a> AnyEventLoop<'a> { // instantiation in the tree is already `'static` (verified: install, patch, // build_command, ChangedFilesFilter, `js()`/`js_current()`). impl AnyEventLoop<'static> { - // PORT NOTE: renamed via raw identifier — `loop` is a Rust keyword. #[inline] pub fn r#loop(&mut self) -> *mut UwsLoop { EventLoopHandle::from_any(self).r#loop() @@ -306,9 +302,9 @@ pub enum EventLoopHandle { /// `Copy`. owner: JsEventLoop, }, - // PORT NOTE: `BackRef` (not `&mut`) because the handle is - // `Copy` and stored in `uws::InternalLoopData` as a non-owning backref — - // matches Zig `*MiniEventLoop`. The pointee is the per-thread singleton + // `BackRef` (not `&mut`) because the handle is `Copy` and + // stored in `uws::InternalLoopData` as a non-owning backref. + // The pointee is the per-thread singleton // (`init_global`) or an `AnyEventLoop::Mini`-owned loop, both of which // strictly outlive every `EventLoopHandle` derived from them — the // [`BackRef`] invariant. Read-only sites use safe `Deref`; the few @@ -378,12 +374,9 @@ impl Drop for EnteredEventLoop { } impl EventLoopHandle { - /// Wrap an erased `*mut jsc::EventLoop`. - // PORT NOTE: Zig `init(anytype)` dispatched on `@TypeOf` over five input - // types. Rust splits by overload: `init` (jsc::EventLoop), `init_mini`, - // `from_any`, plus the trivial `EventLoopHandle → EventLoopHandle` is - // identity. The `*VirtualMachine` overload moves to bun_runtime (it must - // call `vm.eventLoop()`). + /// Wrap an erased `*mut jsc::EventLoop`. (Sibling constructors: + /// `init_mini`, `from_any`; the `*VirtualMachine` form lives in + /// bun_runtime since it must call `vm.eventLoop()`.) /// /// `js_event_loop` is a live erased `*mut jsc::EventLoop` whose owner /// outlives every dispatch through the returned handle. The pointer is not @@ -715,10 +708,6 @@ impl EventLoopHandle { } } } - - // PORT NOTE: Zig `cast(tag)` returned `tag.Type()` at comptime — no Rust - // equivalent. Callers should pattern-match the enum directly. - // PORT NOTE: Zig `allocator()` dropped per §Allocators (non-AST crate). } // ported from: src/event_loop/AnyEventLoop.zig diff --git a/src/event_loop/AnyTaskWithExtraContext.rs b/src/event_loop/AnyTaskWithExtraContext.rs index d97388802075..3104d0480bd2 100644 --- a/src/event_loop/AnyTaskWithExtraContext.rs +++ b/src/event_loop/AnyTaskWithExtraContext.rs @@ -38,9 +38,9 @@ impl AnyTaskWithExtraContext { #[repr(C)] struct Wrapper { any_task: AnyTaskWithExtraContext, - // TODO(port): LIFETIMES.tsv classifies this as BORROW_PARAM (&'a mut T), - // but Wrapper is Box'd and escapes the call frame, so a borrow lifetime - // cannot be expressed. Kept as raw; caller guarantees `ptr` outlives the task. + // Raw on purpose: Wrapper is Box'd and escapes the call frame, so + // a borrow lifetime cannot be expressed. Caller guarantees `ptr` + // outlives the task. wrapped: *mut T, // Extra field vs Zig: Zig monomorphized the callback into `Wrapper.function` // via `comptime fieldName`. Stable Rust has no const fn-pointer generics, @@ -77,12 +77,12 @@ impl AnyTaskWithExtraContext { /// Zig signature: `fn from(this: *@This(), of: anytype, comptime field: []const u8) *@This()` /// — initializes `this` in place to call `@TypeOf(of).field(of, extra)` with /// `ContextType = void`. - // PORT NOTE: Zig used `@field(T, field)` comptime decl lookup; Rust callers + // Zig used `@field(T, field)` comptime decl lookup; Rust callers // pass the fn pointer directly. - // TODO(port): Zig passes `ContextType = void` (the unit type, NOT `anyopaque`); - // `*void` is zero-bit so the callee is effectively `fn(*T)` only. Mapped here - // to `*mut ()` — could be `fn(*mut T)` with the second arg dropped. - // PORT NOTE: name kept as `from` to match Zig; not the `From` trait. + // Zig passes `ContextType = void` (the unit type, NOT `anyopaque`); `*void` + // is zero-bit so the callee is effectively `fn(*T)` only. Mapped here to + // `*mut ()` to keep the two-arg stored ABI uniform. + // Name kept as `from` to match Zig; not the `From` trait. pub fn from(&mut self, of: *mut T, callback: fn(*mut T, *mut ())) -> *mut Self { *self = New::::init(of, callback); std::ptr::from_mut::(self) @@ -102,8 +102,6 @@ impl AnyTaskWithExtraContext { /// Stable Rust cannot take a fn value as a const generic, so `Callback` moves to /// a runtime argument on `init` and is type-erased (ABI-identical: both forms /// are thin fn pointers taking two thin data pointers). -// TODO(port): if a zero-storage comptime form is ever needed, switch to a -// `trait TaskCallback { fn call(&mut self, extra: *mut C); }` bound on `T`. pub struct New(PhantomData<(*mut T, *mut C)>); impl New { @@ -120,13 +118,11 @@ impl New { } } - // TODO(port): Zig's `New(...).wrap(this: ?*anyopaque, extra: ?*anyopaque)` was - // the type-erasing thunk stored in `.callback = wrap`. Because stable Rust + // Zig's `New(...).wrap(this: ?*anyopaque, extra: ?*anyopaque)` was the + // type-erasing thunk stored in `.callback = wrap`. Because stable Rust // can't take `Callback` as a const generic, `init` erases the typed fn - // pointer directly instead — so `wrap` is folded into that cast and - // intentionally omitted here. If this ever switches to a `TaskCallback` - // trait bound on `T`, reintroduce `wrap` as the 2-arg stored thunk. - // PERF(port): Zig used `@call(bun.callmod_inline, Callback, ...)` — profile if hot. + // pointer directly instead — `wrap` is folded into that cast and + // intentionally omitted here. } // ported from: src/event_loop/AnyTaskWithExtraContext.zig diff --git a/src/event_loop/AutoFlusher.rs b/src/event_loop/AutoFlusher.rs index fda8bb215bb2..0ad0fc5f07a6 100644 --- a/src/event_loop/AutoFlusher.rs +++ b/src/event_loop/AutoFlusher.rs @@ -38,10 +38,10 @@ pub(crate) fn erase_flush_callback() -> DeferredRepeatingTask trampoline:: } -// PORT NOTE (b0): Zig passed `*jsc.VirtualMachine` and reached -// `vm.event_loop().deferred_tasks`. To break the event_loop→jsc upward edge, -// callers now pass the `DeferredTaskQueue` directly (it lives in this crate). -// Higher-tier call sites do `&mut vm.event_loop().deferred_tasks` themselves. +// To avoid an event_loop→jsc upward edge, callers pass the +// `DeferredTaskQueue` directly (it lives in this crate) rather than a VM +// pointer. Higher-tier call sites do `&mut vm.event_loop().deferred_tasks` +// themselves. pub(crate) fn register_deferred_microtask_with_type( this: &mut T, deferred: &mut DeferredTaskQueue, @@ -67,8 +67,8 @@ pub(crate) fn unregister_deferred_microtask_with_type_unchecked(this).cast::())); debug_assert!(removed); diff --git a/src/event_loop/ConcurrentTask.rs b/src/event_loop/ConcurrentTask.rs index d51fcc0416f1..b8609a9f6311 100644 --- a/src/event_loop/ConcurrentTask.rs +++ b/src/event_loop/ConcurrentTask.rs @@ -48,7 +48,7 @@ pub struct TaskTag(pub u8); /// order; `bun_runtime::dispatch::run_task` matches on these. Both sides MUST /// agree — adding a variant requires updating both this list and the runtime /// match arm. -// PORT NOTE: Zig `TaggedPointerUnion` derived tags from a comptime type list; +// Zig `TaggedPointerUnion` derived tags from a comptime type list; // Rust splits the table (here) from the type→arm mapping (runtime tier-6). #[allow(non_upper_case_globals)] pub mod task_tag { @@ -212,7 +212,7 @@ impl Task { /// resolved the tag from the comptime type list. Rust expresses the /// type→tag table as the [`Taskable`] trait; the per-type impl supplies /// `T::TAG` and the body is the Zig `TaggedPointer.init(ptr, tag)`. - // PORT NOTE: Zig accepted `anytype` and reflected on `@TypeOf`; Rust takes + // Zig accepted `anytype` and reflected on `@TypeOf`; Rust takes // `*mut T` directly (the only shape Zig admitted). `&mut T` coerces at // call sites. #[inline] @@ -271,7 +271,7 @@ impl Default for ConcurrentTask { } } -// PORT NOTE: Zig packs `auto_delete` into bit 0 of `next` (`PackedNextPtr`) to +// Zig packs `auto_delete` into bit 0 of `next` (`PackedNextPtr`) to // keep `ConcurrentTask` at 16 bytes. The Rust port deliberately splits it back // out: `Task` is already two words here (tag is not packed into the pointer), // so the struct was never 16B, and profiling (build/create-next benches) showed @@ -334,9 +334,7 @@ impl ConcurrentTask { } pub fn create_from(task: *mut T) -> core::ptr::NonNull { - // TODO(port): re-enable once `mark_binding!` macro arity matches - // `ScopedLogger::log` (concurrent bun_core edit changed it to 1-arg). - // bun_core::mark_binding!(); + bun_core::mark_binding!(); Self::create(Task::init(task)) } @@ -349,16 +347,14 @@ impl ConcurrentTask { Self::create(Task::from_boxed(task)) } - // PORT NOTE: callback returns `JsResult<()>` to match `ManagedTask::new`'s stored ABI; + // callback returns `JsResult<()>` to match `ManagedTask::new`'s stored ABI; // Zig accepted both `fn(*T) void` and `fn(*T) JSError!void` via comptime — Rust callers // that have a `fn(*mut T)` should wrap it as `|p| { f(p); Ok(()) }` at the call site. pub fn from_callback( ptr: *mut T, callback: fn(*mut T) -> crate::JsResult<()>, ) -> core::ptr::NonNull { - // TODO(port): re-enable once `mark_binding!` macro arity matches - // `ScopedLogger::log` (concurrent bun_core edit changed it to 1-arg). - // bun_core::mark_binding!(); + bun_core::mark_binding!(); Self::create(ManagedTask::ManagedTask::new(ptr, callback)) } @@ -367,9 +363,7 @@ impl ConcurrentTask { of: *mut T, auto_deinit: AutoDeinit, ) -> &mut ConcurrentTask { - // TODO(port): re-enable once `mark_binding!` macro arity matches - // `ScopedLogger::log` (concurrent bun_core edit changed it to 1-arg). - // bun_core::mark_binding!(); + bun_core::mark_binding!(); *self = ConcurrentTask { task: Task::init(of), next: Link::new(), diff --git a/src/event_loop/DeferredTaskQueue.rs b/src/event_loop/DeferredTaskQueue.rs index ce28e712f266..0c75b537ce37 100644 --- a/src/event_loop/DeferredTaskQueue.rs +++ b/src/event_loop/DeferredTaskQueue.rs @@ -31,7 +31,7 @@ use core::ptr::NonNull; use bun_collections::ArrayHashMap; -// PORT NOTE: Zig `*const fn(*anyopaque) bool`. Declared `extern "C"` so the +// Declared `extern "C"` so the // same fn-pointer type can flow across the FFI boundary (e.g. // `Bun__VM__postDeferredTask`) without an ABI-crossing fn-ptr cast. All in-tree // producers go through monomorphic `extern "C"` trampolines (see @@ -45,9 +45,8 @@ pub struct DeferredTaskQueue { impl DeferredTaskQueue { pub fn post_task(&mut self, ctx: Option>, task: DeferredRepeatingTask) -> bool { - // Zig: `getOrPutValue(ctx, task).found_existing`. - // PORT NOTE: `ArrayHashMap` is currently aliased to std `HashMap`; the - // entry API gives the same semantics (insert-if-absent + report-existing). + // `ArrayHashMap` is currently aliased to std `HashMap`; the entry API + // gives insert-if-absent + report-existing semantics. match self.map.entry(ctx) { bun_collections::hash_map::Entry::Occupied(_) => true, bun_collections::hash_map::Entry::Vacant(v) => { @@ -65,11 +64,11 @@ impl DeferredTaskQueue { } pub fn run(&mut self) { - // PORT NOTE: Zig used `swapRemoveAt(i)` (O(1) by index). The current - // `ArrayHashMap` exposes `keys()/values()` slices and `swap_remove(&K)` - // (O(n) hash lookup) but not `swap_remove_at`. Keys here are `Copy` - // pointers, so copy the key out and remove by key — semantically - // identical (keys are unique), just an extra hash per removal. + // The current `ArrayHashMap` exposes `keys()/values()` slices and + // `swap_remove(&K)` (hash lookup) but not `swap_remove_at` (O(1) by + // index). Keys here are `Copy` pointers, so copy the key out and + // remove by key — semantically identical (keys are unique), just an + // extra hash per removal. // PERF(port): swap_remove(&K) re-hashes; restore swap_remove_at when // bun_collections::ArrayHashMap grows it. let mut i: usize = 0; @@ -82,7 +81,7 @@ impl DeferredTaskQueue { continue; }; - // PORT NOTE: reshaped for borrowck — copy fn ptr out before calling + // Copy the fn ptr out before calling (borrowck). let task = self.map.values()[i]; // SAFETY: `nn` is the live `*mut T` registered by the caller; the // callback contract (Zig `Type.onAutoFlush`) is that `task` may be diff --git a/src/event_loop/EventLoopTimer.rs b/src/event_loop/EventLoopTimer.rs index 45c3e96b1e59..1f2524a1f49e 100644 --- a/src/event_loop/EventLoopTimer.rs +++ b/src/event_loop/EventLoopTimer.rs @@ -123,7 +123,8 @@ impl EventLoopTimer { // // Zig epoch is `u25` so `-%` wraps mod 2^25. Rust stores it in a wider int, // so we mask the wrapping_sub result to 25 bits to preserve that semantics. - // TODO(port): confirm Rust `epoch` field is masked to 25 bits on write too. + // (`TimerFlags::epoch`/`set_epoch` below mask to 25 bits on both read + // and write, so both operands here are already < 2^25.) const U25_MAX: u32 = (1 << 25) - 1; return (b_epoch.wrapping_sub(a_epoch) & U25_MAX) < U25_MAX / 2; } @@ -135,7 +136,7 @@ impl EventLoopTimer { /// If self was created by set{Immediate,Timeout,Interval}, return its /// JS-timer epoch (used for stable ordering of equal-deadline timers). /// - /// PORT NOTE (b0): Zig `jsTimerInternalsFlags` did `@fieldParentPtr` into + /// Zig `jsTimerInternalsFlags` did `@fieldParentPtr` into /// `TimeoutObject`/`ImmediateObject`/`AbortSignalTimeout` (all tier-6 /// runtime types). The container_of dispatch lives in /// `bun_runtime::dispatch::__bun_js_timer_epoch` (link-time extern). @@ -149,13 +150,13 @@ impl EventLoopTimer { /// Fire the timer's callback. /// - /// PORT NOTE (b0): the `match self.tag { … container_of … }` body was + /// In Zig, the `match self.tag { … container_of … }` body was /// hot-dispatch over ~20 tier-6 variant types (Subprocess, DevServer, /// PostgresSQLConnection, …). That match lives in /// `bun_runtime::dispatch::__bun_fire_timer` (link-time extern). `vm` is /// the erased `*mut VirtualMachine`. /// - /// PORT NOTE (noalias re-entrancy): takes `this: *mut Self`, NOT + /// Deliberately takes `this: *mut Self`, NOT /// `&mut self`. `__bun_fire_timer` dispatches via container_of into a /// tier-6 timer object whose JS callback can re-enter and re-derive a /// `&mut EventLoopTimer` to *this same node* (e.g. `clearTimeout()` → @@ -221,7 +222,7 @@ impl Tag { pub struct TimerCallback { pub callback: fn(*mut TimerCallback), - // TODO(port): lifetime — opaque user ctx, no init/deinit found in src/event_loop/ + // Opaque user ctx; ownership stays with whoever installs the callback. pub ctx: Option>, pub event_loop_timer: EventLoopTimer, } diff --git a/src/event_loop/ManagedTask.rs b/src/event_loop/ManagedTask.rs index adc19341d503..a197178978b9 100644 --- a/src/event_loop/ManagedTask.rs +++ b/src/event_loop/ManagedTask.rs @@ -7,7 +7,7 @@ use core::ptr::NonNull; use crate::{JsResult, Task}; pub struct ManagedTask { - // TODO(port): lifetime — opaque userdata pointer round-tripped through `new`/`run` + // Opaque userdata pointer round-tripped through `new`/`run`; raw by design. pub ctx: Option>, pub callback: fn(*mut c_void) -> JsResult<()>, pub cleanup: Option, @@ -15,7 +15,7 @@ pub struct ManagedTask { impl ManagedTask { pub fn task(this: *mut ManagedTask) -> Task { - // PORT NOTE: Zig `Task.init(this)` mapped variant type → tag at comptime. + // Zig `Task.init(this)` mapped variant type → tag at comptime. // Per §Dispatch (tag+ptr), name the tag explicitly. Task::new(crate::task_tag::ManagedTask, this.cast()) } @@ -45,7 +45,7 @@ impl ManagedTask { self.callback = noop; } - // PORT NOTE: reshaped for borrowck / const-generics limitation. + // reshaped for borrowck / const-generics limitation. // Zig `pub fn New(comptime Type, comptime Callback) type { return struct { init, wrap } }` // cannot be expressed in stable Rust because a fn value is not a valid const-generic // parameter. The `wrap` trampoline (which `@ptrCast`/`@alignCast` the opaque ctx back diff --git a/src/event_loop/MiniEventLoop.rs b/src/event_loop/MiniEventLoop.rs index d5a31fd90463..90b1d3b79681 100644 --- a/src/event_loop/MiniEventLoop.rs +++ b/src/event_loop/MiniEventLoop.rs @@ -84,34 +84,36 @@ pub type Task = AnyTaskWithExtraContext; pub struct MiniEventLoop<'a> { pub tasks: Queue, pub concurrent_tasks: ConcurrentTaskQueue, - // PORT NOTE: Zig `*uws.Loop` — raw pointer because the loop is C-owned + // Zig `*uws.Loop` — raw pointer because the loop is C-owned // (created by `uws_get_loop`/`us_create_loop`) and outlives this struct. pub loop_: *mut UwsLoop, - // PORT NOTE: `std.mem.Allocator param` field dropped — non-AST crate uses global mimalloc. + // `std.mem.Allocator param` field dropped — non-AST crate uses global mimalloc. pub file_polls_: Option>, /// Zig: `env: ?*bun.DotEnv.Loader` — mutable; callers (shell spawn, /// `createNullDelimitedEnvMap`) write through it. Stored as `NonNull` /// (BACKREF) so [`EventLoopHandle::env`] can hand out a `*mut` with /// mutable provenance. `'a` is preserved via PhantomData below. pub env: Option>>, - // PORT NOTE: Zig field is `[]const u8` with mixed provenance (literal "", borrowed `cwd` + // Zig field is `[]const u8` with mixed provenance (literal "", borrowed `cwd` // param, or `allocator.dupe`). Never freed in `deinit`. Use Box<[u8]> and dupe on assign. pub top_level_dir: Box<[u8]>, - // TODO(port): lifetime — opaque ctx assigned externally, only read/cleared here. + // Opaque ctx assigned externally; only read/cleared here. pub after_event_loop_callback_ctx: Option>, pub after_event_loop_callback: Option, pub pipe_read_buffer: Option>, // SAFETY: erased `*mut webcore::blob::Store` (tier-6). Constructed via - // `__bun_stdio_blob_store_new`; intrusive-refcounted on the runtime side. - // TODO(port): Blob.Store uses intrusive ref_count (constructed with ref_count=2); - // LIFETIMES.tsv classifies as Arc but IntrusiveArc may be required for FFI compat. + // `__bun_stdio_blob_store_new` with ref_count=2: one owning intrusive ref + // held by this MiniEventLoop (intentionally never released — the + // MiniEventLoop is a never-freed thread-lifetime singleton, matching Zig), + // one for the eventual Blob consumer. If a teardown path is ever added it + // must release via `__bun_stdio_blob_store_deinit`, as `rare_data.rs` does. pub stdout_store: Option>, pub stderr_store: Option>, } thread_local! { pub static GLOBAL_INITIALIZED: Cell = const { Cell::new(false) }; - // PORT NOTE: Zig `threadlocal var global: *MiniEventLoop = undefined;` — raw pointer + // Zig `threadlocal var global: *MiniEventLoop = undefined;` — raw pointer // because the global is heap-allocated once (heap::alloc) and lives for the // thread's lifetime (a true thread-lifetime singleton; never freed in Zig either). pub static GLOBAL: Cell<*mut MiniEventLoop<'static>> = const { Cell::new(core::ptr::null_mut()) }; @@ -119,7 +121,7 @@ thread_local! { /// Returns the thread-local `*mut MiniEventLoop` (Zig: `*MiniEventLoop`). /// -/// PORT NOTE (aliasing): Zig's `*T` aliases freely; returning `&'static mut` +/// Zig's `*T` aliases freely; returning `&'static mut` /// here would let two calls (or `init_global` + `MiniKind::get_vm`) hold /// overlapping `&mut` to the same allocation — UB. Return the raw pointer; /// callers reborrow `&mut` for the scope they need. @@ -133,7 +135,7 @@ pub fn init_global( return GLOBAL.with(|g| g.get()); } let loop_ = MiniEventLoop::init(); - // PORT NOTE: §Forbidden bans `Box::leak` for `&'static`; this is a + // §Forbidden bans `Box::leak` for `&'static`; this is a // thread-lifetime singleton, so use `heap::alloc` (intrusive ownership) // and store the raw pointer in the thread-local — same as Zig // `bun.default_allocator.create` + `threadlocal var global: *MiniEventLoop`. @@ -147,7 +149,7 @@ pub fn init_global( // raw ptr. let global = unsafe { &mut *global_ptr }; - // PORT NOTE: `InternalLoopData::set_parent_event_loop` (typed) lives in a + // `InternalLoopData::set_parent_event_loop` (typed) lives in a // higher tier; the sys-level API is `set_parent_raw(tag, ptr)`. Tag 1 = JS, // tag 2 = mini (matches Zig `EventLoopHandle` discriminant + 1). { @@ -160,7 +162,7 @@ pub fn init_global( }; } - // PORT NOTE: Zig `bun.DotEnv.instance` is a `?*Loader` global. The Rust + // Zig `bun.DotEnv.instance` is a `?*Loader` global. The Rust // port stores it as `AtomicPtr>`. global.env = env.map(NonNull::from).or_else(|| { NonNull::new( @@ -180,7 +182,7 @@ pub fn init_global( // Set top_level_dir from provided cwd or get current working directory if let Some(dir) = cwd { - // PORT NOTE: Zig borrowed `dir`; we dupe to keep Box<[u8]> ownership uniform. + // Zig borrowed `dir`; we dupe to keep Box<[u8]> ownership uniform. global.top_level_dir = Box::<[u8]>::from(dir); } else if global.top_level_dir.is_empty() { let mut buf = bun_paths::PathBuffer::uninit(); @@ -350,7 +352,7 @@ impl<'a> MiniEventLoop<'a> { // which realigns when the contiguous slice is too short, so the // returned slice is always `>= count` long. // - // PORT NOTE: reshaped for borrowck — Zig held `writable` (&mut into self.tasks) while + // reshaped for borrowck — Zig held `writable` (&mut into self.tasks) while // bumping `self.tasks.count` per-iteration (overlapping &mut). Fill the writable slice // first, track items written in a local, then commit via `update()` after the borrow ends. let mut written: usize = 0; @@ -383,7 +385,7 @@ impl<'a> MiniEventLoop<'a> { (*self.loop_ptr()).tick(); (*self.loop_ptr()).dec(); } - // PORT NOTE: Zig `defer this.onAfterEventLoop()` was block-scoped to this `if`. + // Zig `defer this.onAfterEventLoop()` was block-scoped to this `if`. self.on_after_event_loop(); } @@ -408,7 +410,7 @@ impl<'a> MiniEventLoop<'a> { break; } } - // PORT NOTE: Zig `defer this.onAfterEventLoop()` at fn scope; no early returns above. + // Zig `defer this.onAfterEventLoop()` at fn scope; no early returns above. self.on_after_event_loop(); } @@ -430,7 +432,7 @@ impl<'a> MiniEventLoop<'a> { /// per PORTING.md (§reflection) with a caller-supplied `field_offset = /// core::mem::offset_of!(C, )` into the embedded `AnyTaskWithExtraContext`. /// - /// PORT NOTE: the Zig body is dead code — it calls `Task.New(Context, Callback)` + /// the Zig body is dead code — it calls `Task.New(Context, Callback)` /// (wrong arity; `New` takes 3 type args) and `this.enqueueJSCTask(...)` (no such /// decl). Zig's lazy analysis lets this compile because no caller exists. The /// faithful port writes to `self.tasks` (the local non-concurrent FIFO), which is @@ -450,7 +452,7 @@ impl<'a> MiniEventLoop<'a> { // Zig: `@field(ctx, name) = TaskType.init(ctx);` // SAFETY: `task` points at a properly aligned `AnyTaskWithExtraContext` field of `*ctx`. unsafe { task.write(New::::init(ctx, callback)) }; - // Zig: `this.enqueueJSCTask(&@field(ctx, name))` — see PORT NOTE above. + // Zig: `this.enqueueJSCTask(&@field(ctx, name))` — see the note above. self.tasks.write_item(task).expect("unreachable"); } @@ -573,7 +575,7 @@ impl<'a> MiniEventLoop<'a> { impl<'a> Drop for MiniEventLoop<'a> { fn drop(&mut self) { - // PORT NOTE: `tasks.deinit()` is implicit via Queue's Drop. + // `tasks.deinit()` is implicit via Queue's Drop. debug_assert!(self.concurrent_tasks.is_empty()); } } @@ -581,7 +583,7 @@ impl<'a> Drop for MiniEventLoop<'a> { // ───────────────────────────── MiniVM ───────────────────────────── pub struct MiniVM<'a> { - // PORT NOTE: LIFETIMES.tsv classifies this BORROW_PARAM `&'a`, but `file_polls()` + // LIFETIMES.tsv classifies this BORROW_PARAM `&'a`, but `file_polls()` // mutates the loop (lazy-inits the store). Hold `&'a mut` instead of // casting `&T`→`&mut T` (UB, and forbidden by PORTING.md "no raw pointers to silence // borrowck"). Zig's `*MiniEventLoop` was always mutable. @@ -646,7 +648,7 @@ impl EventLoopKindT for JsKind { impl EventLoopKindT for MiniKind { type Loop = MiniEventLoop<'static>; - // PORT NOTE (aliasing): Zig `refType() = *MiniEventLoop` is a freely-aliasing + // Zig `refType() = *MiniEventLoop` is a freely-aliasing // pointer. Returning `&'static mut` would let two `get_vm()` calls (or // `get_vm()` + `init_global()`) hold overlapping `&mut` — UB. Return the raw // pointer (matches `JsKind::Ref = *mut ()`); callers reborrow scoped `&mut`. @@ -667,7 +669,7 @@ pub trait AbstractVM<'a> { fn abstract_vm(self) -> Self::Wrapped; } -// PORT NOTE (b0): `impl AbstractVM for &VirtualMachine` cannot live here +// `impl AbstractVM for &VirtualMachine` cannot live here // without naming the tier-6 `VirtualMachine` type. The impl moves to // `bun_runtime` (move-in pass), which constructs `JsVM { vm, vtable }`. diff --git a/src/event_loop/SpawnSyncEventLoop.rs b/src/event_loop/SpawnSyncEventLoop.rs index 23a05c6975aa..bcd6ef6c93db 100644 --- a/src/event_loop/SpawnSyncEventLoop.rs +++ b/src/event_loop/SpawnSyncEventLoop.rs @@ -97,7 +97,7 @@ pub struct SpawnSyncEventLoop { vm: *mut (), /// Completely separate uws.Loop instance - critical for avoiding recursive event loop execution - // TODO(port): lifetime — FFI-owned handle created via `uws::Loop::create`, freed in Drop via + // FFI-owned handle created via `uws::Loop::create`, freed in Drop via // `Loop::deinit`. Kept as raw because `uws::Loop` is an opaque C type and its address is // stored back into `internal_loop_data` (self-referential w.r.t. `event_loop`). uws_loop: NonNull, @@ -145,15 +145,15 @@ mod handler { } impl SpawnSyncEventLoop { - // TODO(port): in-place init — `self.event_loop` is captured by - // `setParentEventLoop` below, so `Self` must not move after `init` returns. - // Zig caller passes `undefined` storage, hence `MaybeUninit` (out-param ctor exception). - // TODO(refactor): consider `Pin<&mut Self>` or document the no-move invariant at the caller. + // In-place init: `self.event_loop` is captured by `setParentEventLoop` + // below, so `Self` MUST NOT move after `init` returns (no-move invariant + // upheld by the caller). Zig caller passes `undefined` storage, hence + // `MaybeUninit` (out-param ctor exception). pub fn init( this: &mut core::mem::MaybeUninit, vm: *mut (), /* SAFETY: erased *mut VirtualMachine */ ) { - // PORT NOTE: Zig passes a comptime `Handler` type with wakeup/pre/post decls. + // Zig passes a comptime `Handler` type with wakeup/pre/post decls. // The Rust wrapper takes a `LoopHandler` impl with associated-const fn ptrs. let loop_ = uws::Loop::create::(); @@ -177,7 +177,7 @@ impl SpawnSyncEventLoop { // Set up the loop's internal data to point to this isolated event loop // SAFETY: `this` was fully written immediately above so `assume_init_mut` is sound. let this = unsafe { this.assume_init_mut() }; - // PORT NOTE: sys-level API is `set_parent_raw(tag, ptr)`; the typed + // sys-level API is `set_parent_raw(tag, ptr)`; the typed // `set_parent_event_loop` lives in a higher tier. Tag 1 = JS, tag 2 = mini. // `this.event_loop` is the live heap-owned `*mut jsc::EventLoop` // returned by `__bun_spawn_sync_create_event_loop` immediately above — @@ -295,7 +295,7 @@ impl Drop for SpawnSyncEventLoop { } } - // PORT NOTE: Zig order was `event_loop.deinit()` then `uws_loop.deinit()`. + // Zig order was `event_loop.deinit()` then `uws_loop.deinit()`. __bun_spawn_sync_destroy_event_loop(self.event_loop); // SAFETY: uws_loop was returned by `us_create_loop` in `init` and not yet freed. unsafe { uws::Loop::destroy(self.uws_loop.as_ptr()) }; @@ -425,7 +425,7 @@ impl SpawnSyncEventLoop { // On POSIX, the uws tick only polls I/O; callbacks are dispatched later // via the task queue, but we set the flag here uniformly for safety. let _suppress = SuppressMicrotaskDrain::new(self.vm); - // PORT NOTE: Zig `defer` restores at scope exit; RAII Drop mirrors that. + // Zig `defer` restores at scope exit; RAII Drop mirrors that. // Tick the isolated uws loop with the specified timeout // This will only process I/O related to this subprocess diff --git a/src/event_loop/lib.rs b/src/event_loop/lib.rs index e970c9fa131e..8a9a5dab8640 100644 --- a/src/event_loop/lib.rs +++ b/src/event_loop/lib.rs @@ -12,9 +12,9 @@ pub mod ManagedTask; // AnyEventLoop / SpawnSyncEventLoop / MiniEventLoop. // `InternalLoopData::set_parent_event_loop` // is reached via the lower-tier `set_parent_raw(tag, ptr)` + -// `EventLoopHandle::into_tag_ptr()`. Windows-only `MiniVM::platform_event_loop` -// (`uws::Loop::uv_loop`) remains `#[cfg(windows)]`-guarded with a -// `TODO(port)` marker; the POSIX build is gate-free. +// `EventLoopHandle::into_tag_ptr()`. The Windows-only `uv_loop` projection +// lives on `EventLoopHandle::uv_loop` (`#[cfg(windows)]`); the POSIX build is +// gate-free. // ──────────────────────────────────────────────────────────────────────────── #[path = "MiniEventLoop.rs"] diff --git a/src/exe_format/elf.rs b/src/exe_format/elf.rs index 9efb66f96446..cb8737f2c384 100644 --- a/src/exe_format/elf.rs +++ b/src/exe_format/elf.rs @@ -90,7 +90,7 @@ impl ElfFile { return; } - // PORT NOTE: reshaped for borrowck — compute replacement under an + // reshaped for borrowck — compute replacement under an // immutable borrow, then take a mutable borrow for the writes. let replacement: &'static [u8] = { let interp_region = &self.data[interp_offset..][..interp_filesz]; @@ -169,7 +169,7 @@ impl ElfFile { if strtab_end > self.data.len() as u64 { return; } - // PORT NOTE: reshaped for borrowck — copy strtab bounds out so we can + // reshaped for borrowck — copy strtab bounds out so we can // re-borrow self.data mutably below. let strtab_off = usize::try_from(strtab_shdr.sh_offset).expect("int cast"); let strtab_len = usize::try_from(strtab_shdr.sh_size).expect("int cast"); @@ -430,7 +430,7 @@ impl ElfFile { } pub fn write(&self, writer: &mut impl std::io::Write) -> Result<(), bun_core::Error> { - // PORT NOTE: Zig used `writer: anytype` (`std.Io.Writer`); std::io::Write + // Zig used `writer: anytype` (`std.Io.Writer`); std::io::Write // is the canonical Rust equivalent. bun_io has no Write trait. writer.write_all(&self.data)?; Ok(()) @@ -578,7 +578,7 @@ fn host_uses_nix_store_interpreter() -> bool { // Test-only override: lets #29290's regression test force the // Nix-host branch without mutating `/etc/NIXOS` on the shared // rootfs (which would poison concurrent test workers). - // PORT NOTE: env_var .get() returns Option (nullability + // env_var .get() returns Option (nullability // collapsed in the macro port); default-false makes None ≡ false. if env_var::BUN_DEBUG_FORCE_NIX_HOST.get() == Some(true) { return true; @@ -610,7 +610,7 @@ fn host_uses_nix_store_interpreter() -> bool { Ok(fd) => fd, Err(_) => return false, }; - // PORT NOTE: close moved up; fd not needed after read (was `defer fd.close()`). + // close moved up; fd not needed after read (was `defer fd.close()`). let n = match bun_sys::read(fd, &mut buf) { Ok(n) => n, Err(_) => { @@ -663,7 +663,6 @@ fn host_uses_nix_store_interpreter() -> bool { } // --- ELF definitions (from Zig std.elf; defined locally for cross-platform use) --- -// TODO(port): consider moving to a shared bun_exe_format::elf_defs module. const EI_CLASS: usize = 4; const EI_DATA: usize = 5; diff --git a/src/exe_format/macho.rs b/src/exe_format/macho.rs index 5020f3a057ed..9059ef6e644b 100644 --- a/src/exe_format/macho.rs +++ b/src/exe_format/macho.rs @@ -94,7 +94,7 @@ impl MachoFile { let mut found_bun = false; - // PORT NOTE: reshaped for borrowck — capture base ptr as usize before iterating so we can + // reshaped for borrowck — capture base ptr as usize before iterating so we can // compute byte offsets without holding a borrow of self.data across the mutable writes below. let base_addr = self.data.as_ptr() as usize; let mut iter = self.iterator(); @@ -473,7 +473,7 @@ impl MachoFile { } pub fn build(&self, writer: &mut impl std::io::Write) -> Result<(), bun_core::Error> { - // PORT NOTE: Zig used `writer: anytype`; std::io::Write is the canonical + // Zig used `writer: anytype`; std::io::Write is the canonical // Rust equivalent (bun_io has no Write trait). writer.write_all(&self.data)?; Ok(()) @@ -498,8 +498,10 @@ impl MachoFile { Ok(()) } + // Returns `bun_core::Error` (the repo-wide union type) rather than a bespoke + // `MachoError | io::Error` enum: `From` routes through + // `Error::from_name`, so variant names survive into the caller's `e.name()`. pub fn build_and_sign(&self, writer: &mut impl std::io::Write) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set if self.header.cputype == macho::CPU_TYPE_ARM64 && feature_flag::BUN_NO_CODESIGN_MACHO_BINARY.get() != Some(true) { @@ -664,8 +666,8 @@ impl MachoSigner { super_blob_header_size + blob_index_size + code_dir_length } + // `bun_core::Error` union return — see the note on `build_and_sign`. pub(crate) fn sign(&mut self, writer: &mut impl std::io::Write) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set const PAGE_SIZE: usize = MachoSigner::SIGNATURE_PAGE_SIZE; const HASH_SIZE: usize = MachoSigner::SIGNATURE_HASH_SIZE; @@ -751,7 +753,7 @@ impl MachoSigner { self.data.extend_from_slice(id); // Hash and write pages - // PORT NOTE: reshaped for borrowck — index instead of slicing self.data while pushing. + // reshaped for borrowck — index instead of slicing self.data while pushing. let mut off: usize = 0; let end = self.sig_off; while end - off >= PAGE_SIZE { diff --git a/src/exe_format/pe.rs b/src/exe_format/pe.rs index 4b514ad19521..131f469deceb 100644 --- a/src/exe_format/pe.rs +++ b/src/exe_format/pe.rs @@ -335,7 +335,6 @@ impl PEFile { // 5. Read optional header let size_of_optional_header = pe_header.size_of_optional_header; let number_of_sections = pe_header.number_of_sections; - // PORT NOTE: reshaped for borrowck — drop pe_header borrow before re-borrowing data let optional_header = view_at_mut::(&mut data, optional_header_offset)?; // SAFETY: validated bounds above let optional_header = unsafe { &mut *optional_header }; @@ -565,7 +564,7 @@ impl PEFile { // 2. Re-read PE/Optional (pointers may have moved due to resize in strip) let opt = self.get_optional_header_mut()?; // SAFETY: opt points into self.data at validated offset - // PORT NOTE: reshaped for borrowck — capture needed scalars from opt before re-borrowing self.data + // Capture the needed scalars from opt before re-borrowing self.data below. let file_alignment = unsafe { (*opt).file_alignment }; // SAFETY: opt points into self.data at the offset validated by get_optional_header_mut let section_alignment = unsafe { (*opt).section_alignment }; @@ -778,8 +777,6 @@ impl PEFile { /// Write the modified PE file pub fn write(&self, writer: &mut impl std::io::Write) -> Result<(), bun_core::Error> { - // PORT NOTE: Zig used `writer: anytype` (`std.Io.Writer`); std::io::Write - // is the canonical Rust equivalent. bun_io has no Write trait. writer.write_all(&self.data)?; Ok(()) } diff --git a/src/glob/GlobWalker.rs b/src/glob/GlobWalker.rs index 580fe9b099b1..64dc971a2939 100644 --- a/src/glob/GlobWalker.rs +++ b/src/glob/GlobWalker.rs @@ -27,7 +27,7 @@ use bun_core::Error; use bun_core::define_scoped_log; use bun_core::env::IS_WINDOWS; use bun_core::strings; -use bun_core::{String as BunString, ZStr}; +use bun_core::ZStr; use bun_paths::{MAX_PATH_BYTES, PathBuffer, resolve_path}; use bun_sys::dir_iterator as DirIterator; use bun_sys::{self as Syscall, E, Error as SysError, Fd, FdExt, O, Result as Maybe, S, Stat}; @@ -36,7 +36,7 @@ use bun_sys::{self as Syscall, E, Error as SysError, Fd, FdExt, O, Result as May define_scoped_log!(log, Glob, visible); -// PORT NOTE: Zig's `CodepointIterator.Cursor.CodePointType` is `u32` (UnsignedCodepointIterator). +// Note: Zig's `CodepointIterator.Cursor.CodePointType` is `u32` (UnsignedCodepointIterator). // The bun_string Cursor stores `c: i32`; cast at the assignment sites. type Codepoint = u32; fn dummy_filter_false(_val: &[u8]) -> bool { @@ -226,7 +226,6 @@ impl Accessor for SyscallAccessor { // `MatchedPath` was `[]const u8` or `[:0]const u8` depending on `sentinel`. // Without the arena, matched paths are heap-owned; we use `Box<[u8]>` and // include a trailing NUL byte when `SENTINEL == true`. -// TODO(port): MatchedPath sentinel typing — consider a dedicated owned ZStr type. pub type MatchedPath = Box<[u8]>; pub type IgnoreFilterFn = fn(&[u8]) -> bool; @@ -279,12 +278,7 @@ pub type Result_ = Maybe<()>; /// Array hashmap used as a set (values are the keys) /// to store matched paths and prevent duplicates /// -/// BunString is used so that we can call BunString.toJSArray() -/// on the result of `.keys()` to give the result back to JS -/// -/// The only type of string impl we use is ZigString since -/// all matched paths are UTF-8 (DirIterator converts them on -/// windows) and allocated on the arena +/// All matched paths are UTF-8 (DirIterator converts them on windows) /// /// Multiple patterns are not supported so right now this is /// only possible when running a pattern like: @@ -292,35 +286,23 @@ pub type Result_ = Maybe<()>; /// `foo/**/*` /// /// Use `.keys()` to get the matched paths -// PORT NOTE: Zig keys this on `BunString` so `.keys()` can hand a slice +// Zig keys this on `BunString` so `.keys()` can hand a slice // straight to `BunString.toJSArray`. `to_js_array` lives in a `*_jsc` crate // (per PORTING.md §Strings, `.toJS` is only callable there), so the JS-array // fast path moves up-tier anyway and there's no win keeping `BunString` keys // here. Use `StringArrayHashMap<()>` (boxed `[u8]` keys); the JSC consumer -// rebuilds `BunString`s from `.keys()`. -// TODO(refactor): wire `MatchedMapContext` as a `StringArrayHashMap` -// custom context once SENTINEL-aware hashing matters (currently the trailing -// NUL is part of the key so dedupe is still exact). +// rebuilds `BunString`s from `.keys()`. Default hashing includes the trailing +// NUL when `SENTINEL == true`. For keys produced by `join` (the +// `prepare_matched_path` path) probe and stored key both carry the NUL, so +// dedupe is exact there. The symlink path is the exception: +// `prepare_matched_path_symlink` probes with `ZStr::as_bytes()` (NUL-less) +// while its stored keys come from `dupe_z` (NUL included), so in SENTINEL +// mode symlink-match probes never hit and symlink dedupe silently misses — +// a known quirk preserved for parity with GlobWalker.zig, whose +// `BunString.fromBytes(symlink_full_path)` probe is likewise NUL-less +// against NUL-carrying stored keys. pub type MatchedMap = bun_collections::StringArrayHashMap<()>; -pub struct MatchedMapContext; -// TODO(port): ArrayHashMap context trait shape — wire the actual trait. -impl MatchedMapContext { - pub fn hash(&self, this: &BunString) -> u32 { - debug_assert!(this.tag() == bun_core::Tag::ZigString); - let slice = this.byte_slice(); - // For SENTINEL the slice includes trailing NUL; hash excludes it. - // TODO(port): const-generic SENTINEL not reachable here; Zig branched at comptime. - // Thread `SENTINEL` through `MatchedMapContext` (or strip the NUL at - // insert time so the key never carries it). - bun_collections::array_hash_map::hash_string(slice) - } - - pub fn eql(&self, this: &BunString, other: &BunString, _idx: usize) -> bool { - this.eql(other) - } -} - /// The glob walker references the .directory.path so its not safe to /// copy/move this pub enum IterState { @@ -351,7 +333,6 @@ pub struct Directory { pub path: Box, // Zig: `dir_path: [:0]const u8` is a slice into `path` (self-referential). // Store the length and reconstruct on demand. - // TODO(port): self-referential dir_path; may need Pin or raw-ptr slice. pub dir_path_len: usize, /// Active component indices. Multiple indices mean one readdir @@ -506,7 +487,7 @@ impl<'a, A: Accessor, const SENTINEL: bool> Iterator<'a, A, SENTINEL> { ) }; - // PORT NOTE: reshaped for borrowck — `path_buf` aliases `self.walker.path_buf`; + // Note: reshaped for borrowck — `path_buf` aliases `self.walker.path_buf`; // capture the raw ptr+len up front so the &mut borrow ends before // `handle_sys_err_with_path` re-borrows `self.walker`. let root_path = &root_work_item.path; @@ -602,7 +583,7 @@ impl<'a, A: Accessor, const SENTINEL: bool> Iterator<'a, A, SENTINEL> { // NUL in their `.len()`; the logical path drops it (see `work_item_logical_path`). let work_item_path: &[u8] = work_item_logical_path(&work_item.path); log!("transition => {}", bstr::BStr::new(work_item_path)); - // PORT NOTE: reshaped for borrowck — Zig set `iter_state = .{ .directory = .{...} }` + // Note: reshaped for borrowck — Zig set `iter_state = .{ .directory = .{...} }` // up front and then mutated `this.iter_state.directory.*` while also borrowing // `this.walker`. Build the Directory in a local and assign at the end. let mut dir_path_buf = Box::new(PathBuffer::uninit()); @@ -831,7 +812,7 @@ impl<'a, A: Accessor, const SENTINEL: bool> Iterator<'a, A, SENTINEL> { pub fn next(&mut self) -> Result>, Error> { 'outer: loop { - // PORT NOTE: reshaped for borrowck — take/replace iter_state where needed. + // Note: reshaped for borrowck — take/replace iter_state where needed. match &mut self.iter_state { IterState::Matched(_) => { let IterState::Matched(path) = @@ -868,13 +849,13 @@ impl<'a, A: Accessor, const SENTINEL: bool> Iterator<'a, A, SENTINEL> { ) .with_path(work_item_path))); } - // PORT NOTE: reshaped for borrowck — Zig used `self.path_buf` + // Note: reshaped for borrowck — Zig used `self.path_buf` // both as the scratch buffer here and from inside // `collapseDots`/`handleSysErrWithPath`. In Rust we split-borrow // `path_buf` and `pattern_components` (disjoint fields) for the // write+normalize, then drop the &mut and read via `self.walker`. let mut symlink_full_path_len = work_item_path.len(); - // PORT NOTE: reshaped for borrowck — entry_name is a sub-slice + // Note: reshaped for borrowck — entry_name is a sub-slice // of symlink_full_path; capture range and re-slice later. let entry_start = work_item.entry_start as usize; @@ -1004,7 +985,7 @@ impl<'a, A: Accessor, const SENTINEL: bool> Iterator<'a, A, SENTINEL> { let dir_fd = dir.fd; let at_cwd = dir.at_cwd; let dir_path = dir.dir_path(); - // PORT NOTE: reshaped for borrowck + // Note: reshaped for borrowck let err = self.walker.handle_sys_err_with_path(&err, dir_path); if !at_cwd { self.close_disallowing_cwd(dir_fd); @@ -1353,7 +1334,7 @@ impl SyntaxHint { impl GlobWalker { /// The arena parameter is dereferenced and copied if all allocations go well and nothing goes wrong - // PORT NOTE: out-param constructor reshaped to return Self. + // Note: out-param constructor reshaped to return Self. pub fn init( pattern: &[u8], dot: bool, @@ -1399,7 +1380,7 @@ impl GlobWalker { /// `cwd` should be allocated with the arena /// The arena parameter is dereferenced and copied if all allocations go well and nothing goes wrong - // PORT NOTE: out-param constructor reshaped to return Self. + // Note: out-param constructor reshaped to return Self. pub fn init_with_cwd( pattern: &[u8], cwd: &[u8], @@ -1492,7 +1473,7 @@ impl GlobWalker { Ok(Ok(())) } - // PORT NOTE: associated fn taking `pattern_components` so callers can + // Note: associated fn taking `pattern_components` so callers can // split-borrow it from `&mut self.path_buf` (Zig freely aliased; Rust // forbids `&mut self` + `&mut self.path_buf`). Error path builds SysError // directly from `path_buf` (which is already `self.path_buf` for the @@ -2053,10 +2034,10 @@ impl GlobWalker { let mut width: u32 = 0; while (i as usize) < pattern.len() { let c = pattern[i as usize]; - // PORT NOTE: Zig calls bun.strings.utf8ByteSequenceLength; same table as wtf8. + // Note: Zig calls bun.strings.utf8ByteSequenceLength; same table as wtf8. width = u32::from(strings::wtf8_byte_sequence_length(c)); - // PORT NOTE: GlobWalker.zig duplicates this block across the '\\' (Windows) and '/' + // Note: GlobWalker.zig duplicates this block across the '\\' (Windows) and '/' // arms because Zig has no or-pattern with a comptime guard; merged here. if bun_core::path_sep::is_sep_native(c) { let mut end_byte = i; diff --git a/src/glob/lib.rs b/src/glob/lib.rs index 1b20e24ff839..432a46cca811 100644 --- a/src/glob/lib.rs +++ b/src/glob/lib.rs @@ -10,9 +10,8 @@ pub use crate::glob_walker as walk; pub use crate::matcher::{MatchResult, r#match}; pub use walk::GlobWalker; -// PORT NOTE: Zig passes `null` as the first comptime arg to `GlobWalker_(null, Accessor, sentinel)`. -// In the port, `ignore_filter_fn` is a runtime fn-pointer field (const-generic fn ptrs are -// unstable), so the first param is dropped from the type and supplied at `init()`. +// `ignore_filter_fn` is a runtime fn-pointer field supplied at `init()` rather than a type +// parameter (const-generic fn ptrs are unstable). pub type BunGlobWalker = walk::GlobWalker; pub type BunGlobWalkerZ = walk::GlobWalker; diff --git a/src/glob/matcher.rs b/src/glob/matcher.rs index f768fa0facfe..1bfe91ffae60 100644 --- a/src/glob/matcher.rs +++ b/src/glob/matcher.rs @@ -43,7 +43,7 @@ type BraceStack = BoundedArray; /// alternatives. Patterns that exceed this budget fail to match. const BRACE_BRANCH_BUDGET: u32 = 10_000; -// PORT NOTE: made `pub` — Zig leaks this private type through `pub fn match`; Rust forbids private-in-public. +// `pub` because it appears in the signature of `pub fn match` (private-in-public is forbidden). #[derive(Copy, Clone, Eq, PartialEq)] pub enum MatchResult { NoMatch, @@ -145,7 +145,6 @@ pub fn r#match(glob: &[u8], path: &[u8]) -> MatchResult { state.glob_index += 1; } - // PORT NOTE: `BraceStack.init(0) catch unreachable` — zero-length init cannot fail. let mut brace_stack = BraceStack::default(); let mut brace_budget = BRACE_BRANCH_BUDGET; let matched = glob_match_impl( @@ -626,8 +625,7 @@ fn get_unicode(c: &mut u32, clen: &mut u8, glob: &[u8], glob_index: &mut u32) -> debug_assert!(*clen == 1); const BACKSLASH: u32 = b'\\' as u32; match *c { - // ascii range excluding backslash - // PORT NOTE: Zig `0x0...('\\'-1), '\\'+1...0x7F` — 0x5C is '\\' + // ascii range excluding backslash (0x5C) 0x00..=0x5B | 0x5D..=0x7F => { return true; } diff --git a/src/highway/lib.rs b/src/highway/lib.rs index 11427306ebd7..353228248960 100644 --- a/src/highway/lib.rs +++ b/src/highway/lib.rs @@ -288,8 +288,8 @@ pub fn index_of_any_char(haystack: &[u8], chars: &[u8]) -> Option { Some(result) } -// TODO(port): Zig accepts `[]align(1) const u16` (unaligned). Rust `&[u16]` requires -// 2-byte alignment; callers passing unaligned data must go through the raw extern. +// Zig accepted `[]align(1) const u16` (unaligned); Rust `&[u16]` requires +// 2-byte alignment. Callers with unaligned data must go through the raw extern. #[inline(always)] pub fn copy_u16_to_u8(input: &[u16], output: &mut [u8]) { // SAFETY: input.ptr/len readable, output.ptr writable for at least input.len() bytes diff --git a/src/http/AsyncHTTP.rs b/src/http/AsyncHTTP.rs index c2e9d6a2b7e2..262188b02fa1 100644 --- a/src/http/AsyncHTTP.rs +++ b/src/http/AsyncHTTP.rs @@ -33,10 +33,10 @@ pub struct AsyncHTTP<'a> { pub response: Option>, pub request_headers: headers::EntryList, pub response_headers: headers::EntryList, - // TODO(port): lifetime — caller-owned response buffer; never freed here. + // Caller-owned response buffer (raw pointer, lifetime-erased); never freed here. pub response_buffer: *mut MutableString, pub request_body: HTTPRequestBody<'a>, - // PORT NOTE: `std.mem.Allocator param` field dropped — global mimalloc is used everywhere. + // Note: `std.mem.Allocator param` field dropped — global mimalloc is used everywhere. pub request_header_buf: &'a [u8], pub method: Method, pub url: URL<'a>, @@ -192,7 +192,7 @@ fn make_client<'a>( url, connected_url: URL::default(), verbose: HTTPVerboseLevel::None, - // PORT NOTE: DEFAULT_REDIRECT_COUNT (= 127) is crate-private in lib.rs; + // Note: DEFAULT_REDIRECT_COUNT (= 127) is crate-private in lib.rs; // duplicated as a literal here. remaining_redirect_count: 127, allow_retry: false, @@ -228,7 +228,7 @@ fn make_client<'a>( pub fn load_env(logger: &mut Log, env: &DotEnvLoader) { if let Some(max_http_requests) = env.get(b"BUN_CONFIG_MAX_HTTP_REQUESTS") { - // PORT NOTE: env vars are bytes — never round-trip through &str. Zig used std.fmt.parseInt + // Note: env vars are bytes — never round-trip through &str. Zig used std.fmt.parseInt // on []const u8 directly; map to the byte-slice parser in bun_core::strings. let max: u16 = match bun_core::parse_int::(max_http_requests, 10) { Ok(v) => v, @@ -267,7 +267,7 @@ pub fn load_env(logger: &mut Log, env: &DotEnvLoader) { pub struct Options<'a> { pub http_proxy: Option>, pub proxy_headers: Option, - // PORT NOTE: Zig had `?[]u8` (mutable slice); only ever read, so `&[u8]` here. + // Note: Zig had `?[]u8` (mutable slice); only ever read, so `&[u8]` here. pub hostname: Option<&'a [u8]>, pub signals: Option, pub unix_socket_path: Option, @@ -344,11 +344,11 @@ impl<'a> AsyncHTTP<'a> { } pub fn clear_data(&mut self) { - // PORT NOTE: `response_headers.deinit(allocator)` becomes drop-in-place via assignment. + // Note: `response_headers.deinit(allocator)` becomes drop-in-place via assignment. self.response_headers = headers::EntryList::default(); self.request = None; self.response = None; - // PORT NOTE: ZigString.Slice ownership — Drop releases WTF/owned variants; + // Note: ZigString.Slice ownership — Drop releases WTF/owned variants; // assigning EMPTY runs Drop on the old value. self.client.unix_socket_path = ZigStringSlice::EMPTY; } @@ -359,7 +359,7 @@ impl<'a> AsyncHTTP<'a> { // ────────────────────────────────────────────────────────────────────────── struct Preconnect { - // TODO(port): self-referential — `async_http.response_buffer` borrows + // Self-referential — `async_http.response_buffer` borrows // `self.response_buffer`. Zig relied on stable heap addresses from // `bun.TrivialNew`. `Option` so we can write the field after the heap // address is fixed (late-init); `None` is never observed after `preconnect()` @@ -381,7 +381,7 @@ impl Preconnect { .as_mut() .expect("Preconnect.async_http set in preconnect()") .clear_data(); - // PORT NOTE: Zig `async_http.client.deinit()` — handled by Drop when + // Note: Zig `async_http.client.deinit()` — handled by Drop when // the Box is reclaimed below. if (*this).is_url_owned { // SAFETY: `is_url_owned` is the caller's promise that `url.href` @@ -473,13 +473,13 @@ impl<'a> AsyncHTTP<'a> { let signals = options.signals.unwrap_or_default(); let http_proxy = options.http_proxy.clone(); - // PORT NOTE: reshaped for borrowck — Zig wrote `this.* = .{ .client = undefined, ... }` + // Note: reshaped for borrowck — Zig wrote `this.* = .{ .client = undefined, ... }` // then `this.client = .{...}`. Rust has no `undefined`; build the client first and move // it into the AsyncHTTP literal. let client = make_client( method, url.clone(), - // PORT NOTE: Zig stored the same `headers` value in both `AsyncHTTP.request_headers` + // Note: Zig stored the same `headers` value in both `AsyncHTTP.request_headers` // and `client.header_entries` (shallow copy of the MultiArrayList header → shared // backing storage). `MultiArrayList` in Rust owns its allocation, so clone here. headers.clone().expect("OOM"), @@ -583,7 +583,7 @@ impl<'a> AsyncHTTP<'a> { headers_buf, response_buffer, request_body, - // PORT NOTE: Zig passed `undefined` for callback in sync mode. + // Note: Zig passed `undefined` for callback in sync mode. noop_callback(), redirect_type, Options { @@ -605,7 +605,7 @@ impl<'a> AsyncHTTP<'a> { // ────────────────────────────────────────────────────────────────────────── // 32 pointers much cheaper than 1000 pointers -// PORT NOTE: `bun_threading::Channel` requires `T: Copy`, which +// Note: `bun_threading::Channel` requires `T: Copy`, which // `HTTPClientResult` is not. `send_sync` is a one-shot blocking handoff, so a // Guarded>+Condvar is the exact semantics needed. pub struct SingleHTTPChannel { @@ -647,7 +647,7 @@ fn send_sync_callback( // SAFETY: `async_http` is the HTTP-thread copy (inside ThreadlocalAsyncHTTP) // and `real` was set to the caller's stack/heap AsyncHTTP before scheduling. let async_http = unsafe { &mut *async_http }; - // PORT NOTE: Zig did `async_http.real.?.* = async_http.*` (whole-struct + // Note: Zig did `async_http.real.?.* = async_http.*` (whole-struct // bitwise copy back into the original) then re-seated `response_buffer`. // `AsyncHTTP` is not `Copy`/`Clone` in Rust and a raw `ptr::read`/`ptr::write` // would duplicate owned fields that are later dropped on both sides; instead @@ -682,7 +682,7 @@ impl<'a> AsyncHTTP<'a> { pub fn send_sync(&mut self) -> Result, bun_core::Error> { crate::http_thread::init(&Default::default()); - // PORT NOTE: Zig leaked `ctx` (never destroyed). `Box::leak` is forbidden + // Note: Zig leaked `ctx` (never destroyed). `Box::leak` is forbidden // (PORTING.md §Forbidden); allocate via `heap::alloc` and reclaim once // the single sync callback has fired and we've read the result. let ctx = bun_core::heap::into_raw_nn(Box::new(SingleHTTPChannel::init())); @@ -724,7 +724,7 @@ impl<'a> AsyncHTTP<'a> { async_http: *mut AsyncHTTP<'static>, result: HTTPClientResult<'_>, ) { - // PORT NOTE: kept as raw `*mut` throughout — `this == async_http` (set in + // Note: kept as raw `*mut` throughout — `this == async_http` (set in // `on_start`) and the `!has_more` branch frees that allocation, so a // `&mut self` would be left dangling across the tail of the function // (UB under stacked borrows). @@ -751,17 +751,31 @@ impl<'a> AsyncHTTP<'a> { (*this).state.store(State::Fail, Ordering::Relaxed); } - // PORT NOTE: Zig logged `socket_async_http_abort_tracker.count()` here - // and did `tracker.shrinkAndFree(count)` when `capacity()>10_000 && - // count()<100`. `bun_collections::ArrayHashMap` does not yet expose - // `capacity()`/`shrink_and_free()`. - // TODO(port): wire `bun_collections::ArrayHashMap::{capacity,shrink_and_free}` once they exist and call them under the same guard. + // Zig: AsyncHTTP.zig:386-394 — log the tracker count, then shrink + // the abort tracker back down once it has drained from a + // high-water mark (capacity > 10_000 entries but fewer than 100 + // live), so long-lived heavy-fetch processes don't pin the peak + // allocation forever. HTTP-thread-only access to the global. + { + let tracker = crate::abort_tracker(); + let count = tracker.count(); + if count > 0 { + bun_core::scoped_log!( + AsyncHTTP, + "socket_async_http_abort_tracker count: {}", + count + ); + } + if tracker.capacity() > 10_000 && count < 100 { + tracker.shrink_and_free(count); + } + } let has_more = result.has_more; if has_more { callback.run(async_http, result); } else { - // PORT NOTE: Zig `this.client.deinit()` runs BEFORE the user + // Note: Zig `this.client.deinit()` runs BEFORE the user // callback, then `threadlocal_http.deinit()` (a `TrivialDeinit` // — frees the box, NO field destructors) runs after. // @@ -818,7 +832,7 @@ impl<'a> AsyncHTTP<'a> { in_flight.swap_remove(i); } } - // PORT NOTE: Zig `defer threadlocal_http.deinit()` is + // Note: Zig `defer threadlocal_http.deinit()` is // `bun.TrivialDeinit` — it frees the heap slot WITHOUT running // any field destructors. Reclaiming as `Box<_>` here would // drop the bitwise-shared fields enumerated above and @@ -884,7 +898,7 @@ impl<'a> AsyncHTTP<'a> { ); self.elapsed = http_thread_timer_read(); - // PORT NOTE: Zig reassigned `response_buffer.allocator = bun.http.default_allocator` + // Note: Zig reassigned `response_buffer.allocator = bun.http.default_allocator` // when capacity was 0. MutableString in Rust uses the global allocator // unconditionally; nothing to do. @@ -896,7 +910,7 @@ impl<'a> AsyncHTTP<'a> { NonNull::new(self.response_buffer).expect("response_buffer set in init"), ); - // PORT NOTE: `HTTPRequestBody` is not `Clone` (the `Stream` arm holds an + // Note: `HTTPRequestBody` is not `Clone` (the `Stream` arm holds an // intrusive refcount). Zig passed it by value (shallow copy). Move owned // payloads into the client and leave a detached placeholder so Drop on // `self.request_body` is a no-op. @@ -909,7 +923,7 @@ impl<'a> AsyncHTTP<'a> { // HTTPCallbackPair / HTTPChannel / HTTPChannelContext // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: `HTTPCallbackPair` was Zig anonymous tuple `.{ *AsyncHTTP, HTTPClientResult }`. +// Note: `HTTPCallbackPair` was Zig anonymous tuple `.{ *AsyncHTTP, HTTPClientResult }`. // `bun_threading::Channel` requires `T: Copy`, which `HTTPClientResult` is not, so the // `HTTPChannel` here boxes the pair and ships the pointer (which IS `Copy`) through // a static-buffer channel. The receiver takes ownership of the Box. @@ -922,7 +936,6 @@ pub type HTTPChannel = bun_threading::Channel< pub struct HTTPChannelContext<'a> { pub http: AsyncHTTP<'a>, - // TODO(port): lifetime — no init/assignment found in src/http/; appears unused. // BACKREF: set once by the owner before scheduling; the channel outlives // every callback dispatched through it (Zig dereferenced unconditionally). pub channel: Option>, diff --git a/src/http/CertificateInfo.rs b/src/http/CertificateInfo.rs index 0a7382f02a43..a55d03665f4c 100644 --- a/src/http/CertificateInfo.rs +++ b/src/http/CertificateInfo.rs @@ -6,9 +6,7 @@ pub struct CertificateInfo { pub hostname: Box<[u8]>, } -// PORT NOTE: Zig `deinit` took an allocator and freed `cert`, `cert_error.code`, -// `cert_error.reason`, and `hostname`. In Rust these are `Box<[u8]>` (here and -// in `HTTPCertError`) and drop automatically at scope exit — no explicit `Drop` -// impl needed. The allocator param is deleted per §Allocators. +// All owned fields are `Box<[u8]>` (here and in `HTTPCertError`) and drop +// automatically — no explicit `Drop` impl needed. // ported from: src/http/CertificateInfo.zig diff --git a/src/http/Decompressor.rs b/src/http/Decompressor.rs index 6c550c9c20ef..5f60b8d7d613 100644 --- a/src/http/Decompressor.rs +++ b/src/http/Decompressor.rs @@ -6,7 +6,7 @@ use bun_brotli::BrotliReaderArrayList; use bun_zlib::ZlibReaderArrayList; use bun_zstd::ZstdReaderArrayList; -// PORT NOTE: the `*ReaderArrayList<'a>` types carry a `&'a mut Vec` borrow +// Note: the `*ReaderArrayList<'a>` types carry a `&'a mut Vec` borrow // of the output buffer (and a `&'a [u8]` of the input). The Zig held them by // value with the `ArrayListUnmanaged` aliased into the reader (raw ptr/len/cap // triple). In Rust we erase the borrow to `'static` and uphold the same @@ -60,12 +60,11 @@ unsafe fn seat<'a>(input: &'a [u8], out: &'a mut Vec) -> (&'static [u8], &'s const MAX_DECOMPRESSED_BODY_SIZE: usize = 1024 * 1024 * 1024; impl Decompressor { - // PORT NOTE: Zig `deinit` called `that.deinit()` on the active reader and + // Note: Zig `deinit` called `that.deinit()` on the active reader and // reset to `.none`. The boxed readers' `Drop` impls call `end()`, so an // explicit `Drop` is unnecessary. Callers that want a mid-lifecycle reset // assign `*self = Decompressor::None`. - // TODO(port): narrow error set pub fn update_buffers( &mut self, encoding: Encoding, @@ -85,7 +84,7 @@ impl Decompressor { let mut reader = ZlibReaderArrayList::init_with_options_and_list_allocator( input, out, - // PORT NOTE: Zig passed `body_out_str.allocator` and + // Note: Zig passed `body_out_str.allocator` and // `bun.http.default_allocator`; dropped per §Allocators. bun_zlib::Options { // zlib.MAX_WBITS = 15 @@ -110,7 +109,7 @@ impl Decompressor { let mut reader = BrotliReaderArrayList::new_with_options( input, out, - // PORT NOTE: Zig passed `body_out_str.allocator`; dropped per §Allocators. + // Note: Zig passed `body_out_str.allocator`; dropped per §Allocators. &Default::default(), )?; reader.max_output_size = MAX_DECOMPRESSED_BODY_SIZE; @@ -121,7 +120,7 @@ impl Decompressor { let mut reader = ZstdReaderArrayList::init_with_list_allocator( input, out, - // PORT NOTE: Zig passed `body_out_str.allocator` and + // Note: Zig passed `body_out_str.allocator` and // `bun.http.default_allocator`; dropped per §Allocators. )?; reader.max_output_size = MAX_DECOMPRESSED_BODY_SIZE; @@ -139,7 +138,7 @@ impl Decompressor { reader.zlib.avail_in = buffer.len() as u32; let initial = body_out_str.list.len(); - // PORT NOTE: Zig `expandToCapacity()` set `len = capacity` so the + // Note: Zig `expandToCapacity()` set `len = capacity` so the // zlib output pointers could write into the spare region while // `read_all` later truncated back to `total_out`. `read_all`'s // grow-on-avail_out==0 path reads `list_ptr.len()` to compute the @@ -152,7 +151,7 @@ impl Decompressor { if body_out_str.list.capacity() == initial { body_out_str.list.reserve(4096); } - // PORT NOTE: Zig `reader.list = body_out_str.list` aliased the + // Note: Zig `reader.list = body_out_str.list` aliased the // ArrayListUnmanaged header by value. The Rust reader keeps a // `&mut Vec` instead; re-seat it in case the response buffer // was swapped between chunks. After re-seating, derive @@ -178,7 +177,7 @@ impl Decompressor { let (input, out) = unsafe { seat(buffer, &mut body_out_str.list) }; reader.input = input; reader.total_in = 0; - // PORT NOTE: Zig aliased the ArrayList header; re-seat list_ptr instead. + // Note: Zig aliased the ArrayList header; re-seat list_ptr instead. reader.list_ptr = out; reader.total_out = initial; } @@ -188,7 +187,7 @@ impl Decompressor { let (input, out) = unsafe { seat(buffer, &mut body_out_str.list) }; reader.input = input; reader.total_in = 0; - // PORT NOTE: Zig aliased the ArrayList header; re-seat list_ptr instead. + // Note: Zig aliased the ArrayList header; re-seat list_ptr instead. reader.list_ptr = out; reader.total_out = initial; } @@ -200,7 +199,6 @@ impl Decompressor { Ok(()) } - // TODO(port): narrow error set pub fn read_all(&mut self, is_done: bool) -> Result<(), bun_core::Error> { match self { Decompressor::Zlib(zlib) => zlib.read_all(is_done)?, diff --git a/src/http/H2Client.rs b/src/http/H2Client.rs index 2c780fc0e43f..cef5818a757e 100644 --- a/src/http/H2Client.rs +++ b/src/http/H2Client.rs @@ -12,8 +12,7 @@ use core::sync::atomic::AtomicI32; /// Advertised as SETTINGS_INITIAL_WINDOW_SIZE; replenished via WINDOW_UPDATE /// once half has been consumed. -// PORT NOTE: Zig type was `u31` (HTTP/2 window sizes are 31-bit); Rust has no -// `u31`, so widen to `u32`. Value `1 << 24` is well within range. +// HTTP/2 window sizes are 31-bit on the wire; `1 << 24` is well within range. pub(crate) const LOCAL_INITIAL_WINDOW_SIZE: u32 = 1 << 24; /// Advertised as SETTINGS_MAX_HEADER_LIST_SIZE and enforced as a hard cap on @@ -37,10 +36,9 @@ pub const WRITE_BUFFER_CONTROL_LIMIT: usize = 1024 * 1024; /// Live-object counters for the leak test in fetch-http2-leak.test.ts. /// Incremented at allocation, decremented in deinit. Read from the JS thread /// via TestingAPIs.liveCounts so they must be atomic. -// PORT NOTE: Zig names are `live_sessions`/`live_streams` (snake_case module -// vars). Kept verbatim so cross-crate readers (`bun_http_jsc`) and the gated -// submodules see the same identifier the Zig uses; SCREAMING_SNAKE aliases -// preserved for the existing internal references. +// Lower-case names kept so cross-crate readers (`bun_http_jsc`) and the gated +// submodules share one identifier; SCREAMING_SNAKE aliases preserved for the +// existing internal references. #[allow(non_upper_case_globals)] pub static live_sessions: AtomicI32 = AtomicI32::new(0); #[allow(non_upper_case_globals)] @@ -66,10 +64,6 @@ pub use client_session::ClientSession; pub use pending_connect::PendingConnect; pub use stream::Stream; -// PORT NOTE: Zig had `pub const TestingAPIs = @import("../http_jsc/headers_jsc.zig").H2TestingAPIs;` -// — a `*_jsc` alias. Deleted per PORTING.md: `to_js`/host-fn surfaces live in the -// `*_jsc` crate via extension traits; the base crate has no mention of jsc. - // ═══════════════════════════════════════════════════════════════════════ // Thin `h2_*` forwarders on HTTPClient / HTTPContext that the h2_client // modules call. The real bodies live in lib.rs diff --git a/src/http/H3Client.rs b/src/http/H3Client.rs index 9acb9922725c..423052a2d212 100644 --- a/src/http/H3Client.rs +++ b/src/http/H3Client.rs @@ -46,10 +46,9 @@ pub use stream::Stream; /// Live-object counters for the leak test in fetch-http3-client.test.ts. /// Incremented at allocation, decremented in deinit. Read from the JS thread /// via TestingAPIs.quicLiveCounts so they must be atomic. -// PORT NOTE: Zig names are `live_sessions`/`live_streams` (snake_case module -// vars). Kept verbatim so cross-crate readers (`bun_http_jsc`) and the gated -// submodules see the same identifier the Zig uses; SCREAMING_SNAKE aliases -// preserved for the existing internal references. +// Lower-case names kept so cross-crate readers (`bun_http_jsc`) and the gated +// submodules share one identifier; SCREAMING_SNAKE aliases preserved for the +// existing internal references. #[allow(non_upper_case_globals)] pub static live_sessions: AtomicU32 = AtomicU32::new(0); #[allow(non_upper_case_globals)] diff --git a/src/http/HTTPCertError.rs b/src/http/HTTPCertError.rs index ae3dd64e7c44..8441ab837be5 100644 --- a/src/http/HTTPCertError.rs +++ b/src/http/HTTPCertError.rs @@ -2,9 +2,10 @@ use bun_core::ZStr; pub struct HTTPCertError { pub error_no: i32, - // TODO(port): CertificateInfo.deinit frees code/reason — ownership unclear - // (borrowed in onHandshake, owned via dupeZ in CertificateInfo / http.zig:115). - // May need owned NUL-terminated type (Box / ZString) instead of &'static. + // `code`/`reason` borrow process-lifetime static string tables (uSockets' + // verify-error strings and BoringSSL's `X509_verify_cert_error_string` + // literals — see the SAFETY note in `from_verify_error`), so unlike Zig's + // `dupeZ` + `CertificateInfo.deinit` pair, nothing owns or frees them here. pub code: &'static ZStr, pub reason: &'static ZStr, } @@ -30,9 +31,9 @@ impl HTTPCertError { /// uSockets API populates both together or neither). pub fn from_verify_error(ssl_error: bun_uws::us_bun_verify_error_t) -> Self { /// Borrow a NUL-terminated C string from uSockets as `&'static ZStr`. - /// The string is owned by the long-lived SSL session and outlives the - /// `on_handshake` dispatch; widened to `'static` to match the field - /// type (see TODO above re: ownership). + /// Both sources are process-lifetime static string tables (see the + /// SAFETY note below), so the `'static` widen is genuine, not a + /// convenience. #[inline] fn zstr(p: *const core::ffi::c_char) -> &'static ZStr { // SAFETY: (`bun_ptr::Interned`-style audit — Population A, diff --git a/src/http/HTTPContext.rs b/src/http/HTTPContext.rs index ef19a9a2a260..9c4454864c5c 100644 --- a/src/http/HTTPContext.rs +++ b/src/http/HTTPContext.rs @@ -22,7 +22,7 @@ const MAX_KEEPALIVE_HOSTNAME: usize = 128; /// Zig: `fn NewHTTPContext(comptime ssl: bool) type { return struct { ... } }` /// The const-generic `SSL` is load-bearing for monomorphization (gates hot /// inner-loop branches); do not demote to a runtime bool. -// PORT NOTE: renamed NewHTTPContext→HTTPContext — `New` is a Zig type-factory +// Note: renamed NewHTTPContext→HTTPContext — `New` is a Zig type-factory // naming convention, not part of the type's identity; LIFETIMES.tsv already // aliases `*NewHTTPContext(true)` as `HttpsContext`. #[derive(bun_ptr::CellRefCounted)] @@ -46,15 +46,16 @@ pub struct HTTPContext { pub secure: Option<*mut SSL_CTX>, /// HTTP/2 sessions with at least one active stream, available for /// concurrent attachment if `hasHeadroom()`. - // TODO(port): lifetime — Zig stores `*H2.ClientSession` with manual - // `.ref()`/`.deref()`. Kept as raw pointers; ref/deref is intrusive. + // Zig stores `*H2.ClientSession` with manual `.ref()`/`.deref()`. Kept as + // raw pointers; the intrusive refcount (bumped on insert, dropped on + // removal) is what keeps each session alive while listed here. pub active_h2_sessions: Vec<*mut h2::ClientSession>, /// HTTPClients whose fresh TLS connect is in flight and whose request /// is h2-capable. Subsequent h2-capable requests to the same origin /// coalesce onto the first one's session once ALPN resolves rather /// than each opening its own socket. - // TODO(port): lifetime — owned Box; `pc.deinit()` in Drop. - // The `Box` is load-bearing: `client.pending_h2` holds `NonNull` + // Owned Box; `pc.deinit()` runs in Drop. The `Box` is + // load-bearing: `client.pending_h2` holds `NonNull` // into the box interior; unboxing would dangle it on `Vec` realloc. #[expect(clippy::vec_box)] pub pending_h2_connects: Vec>, @@ -79,7 +80,7 @@ pub(crate) type ActiveSocket = TaggedPtrUnion; -// PORT NOTE: tags assigned 1024 - i to match Zig's `TagTypeEnumWithTypeMap` +// Note: tags assigned 1024 - i to match Zig's `TagTypeEnumWithTypeMap` // (`@typeName(Types[0])` → 1024, descending). impl bun_ptr::tagged_pointer::TypeList for ActiveSocketTypes { const LEN: usize = 4; @@ -295,7 +296,7 @@ impl ExistingSocket { /// `dispatch.zig` reaches `Handler` via this name. The ext stores /// `*anyopaque` (the `ActiveSocket` tagged pointer), so dispatch reads /// it as `**anyopaque` and `Handler` decodes the tag. -// PORT NOTE: was `pub type ActiveSocketHandler = Handler;` (inherent +// Note: was `pub type ActiveSocketHandler = Handler;` (inherent // associated type — unstable). Hoisted to a free alias. pub type ActiveSocketHandler = Handler; @@ -409,7 +410,7 @@ impl HTTPContext { if s.registry_index() != u32::MAX { return; } - // PORT NOTE: `session.ref()` — intrusive refcount bump. + // Note: `session.ref()` — intrusive refcount bump. s.ref_(); s.set_registry_index(u32::try_from(self.active_h2_sessions.len()).expect("int cast")); self.active_h2_sessions.push(session); @@ -490,9 +491,9 @@ impl HTTPContext { &mut self, client: &mut HTTPClient, ) -> Result<(), InitError> { - // TODO(port): `if (!comptime ssl) @compileError("ssl only")` — Rust - // cannot @compileError on a const-generic bool branch without nightly; - // debug_assert until the SSL/non-SSL impls are split. + // Zig: `if (!comptime ssl) @compileError("ssl only")`. Rust cannot + // @compileError on a const-generic bool branch on stable, so this is + // a debug_assert instead of a compile-time rejection. debug_assert!(SSL, "ssl only"); let opts = client .tls_props @@ -843,7 +844,6 @@ impl HTTPContext { client: &mut HTTPClient, socket_path: &[u8], ) -> Result>, Error> { - // TODO(port): narrow error set client.connected_url = client .http_proxy .clone() @@ -872,7 +872,6 @@ impl HTTPContext { hostname_: &[u8], port: u16, ) -> Result>, Error> { - // TODO(port): narrow error set let hostname: &[u8] = if FeatureFlags::HARDCODE_LOCALHOST_TO_127_0_0_1 && hostname_ == b"localhost" { b"127.0.0.1" @@ -884,9 +883,9 @@ impl HTTPContext { .http_proxy .clone() .unwrap_or_else(|| client.url.clone()); - // TODO(port): URL.hostname is a borrowed slice — assigning a local - // overwrites lifetime. Preserved as-is via raw lifetime erasure matching - // the Zig pointer assignment semantics. + // URL.hostname is a borrowed slice — assigning a local would not + // satisfy the field's lifetime, so this uses raw lifetime erasure + // matching the Zig pointer-assignment semantics. client.connected_url.hostname = // SAFETY: hostname borrows either a static literal or `client.url`/ // `client.http_proxy` which outlive `connected_url` for the @@ -989,7 +988,7 @@ impl HTTPContext { client.allow_retry = true; if let Some(session) = found.h2_session { if SSL { - // PORT NOTE: `session.socket = sock` — direct field + // Note: `session.socket = sock` — direct field // write; ClientSession.socket is `HTTPSocket`. // Re-derive `&mut` at each step (via the accessor) // rather than holding one across `register_h2` — that @@ -1099,8 +1098,8 @@ impl Drop for HTTPContext { } } - // PORT NOTE: Vec drop subsumes `active_h2_sessions.deinit()`. - // PORT NOTE: Box Drop subsumes `pc.deinit()`; Vec drop + // Note: Vec drop subsumes `active_h2_sessions.deinit()`. + // Note: Box Drop subsumes `pc.deinit()`; Vec drop // subsumes `pending_h2_connects.deinit()`. // `init_with_opts` can fail before `group.init()` runs (HTTPThread @@ -1111,7 +1110,7 @@ impl Drop for HTTPContext { // Force-close any remaining sockets before unlinking the group so // the loop never dereferences a freed `*Context` via `group->ext`. self.group.close_all(); - // PORT NOTE: SocketGroup deinit must run before the embedding struct + // Note: SocketGroup deinit must run before the embedding struct // is freed (it unlinks from the loop's group list). // SAFETY: group was init()'d in `init`/`init_with_opts`; HTTP-thread-only. unsafe { uws::SocketGroup::destroy(&raw mut self.group) }; @@ -1122,7 +1121,7 @@ impl Drop for HTTPContext { unsafe { bun_boringssl_sys::SSL_CTX_free(c) }; } } - // PORT NOTE: `bun.default_allocator.destroy(this)` is the Box drop + // Note: `bun.default_allocator.destroy(this)` is the Box drop // performed by IntrusiveRc when refcount hits 0; not repeated here. } } @@ -1373,9 +1372,10 @@ pub(crate) struct DeadSocket { garbage: u8, } -// TODO(port): Zig used `pub var dead_socket align(@alignOf(usize)) = .{}` and -// a module-level `var dead_socket = &DeadSocket.dead_socket`. Using a static -// + accessor; revisit if `&'static mut` is needed for TaggedPtrUnion::init. +// Zig used `pub var dead_socket align(@alignOf(usize)) = .{}` and a +// module-level `var dead_socket = &DeadSocket.dead_socket`; a shared static +// + accessor is the Rust equivalent (the pointer is only ever compared, never +// written through). static DEAD_SOCKET: DeadSocket = DeadSocket { garbage: 0 }; #[inline] diff --git a/src/http/HTTPRequestBody.rs b/src/http/HTTPRequestBody.rs index a58cb794d8e0..415848bd32d3 100644 --- a/src/http/HTTPRequestBody.rs +++ b/src/http/HTTPRequestBody.rs @@ -4,9 +4,9 @@ use crate::ThreadSafeStreamBuffer; /// Request body payload. Parameterized over `'a` so callers can hand in /// stack-/arena-borrowed bytes without erasing the lifetime to `&'static` /// at every `AsyncHTTP::init` call site. -// PORT NOTE: no `Owned(Vec)` variant — the body is bitwise-copied across -// threads via `core::ptr::read` in `start_queued_task`, so every arm must be -// trivially-droppable. Zig has only `bytes` / `sendfile` / `stream`. +// No `Owned(Vec)` variant — the body is bitwise-copied across threads via +// `core::ptr::read` in `start_queued_task`, so every arm must be +// trivially-droppable. pub enum HTTPRequestBody<'a> { /// Borrowed bytes — caller guarantees they outlive the request. Bytes(&'a [u8]), @@ -15,10 +15,10 @@ pub enum HTTPRequestBody<'a> { } pub struct Stream { - // PORT NOTE: ThreadSafeStreamBuffer carries an *intrusive* atomic refcount and - // is round-tripped as a raw pointer between the main thread and the HTTP - // thread, so per §Pointers we keep the intrusive form (raw `*mut T` + manual - // ref/deref) instead of `Arc`. + // ThreadSafeStreamBuffer carries an *intrusive* atomic refcount and is + // round-tripped as a raw pointer between the main thread and the HTTP + // thread, so we keep the intrusive form (raw pointer + manual ref/deref) + // instead of `Arc`. pub buffer: Option>, pub ended: bool, } diff --git a/src/http/HTTPThread.rs b/src/http/HTTPThread.rs index 04230f299546..bd8f7a5839c8 100644 --- a/src/http/HTTPThread.rs +++ b/src/http/HTTPThread.rs @@ -15,10 +15,11 @@ use crate::proxy_tunnel::ProxyTunnel; use crate::ssl_config::{self, SSLConfig}; use crate::{AsyncHttp, HTTPContext, HttpClient, InitError, NewHttpContext, h3}; +// Zig had two `Output.scoped(.HTTPThread, ...)` with different visibilities +// (.hidden + .visible); the Rust scope registry keys on name, so they are +// split into two scope names instead. bun_core::declare_scope!(HTTPThread, hidden); // threadlog bun_core::declare_scope!(HTTPThread_log, visible); // log -// TODO(port): Zig had two `Output.scoped(.HTTPThread, ...)` with different visibilities (.hidden + .visible). -// Rust scope registry keys on name; pick one visibility or split scope names. /// SSL context cache keyed by interned SSLConfig pointer. /// Since configs are interned via SSLConfig.GlobalRegistry, pointer equality @@ -198,8 +199,8 @@ impl HttpThread { pub struct HeapRequestBodyBuffer { pub buffer: [u8; 512 * 1024], - // TODO(port): was `std.heap.FixedBufferAllocator` borrowing `buffer` — - // self-referential. Use bun_alloc::FixedBufferAllocator or just a cursor. + // Zig used a `std.heap.FixedBufferAllocator` borrowing `buffer` + // (self-referential); replaced with a plain write cursor. pub cursor: usize, } @@ -208,7 +209,6 @@ unsafe impl bun_core::Zeroable for HeapRequestBodyBuffer {} impl HeapRequestBodyBuffer { pub fn init() -> Box { - // TODO(port): self-referential init; FixedBufferAllocator borrows this.buffer. bun_core::boxed_zeroed() } @@ -229,7 +229,6 @@ pub enum RequestBodyBuffer { // Option<> so Drop can `.take()` the Box and hand it to `put()` (which consumes by value). Heap(Option>), // PERF(port): was std.heap.StackFallbackAllocator(32KB) — inline stack buffer with heap fallback. - // TODO(port): bun_alloc::StackFallbackAllocator Stack(Box<[u8; REQUEST_BODY_SEND_STACK_BUFFER_SIZE]>), } @@ -252,8 +251,9 @@ impl RequestBodyBuffer { } pub(crate) fn to_array_list(&mut self) -> Vec { - // TODO(port): Zig built an ArrayList over self.arena()/self.allocated_slice() with len=0. - // Rust Vec cannot adopt a foreign allocator+buffer; expose a cursor type instead. + // Zig built an ArrayList over self.arena()/self.allocated_slice() with + // len=0; a Rust Vec cannot adopt a foreign allocator+buffer, so this + // allocates a fresh Vec of the same capacity. // PERF(port): was FixedBufferAllocator/StackFallback (allocator() accessor // dropped per PORTING.md non-AST rule; callers should write into allocated_slice() directly). let mut arraylist = Vec::with_capacity(self.allocated_slice().len()); @@ -324,7 +324,8 @@ pub(crate) type Queue = UnboundedQueue>; // deep-clones its slot list. #[derive(Clone)] pub struct InitOpts { - // TODO(port): lifetime — Zig `[]stringZ` borrowed from caller config; copied into spawned thread. + // Zig `[]stringZ` borrowed from caller config; the pointers are copied + // into the spawned thread and only read there (see the Send SAFETY note). pub ca: Vec<*const c_void>, // *const [*:0]const u8 pub abs_ca_file_name: &'static [u8], pub for_install: bool, @@ -453,7 +454,7 @@ impl HttpThread { } pub fn context(&mut self) -> &mut NewHttpContext { - // PORT NOTE: const-generic dispatch over two distinct fields — `NewHttpContext` + // Note: const-generic dispatch over two distinct fields — `NewHttpContext` // and `NewHttpContext` are the same type when IS_SSL, just spelled // differently. Route through a raw-pointer `.cast()` (identity). if IS_SSL { @@ -487,9 +488,7 @@ impl HttpThread { pub fn connect( &mut self, client: &mut HttpClient, - ) -> Result>, bun_core::Error> -// TODO(port): narrow error set - { + ) -> Result>, bun_core::Error> { if IS_SSL { // First SSL connect: materialize the default HTTPS `SSL_CTX` + // socket group now (deferred from `on_start`). Runs once; every @@ -497,7 +496,7 @@ impl HttpThread { // funnels through here before touching `https_context.{group,secure}`. self.ensure_https_context_init(); } - // PORT NOTE: borrowck — `slice()` borrows `client`; capture into a + // Note: borrowck — `slice()` borrows `client`; capture into a // `bun_ptr::RawSlice` (encapsulated outlives-holder invariant) so the // borrow of `client` ends before we hand `&mut client` to // `connect_socket`. Backing storage is `client.unix_socket_path`, which @@ -535,7 +534,7 @@ impl HttpThread { let (hn, pt) = (client.url.hostname, client.url.get_port_auto()); ctx.connect(client, hn, pt) } - // PORT NOTE: NewHttpContext == NewHttpContext here (IS_SSL branch). + // Note: NewHttpContext == NewHttpContext here (IS_SSL branch). .map(|o| o.map(|s| s.cast_ssl::())); } @@ -602,7 +601,7 @@ impl HttpThread { let (hn, pt) = (client.url.hostname, client.url.get_port_auto()); custom_context.connect(client, hn, pt) }; - // PORT NOTE: NewHttpContext == NewHttpContext here (IS_SSL branch). + // Note: NewHttpContext == NewHttpContext here (IS_SSL branch). return result.map(|o| o.map(|s| s.cast_ssl::())); } } @@ -1164,7 +1163,7 @@ fn start_queued_task( ) { // SAFETY: http points to a live AsyncHttp queued by the caller thread. let cloned = crate::ThreadlocalAsyncHttp::new(unsafe { core::ptr::read(http) }); - // PORT NOTE: Zig used struct copy `http.*`; AsyncHttp is byte-copied here + // Note: Zig used struct copy `http.*`; AsyncHttp is byte-copied here // since the original stays valid (real owner is `http`, copy is the // HTTP-thread working set). let cloned = bun_core::heap::release(cloned); @@ -1189,12 +1188,10 @@ fn abort_tracker() -> &'static mut ArrayHashMap { use core::cell::Cell; // ═══════════════════════════════════════════════════════════════════════════ -// init / on_start / process_events — depends on `bun_event_loop::MiniEventLoop` -// (higher-tier) for `loop_.loop_.{tick,inc,dec,num_polls}`. The wakeup path -// above uses the raw `*mut uws::Loop` directly so the rest of the thread -// machinery compiles; the actual event-loop drive stays gated until the tier -// boundary is resolved. -// TODO(port): MiniEventLoop is in bun_event_loop (not in bun_http deps). +// init / on_start / process_events — uses `bun_event_loop::MiniEventLoop` +// for `loop_.loop_.{tick,inc,dec,num_polls}` (`on_start` calls +// `mini_event_loop::init_global` and drives the thread's event loop). The +// wakeup path above still uses the raw `*mut uws::Loop` directly. // ═══════════════════════════════════════════════════════════════════════════ mod _event_loop_draft { @@ -1202,7 +1199,7 @@ mod _event_loop_draft { use std::sync::Once; static INIT_ONCE: Once = Once::new(); - // PORT NOTE: Zig `std.Thread.spawn` + `.detach()` allocates nothing on the + // Note: Zig `std.Thread.spawn` + `.detach()` allocates nothing on the // heap. Rust's `Builder::spawn` allocates an `Arc` (48 B) // shared between the `JoinHandle` and the new thread's TLS `current()`. // Dropping the handle leaves the only strong ref inside the spawned diff --git a/src/http/HeaderBuilder.rs b/src/http/HeaderBuilder.rs index 125e34e5c3cd..94d8cd300481 100644 --- a/src/http/HeaderBuilder.rs +++ b/src/http/HeaderBuilder.rs @@ -19,7 +19,6 @@ impl HeaderBuilder { pub fn allocate(&mut self) -> Result<(), AllocError> { self.content.allocate()?; - // TODO(port): narrow error set self.entries .ensure_total_capacity(self.header_count as usize)?; Ok(()) @@ -81,7 +80,7 @@ impl HeaderBuilder { let _ = self.content.append(name); - // PORT NOTE: reshaped for borrowck — `fmt` returns a borrow into the + // Note: reshaped for borrowck — `fmt` returns a borrow into the // builder buffer; capture its length, then re-read `content.len`. let value_len = self.content.fmt(args).len(); @@ -99,9 +98,9 @@ impl HeaderBuilder { pub fn apply(&mut self, client: &mut crate::HTTPClient) { client.header_entries = core::mem::take(&mut self.entries); - // TODO(port): lifetime — header_buf borrows from self.content's allocation; in Zig this - // is a non-owning slice into the StringBuilder's buffer. Decide whether - // HttpClient should take ownership of the buffer or borrow it. + // header_buf is a non-owning slice into self.content's allocation (in + // Zig, a slice of the StringBuilder's buffer); the builder's owner must + // keep `self.content` alive for as long as the client uses the slice. // SAFETY: content.ptr was set by allocate() and exactly content.len bytes have been written. // Cannot use `written_slice()` here — the borrow must outlive `&self` (`HTTPClient<'a>` // holds it past this call); the lifetime is intentionally unbound. diff --git a/src/http/HeaderValueIterator.rs b/src/http/HeaderValueIterator.rs index f25ef090a0fd..de44d8c69d96 100644 --- a/src/http/HeaderValueIterator.rs +++ b/src/http/HeaderValueIterator.rs @@ -1,9 +1,8 @@ use bun_core::strings; -// PORT NOTE: Zig stored a `std.mem.TokenIterator(u8, .scalar)` field. The Rust -// equivalent (`slice::Split<'_, u8, _>` + `.filter(..)`) has an unnameable -// closure type, so we store the remaining input slice and inline the -// tokenize-by-',' logic in `next()`. Behavior is identical. +// Stores the remaining input slice and inlines the tokenize-by-',' logic in +// `next()` — the std iterator equivalent (`slice::Split` + `.filter(..)`) has +// an unnameable closure type that can't be stored as a field. pub struct HeaderValueIterator<'a> { remaining: &'a [u8], } diff --git a/src/http/Headers.rs b/src/http/Headers.rs index 7c15779cc640..cf97dfd346ac 100644 --- a/src/http/Headers.rs +++ b/src/http/Headers.rs @@ -23,7 +23,7 @@ pub use bun_http_types::Method::HeaderName; // `bun_jsc::FetchHeaders`). pub use bun_http_types::ETag::{HeaderEntry as Entry, HeaderEntryList as EntryList, Headers}; -// PORT NOTE: `pub const toFetchHeaders = @import("../http_jsc/headers_jsc.zig").toFetchHeaders;` +// Note (port): `pub const toFetchHeaders = @import("../http_jsc/headers_jsc.zig").toFetchHeaders;` // deleted — to_fetch_headers lives as an extension-trait method in bun_http_jsc. /// Extension constructors for `Headers` that depend on T5 crates @@ -34,7 +34,7 @@ pub trait HeadersExt { } impl HeadersExt for Headers { - // PORT NOTE: was `!Headers`; all fallible calls were bun.handleOom-wrapped allocations. + // Note (port): was `!Headers`; all fallible calls were bun.handleOom-wrapped allocations. fn from_pico_http_headers(headers: &[picohttp::Header]) -> Headers { let header_count = headers.len(); let mut result = Headers { @@ -54,14 +54,14 @@ impl HeadersExt for Headers { for header in headers { let name = header.name(); let value = header.value(); - // PORT NOTE: Zig used `@truncate` for offsets/lengths; mirror with `as u32` + // Note (port): Zig used `@truncate` for offsets/lengths; mirror with `as u32` // (silent wrap on >4GiB aggregate headers) rather than `try_from().unwrap()`. let name_offset = result.buf.len() as u32; result.buf.extend_from_slice(name); let value_offset = result.buf.len() as u32; result.buf.extend_from_slice(value); - // PORT NOTE: Zig pre-set `entries.len = headers.len` then `set(i, ..)`. + // Note (port): Zig pre-set `entries.len = headers.len` then `set(i, ..)`. // Rust `MultiArrayList` lacks `set_len`; capacity was reserved above // so use `append_assume_capacity` which is equivalent. result.entries.append_assume_capacity(Entry { @@ -79,14 +79,14 @@ impl HeadersExt for Headers { } } -// PORT NOTE: `pub fn deinit` only freed `entries` and `buf`; both are Drop types now — no explicit Drop impl needed. +// Note (port): `pub fn deinit` only freed `entries` and `buf`; both are Drop types now — no explicit Drop impl needed. /// Compute the ETag for `bytes` (xxhash64, hex-lowered, quoted) and append it as /// an `etag` header. Re-exported from `bun_http_types` now that `Headers` is /// the same type in both crates. #[inline] pub fn append_etag(bytes: &[u8], headers: &mut Headers) { - let _ = bun_http_types::ETag::append_to_headers(bytes, headers); + bun_http_types::ETag::append_to_headers(bytes, headers); } // ported from: src/http/Headers.zig diff --git a/src/http/InternalState.rs b/src/http/InternalState.rs index 07d404b059f9..8e32b8b74fab 100644 --- a/src/http/InternalState.rs +++ b/src/http/InternalState.rs @@ -4,7 +4,7 @@ use bun_core::MutableString; use bun_core::{Error, Output}; use crate::{ - CertificateInfo, Decompressor, EXTREMELY_VERBOSE, Encoding, HTTPRequestBody, + CertificateInfo, Decompressor, Encoding, HTTPRequestBody, HTTPResponseMetadata, }; @@ -33,8 +33,8 @@ pub struct InternalState<'a> { pub chunked_decoder: bun_picohttp::phr_chunked_decoder, pub decompressor: Decompressor, pub stage: Stage, - /// This is owned by the user and should not be freed here - // TODO(port): lifetime — user-owned back-reference; no LIFETIMES.tsv row, kept as raw NonNull + /// This is owned by the user and should not be freed here. + /// Non-owning back-reference, kept as a raw `NonNull` (BACKREF per PORTING.md). pub body_out_str: Option>, pub compressed_body: MutableString, pub content_length: Option, @@ -51,7 +51,7 @@ pub struct InternalState<'a> { pub certificate_info: Option, } -// PORT NOTE: was a `packed struct(u8)` in Zig. Kept as a struct-of-bools so the +// was a `packed struct(u8)` in Zig. Kept as a struct-of-bools so the // HTTPClient state machine in lib.rs can use field syntax (`flags.allow_keepalive // = true`) directly; restore packing if size ever matters. #[derive(Clone, Copy)] @@ -150,7 +150,7 @@ impl<'a> InternalState<'a> { } pub fn reset(&mut self) { - // PORT NOTE: allocator param dropped (global mimalloc). + // allocator param dropped (global mimalloc). self.compressed_body = MutableString::init_empty(); self.response_message_buffer = MutableString::init_empty(); @@ -158,9 +158,9 @@ impl<'a> InternalState<'a> { if let Some(body) = body_msg { crate::body_out::as_mut(body).reset(); } - // PORT NOTE: Zig calls `this.decompressor.deinit()` here. The boxed + // Zig calls `this.decompressor.deinit()` here. The boxed // Zlib/Brotli/Zstd readers all impl Drop calling end()/destroy_instance - // (see Decompressor.rs PORT NOTE), so the `*self = ...` assignment below + // (see the note in Decompressor.rs), so the `*self = ...` assignment below // frees the FFI handle via drop glue — no explicit reset needed. // just in case we check and free to avoid leaks @@ -236,7 +236,6 @@ impl<'a> InternalState<'a> { self.flags.received_last_chunk } - // TODO(port): narrow error set pub fn decompress_bytes( &mut self, buffer: &[u8], @@ -252,15 +251,9 @@ impl<'a> InternalState<'a> { return Ok(()); } - // PORT NOTE: Zig `defer this.compressed_body.reset()` runs on every exit. scopeguard would + // Zig `defer this.compressed_body.reset()` runs on every exit. scopeguard would // hold &mut self.compressed_body across the body and conflict with &mut self.decompressor, // so each early-return below calls `self.compressed_body.reset()` explicitly. - let mut gzip_timer: Option = None; - - if EXTREMELY_VERBOSE { - gzip_timer = Some(std::time::Instant::now()); - } - let mut still_needs_to_decompress = true; if bun_core::feature_flags::is_libdeflate_enabled() { @@ -380,29 +373,21 @@ impl<'a> InternalState<'a> { } } - if EXTREMELY_VERBOSE { - // TODO(port): `gzip_elapsed` is not a field on InternalState in the Zig source either — - // this looks like dead code referencing a removed field. Preserved as a no-op read. - let _ = gzip_timer.map(|t| t.elapsed()); - } - self.compressed_body.reset(); Ok(()) } - // TODO(port): narrow error set pub fn decompress( &mut self, buffer: &MutableString, body_out_str: &mut MutableString, is_final_chunk: bool, ) -> Result<(), Error> { - // PORT NOTE: reshaped for borrowck — Zig passed MutableString by value; we borrow the inner slice. + // reshaped for borrowck — Zig passed MutableString by value; we borrow the inner slice. self.decompress_bytes(buffer.list.as_slice(), body_out_str, is_final_chunk) } - // TODO(port): narrow error set - // PORT NOTE: Zig takes `buffer: MutableString` BY VALUE (a shallow struct copy whose + // Zig takes `buffer: MutableString` BY VALUE (a shallow struct copy whose // `.list.items` aliases the same allocation). Every caller passes `getBodyBuffer().*`, // so `buffer` is always the current body buffer's bytes. To avoid aliased &mut/& under // Stacked Borrows (decompress_bytes mutates `self.compressed_body`; the uncompressed @@ -420,7 +405,7 @@ impl<'a> InternalState<'a> { return Ok(false); } - // PORT NOTE: not `self.body_out_mut()` — `decompress_bytes` below takes + // not `self.body_out_mut()` — `decompress_bytes` below takes // `&mut self` alongside `body_out_str`; the accessor would tie the // borrow to `self`. The free `body_out::as_mut` yields an unbounded // `&mut` to the disjoint caller-owned allocation. diff --git a/src/http/ProxyTunnel.rs b/src/http/ProxyTunnel.rs index 4c4cc9c997ce..aa5a8b4d2ac5 100644 --- a/src/http/ProxyTunnel.rs +++ b/src/http/ProxyTunnel.rs @@ -38,10 +38,8 @@ pub(crate) fn raw_as_mut<'a>(ptr: *mut ProxyTunnel) -> &'a mut ProxyTunnel { type ProxyTunnelWrapper = SSLWrapper<*mut HTTPClient<'static>>; /// active socket is the socket that is currently being used -// PORT NOTE: Zig used `NewHTTPContext(B).HTTPSocket`; inherent associated types -// are unstable in Rust, so the free `HTTPSocket` alias from http_context -// is used instead. `HTTPSocket` = `uws::SocketHandler` = `NewSocketHandler`, -// so the canonical 3-arm enum lives in `bun_uws` next to its payload type. +// `HTTPSocket` = `uws::SocketHandler` = `NewSocketHandler`, so the +// canonical 3-arm enum lives in `bun_uws` next to its payload type. pub use bun_uws::MaybeAnySocket as Socket; #[derive(bun_ptr::CellRefCounted)] @@ -244,8 +242,6 @@ fn on_open(ctx: *mut HTTPClient) { if let Some(ssl_ptr) = ProxyTunnel::wrapper_ssl(proxy_nn) { let _hostname = this.hostname.unwrap_or(this.url.hostname); - // PORT NOTE: Zig `configureHTTPClient` is `configureHTTPClientWithALPN(ssl, host, .h1)`; - // the Rust port already exposes the ALPN form in `crate::configure_http_client_with_alpn`. // SAFETY: `ssl_ptr` is the live SSL handle from the tunnel's SSLWrapper. let ssl = unsafe { &mut *ssl_ptr.as_ptr() }; if bun_core::is_ip_address(_hostname) { @@ -537,7 +533,7 @@ fn on_close(ctx: *mut HTTPClient) { let proxy_ptr = proxy_nn.as_ptr(); // close_raw still holds `&mut SSLWrapper` on `(*proxy_ptr).wrapper`, so // bump refcount via the disjoint Cell projection. - // PORT NOTE: not a ScopedRef — the matching deref is deferred via + // Not a ScopedRef — the matching deref is deferred via // `schedule_proxy_deref` to avoid freeing within the callback. { let rc = ProxyTunnel::ref_count_of(proxy_nn); @@ -720,9 +716,9 @@ impl ProxyTunnel { let self_nn = NonNull::from(&mut *self); let self_ptr = self_nn.as_ptr(); let _guard = Self::ref_scope(self_nn); - // PORT NOTE: Zig `defer wrapper.flush()` runs AFTER the body but BEFORE - // `defer deref()` (LIFO). We mirror that order explicitly at the single - // exit. flush() → handle_traffic → write_encrypted reenters and touches + // flush() must run AFTER the body but BEFORE the guard's deref; that + // order is written out explicitly at the single exit below. + // flush() → handle_traffic → write_encrypted reenters and touches // `write_buffer`/`socket` via raw projection; we must not hold a // `&mut ProxyTunnel` (or any borrow overlapping those fields) across it. { diff --git a/src/http/SendFile.rs b/src/http/SendFile.rs index 983d5a848a16..46d124b81397 100644 --- a/src/http/SendFile.rs +++ b/src/http/SendFile.rs @@ -22,9 +22,7 @@ impl SendFile { url.is_http() && url.href.len() > 0 } - // PORT NOTE: reshaped — Zig took `NewHTTPContext(false).HTTPSocket` and only ever - // called `socket.fd()` on it. `NewHTTPContext` is part of the still-gated - // mutually-recursive cluster, so accept the resolved fd directly. Callers pass + // Takes the resolved fd directly rather than the socket; callers pass // `socket.fd()`. pub fn write(&mut self, socket_fd: Fd) -> Status { // Zig u63 max == i64::MAX. Clamp `remain` so the signed sendfile count cannot overflow. diff --git a/src/http/Signals.rs b/src/http/Signals.rs index 80ad7ea740f7..7fa6e4805caf 100644 --- a/src/http/Signals.rs +++ b/src/http/Signals.rs @@ -3,8 +3,8 @@ use core::sync::atomic::{AtomicBool, Ordering}; #[derive(Default, Clone, Copy)] pub struct Signals { - // TODO(port): lifetime — these are non-owning pointers into a `Store` held by the caller. - // LIFETIMES.tsv had no entry; classified as BACKREF (raw) per PORTING.md. + // Non-owning pointers into a `Store` held by the caller (BACKREF per + // PORTING.md); the `Store` outlives every `Signals` derived from it. pub header_progress: Option>, pub response_body_streaming: Option>, pub aborted: Option>, diff --git a/src/http/ThreadSafeStreamBuffer.rs b/src/http/ThreadSafeStreamBuffer.rs index dd24f413fd42..2934b3a4290f 100644 --- a/src/http/ThreadSafeStreamBuffer.rs +++ b/src/http/ThreadSafeStreamBuffer.rs @@ -82,10 +82,9 @@ impl ThreadSafeStreamBuffer { pub fn acquire(&mut self) -> &mut StreamBuffer { self.mutex.lock(); - // PORT NOTE: reshaped for borrowck — Zig returns &this.buffer while the - // mutex stays locked until `release()`. Prefer `lock()` (RAII guard) for - // simple critical sections; this split form remains for callers that - // interleave release with disjoint `self` access. + // The mutex stays locked until `release()`. Prefer `lock()` (RAII + // guard) for simple critical sections; this split form remains for + // callers that interleave release with disjoint `self` access. &mut self.buffer } diff --git a/src/http/h2_client/ClientSession.rs b/src/http/h2_client/ClientSession.rs index bfaf125bf681..acfa8f32d9b6 100644 --- a/src/http/h2_client/ClientSession.rs +++ b/src/http/h2_client/ClientSession.rs @@ -26,7 +26,7 @@ pub type Socket = HTTPSocket; const LOCAL_INITIAL_WINDOW_SIZE: u32 = super::LOCAL_INITIAL_WINDOW_SIZE; -// PORT NOTE: Zig `u31`/`u24` widened to u32; range asserts at use sites. +// 31-/24-bit wire fields stored as u32; range asserts at use sites. #[allow(non_camel_case_types)] type u31 = u32; #[allow(non_camel_case_types)] @@ -553,7 +553,8 @@ impl ClientSession { /// end-of-body) are available in the ThreadSafeStreamBuffer. pub fn stream_body_by_http_id(&mut self, async_http_id: u32, ended: bool) { let _guard = self.ref_scope(); - // PORT NOTE: reshaped for borrowck — collect target stream ptr before mutating self. + // Collect the target stream ptr first so no borrow of `self.streams` + // is held while mutating `self` below. let mut target: Option<*mut Stream> = None; for &stream in self.streams.values() { let Some(client) = stream_mut(stream).client_mut() else { @@ -599,7 +600,7 @@ impl ClientSession { self.write_window_update(0, self.conn_unacked_bytes); self.conn_unacked_bytes = 0; } - // PORT NOTE: reshaped for borrowck — collect (id, unacked) pairs before mutating self. + // Collect (id, unacked) pairs before mutating self. let mut updates: Vec<(u32, u32)> = Vec::new(); for &s in self.streams.values() { let s = stream_mut(s); @@ -615,10 +616,10 @@ impl ClientSession { } pub fn flush(&mut self) -> Result { - // PORT NOTE: reshaped for borrowck — capture as `bun_ptr::RawSlice` - // (encapsulated outlives-holder invariant) so the loop can borrow - // `self.socket` while still subslicing `write_buffer`. The buffer is - // not reallocated until `wrote()` after the loop. + // Captured as `bun_ptr::RawSlice` (encapsulated outlives-holder + // invariant) so the loop can borrow `self.socket` while still + // subslicing `write_buffer`. The buffer is not reallocated until + // `wrote()` after the loop. let pending = bun_ptr::RawSlice::new(self.write_buffer.slice()); if pending.is_empty() { return Ok(false); @@ -658,11 +659,10 @@ impl ClientSession { } } else { self.read_buffer.extend_from_slice(incoming); - // PORT NOTE: reshaped for borrowck — `parse_frames` takes - // `&mut self` plus a view into `self.read_buffer`. Capture as - // `bun_ptr::RawSlice`: read_buffer is not reallocated during - // parse_frames (only consumed), so the outlives-holder invariant - // holds for the call. + // `parse_frames` takes `&mut self` plus a view into + // `self.read_buffer`, so capture the view as `bun_ptr::RawSlice`: + // read_buffer is not reallocated during parse_frames (only + // consumed), so the outlives-holder invariant holds for the call. let buf = bun_ptr::RawSlice::new(self.read_buffer.as_slice()); let consumed = dispatch::parse_frames(self, buf.slice()); self.read_buffer.drain_front(consumed); @@ -821,9 +821,9 @@ impl ClientSession { /// Called from the HTTP thread's shutdown queue when a fetch on this /// session is aborted. RST_STREAMs that one request; siblings continue. pub fn abort_by_http_id(&mut self, async_http_id: u32) { - // PORT NOTE: reshaped for borrowck — find index via raw-ptr field read - // first, then swap_remove, so no `&mut HTTPClient` is held across the - // Vec mutation and no `&mut` is materialised during iteration. + // Find the index via a raw-ptr field read first, then swap_remove, so + // no `&mut HTTPClient` is held across the Vec mutation and no `&mut` + // is materialised during iteration. let found = self .pending_attach .iter() @@ -835,7 +835,8 @@ impl ClientSession { self.maybe_release(); return; } - // PORT NOTE: reshaped for borrowck — find target before detaching. + // Find the target before detaching so no borrow of `self.streams` is + // held across the mutation. let mut target: Option<*mut Stream> = None; for &e in self.streams.values() { if stream_ref(e) diff --git a/src/http/h2_client/PendingConnect.rs b/src/http/h2_client/PendingConnect.rs index edc8a884258e..5981c7bf2da5 100644 --- a/src/http/h2_client/PendingConnect.rs +++ b/src/http/h2_client/PendingConnect.rs @@ -14,7 +14,7 @@ use crate::ssl_config::SSLConfig; pub struct PendingConnect { pub hostname: Box<[u8]>, pub port: u16, - // TODO(port): lifetime — compared by pointer identity only, never derefed/freed here + // Compared by pointer identity only, never derefed/freed here; lifetime-erased. pub ssl_config: Option>, /// Whether the client that initiated this in-flight TLS connect requested /// `rejectUnauthorized`. The eventual `ClientSession` records this as @@ -66,7 +66,7 @@ impl PendingConnect { /// Box until scope exit (Zig: `defer pc.deinit()`). pub fn unregister_from(this: *const Self, ctx: &mut NewHTTPContext) -> Option> { let list = &mut ctx.pending_h2_connects; - // PORT NOTE: reshaped for borrowck (was `for + swapRemove + return`) + // reshaped for borrowck (was `for + swapRemove + return`) list.iter() .position(|p| core::ptr::eq(&raw const **p, this)) .map(|i| list.swap_remove(i)) diff --git a/src/http/h2_client/Stream.rs b/src/http/h2_client/Stream.rs index f835c4e3b308..05aa748de5ee 100644 --- a/src/http/h2_client/Stream.rs +++ b/src/http/h2_client/Stream.rs @@ -18,7 +18,7 @@ use crate::HTTPClient; // Drop runs when removed from the map. pub struct Stream { - // PORT NOTE: was u31 (HTTP/2 stream IDs are 31-bit); top bit must stay clear. + // HTTP/2 stream IDs are 31-bit; the top bit must stay clear. pub id: u32, // BACKREF: this Stream is owned by `session.streams`; raw ptr per LIFETIMES class BACKREF. pub session: *mut ClientSession, @@ -98,7 +98,7 @@ impl Stream { /// RFC 9113 §5.1. A `Stream` is created by sending HEADERS, so it starts /// `.open`; `idle`/`reserved` are never represented as objects. END_STREAM /// half-closes one side; both, or any RST_STREAM, transitions to `.closed`. -#[repr(u8)] // PORT NOTE: was enum(u2) +#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq)] pub enum State { Open, @@ -150,7 +150,7 @@ impl Stream { }) } - // PORT NOTE: Stream.zig:rst() re-entered the session via the `session` + // Stream.zig:rst() re-entered the session via the `session` // backref. In Rust that autorefs a second `&mut ClientSession` while // `parse_frames`' `&mut ClientSession` is still live (Stacked-Borrows UB), // so RST is routed through `ClientSession::rst_stream` instead — the diff --git a/src/http/h2_client/dispatch.rs b/src/http/h2_client/dispatch.rs index f632f15f9009..b03d42d72d8e 100644 --- a/src/http/h2_client/dispatch.rs +++ b/src/http/h2_client/dispatch.rs @@ -64,7 +64,7 @@ pub fn parse_frames(session: &mut ClientSession, buf: &[u8]) -> usize { consumed } -// PORT NOTE: Zig's `wire.FrameType` is non-exhaustive (any u8 valid). A +// Frame types are non-exhaustive on the wire (any u8 is valid). A // `#[repr(u8)]` Rust enum is UB for unknown discriminants, so dispatch on the // raw u8. const FT_DATA: u8 = wire::FrameType::HTTP_FRAME_DATA as u8; @@ -140,7 +140,7 @@ pub(crate) fn dispatch_frame( &payload[i..i + wire::SettingsPayloadUnit::BYTE_SIZE], 0, ); - // PORT NOTE: brace-expr copies of packed fields (unaligned-safe). + // Brace-expr copies of packed fields (unaligned-safe). let utype = { unit.type_ }; let uvalue = { unit.value }; match utype { @@ -543,7 +543,7 @@ pub(crate) fn dispatch_frame( /// Feed an orphaned (untracked-stream) header block through the HPACK /// decoder purely to keep the dynamic table in sync, then discard. pub fn decode_discard_orphan(session: &mut ClientSession) { - // PORT NOTE: reshaped for borrowck (was `defer .clearRetainingCapacity()`). + // Reshaped from the Zig original for borrowck (was `defer .clearRetainingCapacity()`). let mut offset: usize = 0; while offset < session.orphan_header_block.len() { // Disjoint field borrows: `hpack` (mut) vs `orphan_header_block` (shared). @@ -565,7 +565,7 @@ pub fn decode_discard_orphan(session: &mut ClientSession) { /// HEADERS frames arrive in one read. 1xx and trailers are decoded then /// dropped; the final response is stored on the stream for delivery. pub fn decode_header_block(session: &mut ClientSession, stream: &mut Stream) { - // PORT NOTE: reshaped for borrowck (was `defer stream.header_block.clearRetainingCapacity()`) + // Reshaped from the Zig original for borrowck (was `defer stream.header_block.clearRetainingCapacity()`) // — `.clear()` is inlined before each return below. let mut status: u32 = 0; let mut bounds: Vec<[u32; 3]> = Vec::new(); @@ -705,7 +705,7 @@ pub fn decode_header_block(session: &mut ClientSession, stream: &mut Stream) { .saturating_sub(stream.decoded_headers.capacity()), ); for b in &bounds { - // PORT NOTE: self-referential — decoded_headers stores raw-ptr slices + // Self-referential — decoded_headers stores raw-ptr slices // borrowing stream.decoded_bytes. picohttp::Header stores raw ptrs so // this is sound as long as decoded_bytes is not reallocated before // delivery (it isn't — only ever appended to once per END_HEADERS). @@ -762,7 +762,6 @@ pub(crate) fn is_malformed_response_field(name: &[u8]) -> bool { _ => return true, } } - // PORT NOTE: Zig used a comptime string set; small enough to open-code. matches!( name, b"connection" @@ -783,7 +782,7 @@ pub fn is_malformed_response_value(value: &[u8]) -> bool { } pub fn error_code_for(err: bun_core::Error) -> wire::ErrorCode { - // PORT NOTE: bun_core::Error is a NonZeroU16 interned tag; `err!()` yields + // bun_core::Error is a NonZeroU16 interned tag; `err!()` yields // a const Error per name once the link-time table lands. Until then all // arms compare equal to `Error::TODO`, so this degrades to the first arm — // no worse than the prior unconditional INTERNAL_ERROR, and correct once diff --git a/src/http/h2_client/encode.rs b/src/http/h2_client/encode.rs index 413d85014cb1..94d3df5255f1 100644 --- a/src/http/h2_client/encode.rs +++ b/src/http/h2_client/encode.rs @@ -65,8 +65,8 @@ enum RequestHeader { Sensitive, } -// PORT NOTE: Zig used a comptime case-insensitive map. The first pass below -// pre-lowercases the probe so a case-sensitive match suffices. +// The match is case-sensitive; the first pass below pre-lowercases the probe +// so that suffices (header name matching must be case-insensitive). fn classify_request_header(name: &[u8]) -> Option { Some(match name { b"connection" => RequestHeader::Drop, @@ -91,7 +91,7 @@ pub fn write_request( stream: &mut Stream, request: &picohttp::Request<'_>, ) -> Result<(), bun_core::Error> { - // PORT NOTE: reshaped for borrowck — `encode_scratch` is borrowed mutably + // Reshaped from the Zig original for borrowck — `encode_scratch` is borrowed mutably // alongside `&mut *session` below; pull the Vec out, push it back at the end. let mut encoded = core::mem::take(&mut session.encode_scratch); encoded.clear(); @@ -370,7 +370,7 @@ pub(crate) fn drain_send_bodies(session: &mut ClientSession) { let slice: usize = session.remote_max_frame_size as usize; while session.conn_send_window > 0 && session.write_buffer.size() < WRITE_BUFFER_HIGH_WATER { let mut progressed = false; - // PORT NOTE: reshaped for borrowck — Zig iterates `session.streams.values()` + // Reshaped from the Zig original for borrowck — Zig iterates `session.streams.values()` // while passing `session` mutably to `drain_send_body`. Iterate by index // and re-borrow each pass. let mut i = 0usize; diff --git a/src/http/h3_client/AltSvc.rs b/src/http/h3_client/AltSvc.rs index 3dcb713e9d44..00660ea35ddb 100644 --- a/src/http/h3_client/AltSvc.rs +++ b/src/http/h3_client/AltSvc.rs @@ -131,7 +131,7 @@ struct Record { expires_at: i64, } -// PORT NOTE: module-level mutable state. Zig used a plain `var`; safe because +// module-level mutable state. Zig used a plain `var`; safe because // every access is on the single HTTP thread (see module doc). // PORTING.md §Global mutable state: HTTP-thread-only map → RacyCell. static CACHE: bun_core::RacyCell>> = bun_core::RacyCell::new(None); @@ -163,32 +163,16 @@ fn key<'a>(buf: &'a mut [u8], hostname: &[u8], port: u16) -> &'a [u8] { // hostname verbatim, then format only the port. cursor.write_all(hostname).expect("unreachable"); write!(cursor, ":{}", port).expect("unreachable"); - // PORT NOTE: reshaped for borrowck — capture remaining len before reborrowing buf. + // reshaped for borrowck — capture remaining len before reborrowing buf. let remaining = cursor.len(); let written = buf.len() - remaining; &buf[..written] } fn sweep_expired(now: i64) { - let cache = cache(); - // Unmanaged hash-map iteration is not removal-safe; restart after each removal. - // TODO(port): `StringHashMap` API — assumes `iter()` yielding `(&Box<[u8]>, &Record)` - // and `remove(&[u8])` that drops the owned key. Adjust to actual bun_collections API. - 'outer: loop { - let mut to_remove: Option> = None; - for (k, v) in cache.iter() { - if now >= v.expires_at { - to_remove = Some(Box::<[u8]>::from(&**k)); - break; - } - } - match to_remove { - Some(k) => { - cache.remove(&k[..]); - } - None => break 'outer, - } - } + // `retain` (hashbrown, via DerefMut) is removal-safe during iteration; + // dropping each removed entry frees its owned key. + cache().retain(|_, v| now < v.expires_at); } /// Remember (or refresh / clear) the h3 alternative for `origin_host:origin_port` @@ -221,7 +205,7 @@ pub(crate) fn record(origin_host: &[u8], origin_port: u16, field_value: &[u8]) { return; } } - // PORT NOTE: `StringHashMap::put` dupes the key on insert (matches Zig getOrPut). + // `StringHashMap::put` dupes the key on insert (matches Zig getOrPut). let _ = cache().put( k, Record { diff --git a/src/http/h3_client/ClientSession.rs b/src/http/h3_client/ClientSession.rs index 096b75e4c9ab..0f4fafec8f67 100644 --- a/src/http/h3_client/ClientSession.rs +++ b/src/http/h3_client/ClientSession.rs @@ -174,7 +174,7 @@ impl ClientSession { } pub fn fail(&mut self, stream: *mut Stream, err: bun_core::Error) { - // PORT NOTE: reshaped for borrowck — capture client ptr before detach() invalidates stream. + // Capture the client ptr before detach() invalidates `stream`. let client = stream_mut(stream).client; stream_mut(stream).abort(); self.detach(stream); @@ -191,7 +191,7 @@ impl ClientSession { /// port-reuse race where a pooled session goes stale between the /// `matches()` check and the first stream open. pub fn retry_or_fail(&mut self, stream: *mut Stream, err: bun_core::Error) { - // PORT NOTE: reshaped for Stacked Borrows like `fail` below — `detach()` + // Shaped for Stacked Borrows like `fail` below — `detach()` // re-derives `&mut HTTPClient` from the same raw ptr to null `h3`, which // would invalidate any `&mut HTTPClient` held across it. Hold the raw // `client_ptr` across `detach` and only form `&mut` afterward. @@ -234,7 +234,7 @@ impl ClientSession { } pub fn abort_by_http_id(&mut self, async_http_id: u32) -> bool { - // PORT NOTE: Zig iterates `pending.items` and calls `this.fail` (which + // The Zig original iterates `pending.items` and calls `this.fail` (which // mutates `pending`) mid-loop. Rust borrowck forbids reborrowing // `&mut self` while the iterator holds `&self.pending`, and only one // entry can match — so locate first via raw-ptr reads, then act. diff --git a/src/http/h3_client/encode.rs b/src/http/h3_client/encode.rs index 605ad7234cb2..cabb1cba8e10 100644 --- a/src/http/h3_client/encode.rs +++ b/src/http/h3_client/encode.rs @@ -27,7 +27,7 @@ pub fn write_request( }; // `stream.client` is a live backref while attached — see `client_mut` doc. let client: &mut HTTPClient = super::client_session::client_mut(client_ptr); - // PORT NOTE: reshaped for borrowck — `build_request` returns a `Request<'_>` + // `build_request` returns a `Request<'_>` // that mutably borrows `client`; capture every field we need first. let verbose = client.verbose; let href: &[u8] = client.url.href; diff --git a/src/http/lib.rs b/src/http/lib.rs index 8d0f9f5033a5..680e3d716daa 100644 --- a/src/http/lib.rs +++ b/src/http/lib.rs @@ -69,7 +69,7 @@ pub use thread_safe_stream_buffer::ThreadSafeStreamBuffer; #[path = "ssl_config.rs"] pub mod ssl_config; pub use ssl_config::SSLConfig; -// PORT NOTE: SSLWrapper was MOVE_DOWN to bun_uws (tier 4); re-export here so +// SSLWrapper was MOVE_DOWN to bun_uws (tier 4); re-export here so // `crate::ssl_wrapper::SSLWrapper` resolves for ProxyTunnel/HTTPContext. pub use bun_uws::ssl_wrapper; pub use bun_uws::ssl_wrapper::SSLWrapper; @@ -123,7 +123,7 @@ pub const EXTREMELY_VERBOSE: bool = false; /// Cloned response metadata (headers + url + status). Ownership transfers to /// the user once the headers phase completes. -// PORT NOTE: hoisted so `InternalState` can name it. +// hoisted so `InternalState` can name it. // The `picohttp::Response<'static>` borrows into `owned_buf`. pub struct HTTPResponseMetadata { // Borrows `owned_buf` (sibling field) — `RawSlice` carries the @@ -180,7 +180,7 @@ pub enum HTTPUpgradeState { Upgraded = 2, } -// PORT NOTE: was `packed struct(u32)` with mixed bool + 2-bit enum fields. +// was `packed struct(u32)` with mixed bool + 2-bit enum fields. // Kept as a plain struct since it never crosses FFI; restore packing // if the 32-byte vs 4-byte size difference shows up in profiling. // PERF(port): was packed struct(u32) — profile if hot. @@ -511,7 +511,7 @@ impl HTTPClientResultCallback { (self.function)(self.ctx, async_http, result); } - // PORT NOTE: `Callback.New(comptime Type, comptime callback)` was a + // `Callback.New(comptime Type, comptime callback)` was a // type-returning fn that wrapped a typed callback in *anyopaque erasure. pub fn new( this: *mut T, @@ -564,8 +564,8 @@ pub trait SocketTimeout { // lowercase hash header names so that we can be sure pub fn hash_header_name(name: &[u8]) -> u64 { - // Also fixes the Wyhash11→Wyhash port bug noted in the deleted TODO; safe — - // `hash_header_const` at :809 is computed by this same fn at runtime, no + // Uses the std Wyhash algorithm (matches Zig `std.hash.Wyhash`); safe — + // every comparison hash is computed by this same fn at runtime, no // persisted hashes. bun_wyhash::hash_ascii_lowercase(0, name) } @@ -610,7 +610,8 @@ pub struct HTTPClient<'a> { /// until `do_redirect` has released the socket, so it is freed there rather /// than at the assignment site. Also freed in `Drop` for error paths. pub prev_redirect: Vec, - // TODO(port): lifetime — &mut Progress::Node owned by caller; raw to avoid <'a> + // Non-owning back-reference to a `Progress::Node` owned by the caller; + // raw to avoid threading another lifetime param. pub progress_node: Option>, pub flags: Flags, @@ -775,12 +776,9 @@ use bun_core::StringBuilder; use bun_core::{FeatureFlags, Global, Output, err}; use bun_core::{OwnedString, String as BunString, Tag as BunStringTag, immutable as strings}; use bun_uws as uws; -// TODO(port): spec http.zig:829 uses `std.hash.Wyhash` (NOT Wyhash11 — see -// PORTING.md §Crate-map). bun_wyhash currently only exports Wyhash11; swap -// once `bun_wyhash::Wyhash` (std algorithm) lands so proxy_auth_hash() and -// header-name hashing match any component still computing the Zig hash. use bun_http_types::ETag::StringPointer; -use bun_wyhash::Wyhash11 as Wyhash; +// spec http.zig:829 uses `std.hash.Wyhash` (the std algorithm, not Wyhash11). +use bun_wyhash::Wyhash; use crate::http_context::HTTPSocket as HttpSocket; use crate::internal_state::{RequestStage, ResponseStage, Stage}; @@ -827,11 +825,11 @@ fn get_user_agent_header() -> picohttp::Header { } // ── header-hash constants ─────────────────────────────────────────────── -// PORT NOTE: Zig computed these at comptime via `Wyhash + lowerString`. -// Wyhash11 is not yet `const fn`, so use a runtime alias of `hash_header_name` -// and cache the three values that are looked up on every request via -// `LazyLock`. The per-header `match` arms inside `build_request` / -// `handle_response_metadata` already call `hash_header_const` at runtime. +// Zig computed these at comptime via `Wyhash + lowerString`. `Wyhash` is not +// `const fn`, so use a runtime alias of `hash_header_name` and cache the three +// values that are looked up on every request via `LazyLock`. The per-header +// `match` arms inside `build_request` / `handle_response_metadata` already +// call `hash_header_const` at runtime. #[inline(always)] fn hash_header_const(name: &[u8]) -> u64 { hash_header_name(name) @@ -899,9 +897,8 @@ mod scratch { pub use scratch::temp_hostname; // ── ALPN offer enum ───────────────────────────────────────────────────── -// PORT NOTE: Zig used `boringssl.SSL.AlpnOffer`; bun_boringssl doesn't yet -// expose one, so define it locally and TODO(port) wire through to -// `configure_http_client_with_alpn` once that lands. +// Zig used `boringssl.SSL.AlpnOffer`; bun_boringssl doesn't expose one, so +// it is defined locally next to `configure_http_client_with_alpn`. #[derive(Copy, Clone, PartialEq, Eq)] pub enum AlpnOffer { H1, @@ -1174,7 +1171,6 @@ pub fn print_request( Protocol::Http2 => "HTTP/2", Protocol::Http3 => "HTTP/3", }; - // TODO(port): pretty_fmt prefix elided pending Output::error_writer() in bun_core. bun_core::pretty_errorln!("> {} {} {}", ver, BStr::new(request.method), BStr::new(url)); for header in request.headers { bun_core::pretty_errorln!("> {}", header); @@ -1232,7 +1228,7 @@ fn write_to_socket_with_buffer_fallback( /// onto a `bun_core::Error` whose name is the upper-snake Zig error-set tag /// (e.g. `CERT_HAS_EXPIRED`). JS-side `error.code` matches on this exact /// string, so do NOT substitute `X509_verify_cert_error_string` output here. -// PORT NOTE: constants are the BoringSSL `X509_V_ERR_*` values from +// constants are the BoringSSL `X509_V_ERR_*` values from // `` (see boringssl.zig:17302-17370). Inlined as literals so // this file doesn't grow a dep on a header-generated const set. pub(crate) fn get_cert_error_from_no(error_no: i32) -> bun_core::Error { @@ -1354,7 +1350,7 @@ impl<'a> HTTPClient<'a> { /// every fail site. fn dispatch_result_and_reset(&mut self, clear_proxy_tunneling: bool) { let callback = self.result_callback; - // PORT NOTE: reshaped for borrowck — `to_result()`'s `body` field is a + // reshaped for borrowck — `to_result()`'s `body` field is a // `&mut MutableString` derived from a NonNull (caller-owned, disjoint // from `self`'s storage), but its lifetime is tied to `&mut self`. // Detach so the `state.reset()` reborrow below compiles. @@ -1750,7 +1746,7 @@ impl<'a> HTTPClient<'a> { }; if alpn == b"h2" { bun_core::scoped_log!(fetch, "ALPN negotiated h2 {}", BStr::new(self.url.href)); - // PORT NOTE: `comptime is_ssl` made this arm `HttpSocket`; in + // `comptime is_ssl` made this arm `HttpSocket`; in // Rust the const-generic isn't unified, so rebuild from the InternalSocket. let tls_socket = uws::SocketTLS::from_any(socket.socket); let ctx = self.get_ssl_ctx::(); @@ -1890,7 +1886,7 @@ impl<'a> HTTPClient<'a> { return; } bun_core::scoped_log!(fetch, "Timeout {}\n", BStr::new(self.url.href)); - // PORT NOTE: reshaped for borrowck — Zig used `defer terminateSocket(socket)` + // reshaped for borrowck — Zig used `defer terminateSocket(socket)` self.fail(err!(Timeout)); GenHttpContext::::terminate_socket(socket); } @@ -2023,8 +2019,7 @@ impl<'a> HTTPClient<'a> { /// Returns the SSL context for this client - either the custom context /// (for mTLS/custom TLS) or the default global context. pub fn get_ssl_ctx(&self) -> *mut GenHttpContext { - // TODO(refactor): returns raw ptr because the global/Arc lifetimes differ; - // unify behind a borrow. + // Returns a raw ptr because the global/Arc lifetimes differ. if IS_SSL { if let Some(ctx) = self.custom_ssl_ctx.as_ref() { return ctx.as_ptr().cast::>(); @@ -2131,7 +2126,6 @@ impl<'a> HTTPClient<'a> { } h if h == hash_header_const(b"if-modified-since") => { if self.flags.force_last_modified && self.if_modified_since.is_empty() { - // TODO(port): lifetime — borrows self.header_buf // SAFETY: header_str() returns a slice into self.header_buf which outlives // this client; lifetime is erased here only because we don't yet thread // struct lifetime params. The borrow is valid for the life of `self`. @@ -2299,7 +2293,7 @@ impl<'a> HTTPClient<'a> { self.flags.is_streaming_request_body = false; } - // PORT NOTE: Zig deinit'd then assigned `.empty` here; the bitwise + // Zig deinit'd then assigned `.empty` here; the bitwise // `task.http.?.* = async_http.*` copy-back later overwrote the // JS-thread original's slice with `.empty`, so the buffer was freed // exactly once. The Rust port has no struct copy-back @@ -2323,7 +2317,7 @@ impl<'a> HTTPClient<'a> { self.state.response_message_buffer = MutableString::default(); - // PORT NOTE: copy the NonNull, do NOT `.take()` — http.zig:1098 reads + // copy the NonNull, do NOT `.take()` — http.zig:1098 reads // `this.state.body_out_str.?` without clearing it, so the // TooManyRedirects `fail()` below still sees a populated body pointer. let body_out_str = self.state.body_out_str.unwrap(); @@ -2412,7 +2406,6 @@ impl<'a> HTTPClient<'a> { } pub fn start(&mut self, body: HTTPRequestBody<'a>, body_out_str: &mut MutableString) { - // TODO(port): body_out_str ownership — Zig stores *MutableString in state body_out_str.reset(); debug_assert!(self.state.response_message_buffer.list.capacity() == 0); @@ -2431,7 +2424,7 @@ impl<'a> HTTPClient<'a> { // mark that we are connecting self.flags.defer_fail_until_connecting_is_complete = true; // this will call .fail() if the connection fails in the middle of the function avoiding UAF with can happen when the connection is aborted - // PORT NOTE: Zig `defer this.completeConnectingProcess()` cannot be a Drop guard here + // Zig `defer this.completeConnectingProcess()` cannot be a Drop guard here // (it needs `&mut self`, which would alias every other `self.*` call in the body), // so it is reshaped as an explicit `self.complete_connecting_process()` before each return. @@ -2548,7 +2541,6 @@ impl<'a> HTTPClient<'a> { return; } Err(err) => { - // TODO(port): bun.handleErrorReturnTrace(err) — error traces not yet wired in Rust self.fail(err); self.complete_connecting_process(); return; @@ -2598,9 +2590,9 @@ impl<'a> HTTPClient<'a> { socket: HttpSocket, ) -> Result { let mut request_body_buffer = self.get_request_body_send_buffer(); - // PORT NOTE: request_body_buffer drops at scope exit (was `defer .deinit()`) + // request_body_buffer drops at scope exit (was `defer .deinit()`) let mut temporary_send_buffer = request_body_buffer.to_array_list(); - // PORT NOTE: temporary_send_buffer drops at scope exit + // temporary_send_buffer drops at scope exit let writer = &mut temporary_send_buffer; // Vec impls bun_io::Write @@ -2729,7 +2721,7 @@ impl<'a> HTTPClient<'a> { pub fn write_to_stream(&mut self, socket: HttpSocket, data: &[u8]) { bun_core::scoped_log!(fetch, "flushStream"); - // PORT NOTE: reshaped for borrowck — copy out the Copy bits we need + // reshaped for borrowck — copy out the Copy bits we need // (`upgrade_state`, the stream-buffer NonNull, `ended`) so the // `&mut self.state.original_request_body` borrow is dropped before any // call that takes `&mut self`. The stream is re-borrowed only at the @@ -2938,7 +2930,7 @@ impl<'a> HTTPClient<'a> { ); } - // PORT NOTE: sendfile.write() takes the raw fd, not the socket handle. + // sendfile.write() takes the raw fd, not the socket handle. match sendfile.write(socket.fd()) { crate::send_file::Status::Done => { self.state.request_stage = RequestStage::Done; @@ -2949,7 +2941,7 @@ impl<'a> HTTPClient<'a> { return; } crate::send_file::Status::Again => { - // PORT NOTE: mark_needs_more_for_sendfile is `const SSL=false`-only; + // mark_needs_more_for_sendfile is `const SSL=false`-only; // this arm is unreachable for SSL (panic above). uws::SocketTCP::from_any(socket.socket) .mark_needs_more_for_sendfile(); @@ -3178,7 +3170,7 @@ impl<'a> HTTPClient<'a> { "handleOnDataHeader data: {}", BStr::new(incoming_data) ); - // PORT NOTE: reshaped for borrowck — `to_read` aliases either + // reshaped for borrowck — `to_read` aliases either // `incoming_data` or `self.state.response_message_buffer`; hold it as a // `RawSlice` (encapsulated outlives-holder backref, safe `.slice()`) // so subsequent `&mut self` calls don't trip the checker. @@ -3542,7 +3534,7 @@ impl<'a> HTTPClient<'a> { // We have to clone metadata immediately after use pub fn clone_metadata(&mut self) { debug_assert!(self.state.pending_response.is_some()); - // PORT NOTE: `Response<'static>` is `Copy`; bind by value so no borrow + // `Response<'static>` is `Copy`; bind by value so no borrow // of `self.state` is held across the `pending_response = None` write // below (Zig nulls it mid-block, which would trip borrowck on a `&`). if let Some(response) = self.state.pending_response { @@ -3554,7 +3546,7 @@ impl<'a> HTTPClient<'a> { builder.count(self.url.href); let _ = builder.allocate(); // headers_buf is owned by the cloned_response (aka cloned_response.headers) - // PORT NOTE: `Response::clone` ties its return lifetime to + // `Response::clone` ties its return lifetime to // `headers: &'a mut [Header]`; leak the box to obtain `'static` so // the cloned response can be stored in `HTTPResponseMetadata`. // Reclaimed by `Drop for HTTPResponseMetadata` (mirrors Zig @@ -3640,7 +3632,7 @@ impl<'a> HTTPClient<'a> { if self.flags.protocol != Protocol::Http1_1 { return self.send_progress_update_multiplexed(); } - // PORT NOTE: reshaped for borrowck — `to_result()` returns an + // reshaped for borrowck — `to_result()` returns an // `HTTPClientResult<'_>` whose lifetime is tied to `&mut self` (via the // `body: &mut MutableString` borrow). Holding that result across the // `is_done` mutations below would require a second live `&mut Self`, @@ -3708,7 +3700,7 @@ impl<'a> HTTPClient<'a> { true }; - // PORT NOTE (diverges from Zig): the same early-reply hazard + // Diverges from Zig: the same early-reply hazard // described above for tunnels applies to direct connections — a // server may answer (200, Content-Length: 0) before a large PUT // body has finished writing (e.g. S3 multipart UploadPart against @@ -3820,7 +3812,7 @@ impl<'a> HTTPClient<'a> { /// transport, so there is no `ctx`/`socket` to hand back to the pool here. fn send_progress_update_multiplexed(&mut self) { debug_assert!(self.flags.protocol != Protocol::Http1_1); - // PORT NOTE: reshaped for borrowck — `to_result()` ties `result`'s + // reshaped for borrowck — `to_result()` ties `result`'s // lifetime to `&mut self`, so holding it across the `is_done` mutations // would require a second live `&mut Self` (aliased UB). Instead snapshot // every owned/Copy field out of the result, drop it, mutate `self` @@ -3916,7 +3908,7 @@ impl<'a> HTTPClient<'a> { b"" }; self.state.response_message_buffer = MutableString::default(); - // PORT NOTE: copy the NonNull, do NOT `.take()` — http.zig:2360 reads + // copy the NonNull, do NOT `.take()` — http.zig:2360 reads // `this.state.body_out_str.?` without clearing it, so the // TooManyRedirects `fail()` below still sees a populated body pointer. let body_out_str = self.state.body_out_str.unwrap(); @@ -4106,7 +4098,7 @@ impl<'a> HTTPClient<'a> { self.state.total_body_received ); } - // PORT NOTE: Zig `defer` block moved to end of fn (no early returns after this point that skip it) + // Zig `defer` block moved to end of fn (no early returns after this point that skip it) // we can ignore the body data in redirects if !self.state.flags.is_redirect_pending { if self.state.encoding.is_compressed() { @@ -4140,7 +4132,7 @@ impl<'a> HTTPClient<'a> { &mut self, incoming_data: &[u8], ) -> Result { - // PORT NOTE: reshaped for borrowck — get_body_buffer() may return + // reshaped for borrowck — get_body_buffer() may return // `&mut self.state.compressed_body`, so its borrow must be scoped // tightly and not held across other `self.state.*` accesses (would be // aliased `&mut`). Read the Copy fields first, then borrow the buffer @@ -4181,7 +4173,7 @@ impl<'a> HTTPClient<'a> { || content_length.is_none() { let is_final_chunk = is_done; - // PORT NOTE: Zig passes `buffer.*` BY VALUE (http.zig:2614). Mirror that by + // Zig passes `buffer.*` BY VALUE (http.zig:2614). Mirror that by // moving the body buffer's bytes out — process_body_buffer takes `&mut self.state` // and may mutate `compressed_body` (via decompress_bytes' reset) or `body_out_str`, // so any `&` into `self.state` held across the call would be aliased UB. @@ -4217,7 +4209,7 @@ impl<'a> HTTPClient<'a> { &mut self, incoming_data: &[u8], ) -> Result { - // PORT NOTE: reshaped for borrowck — `chunked_decoder` and the body + // reshaped for borrowck — `chunked_decoder` and the body // buffer (`compressed_body` / `body_out_str`) are disjoint fields of // `self.state`, so borrow them once together via the split accessor and // operate on safe references. The Zig `var buffer = buffer_ptr.*` was a @@ -4268,7 +4260,7 @@ impl<'a> HTTPClient<'a> { if self.signals.get(signals::Field::ResponseBodyStreaming) { // If we're streaming, we cannot use the libdeflate fast path self.state.flags.is_libdeflate_fast_path_disabled = true; - // PORT NOTE: Zig passes the by-value struct copy (http.zig:2681). Move the + // Zig passes the by-value struct copy (http.zig:2681). Move the // bytes out so no `&` into self.state aliases the `&mut self.state` call. let buffer_snap = core::mem::take(&mut self.state.get_body_buffer().list); return self.state.process_body_buffer(buffer_snap, false); @@ -4279,7 +4271,7 @@ impl<'a> HTTPClient<'a> { // Done _ => { self.state.flags.received_last_chunk = true; - // PORT NOTE: Zig passes the by-value struct copy (http.zig:2689). Move the + // Zig passes the by-value struct copy (http.zig:2689). Move the // bytes out so no `&` into self.state aliases the `&mut self.state` call. let buffer_snap = core::mem::take(&mut self.state.get_body_buffer().list); let _ = self.state.process_body_buffer(buffer_snap, true)?; @@ -4355,7 +4347,7 @@ impl<'a> HTTPClient<'a> { // If we're streaming, we cannot use the libdeflate fast path self.state.flags.is_libdeflate_fast_path_disabled = true; - // PORT NOTE: Zig passes `body_buffer.*` BY VALUE (http.zig:2763). Move + // Zig passes `body_buffer.*` BY VALUE (http.zig:2763). Move // the bytes out so no `&` into self.state aliases the `&mut self.state` // taken by process_body_buffer (which mutates compressed_body/body_out_str). let buffer_snap = core::mem::take(&mut self.state.get_body_buffer().list); @@ -4864,7 +4856,7 @@ impl<'a> HTTPClient<'a> { name: &'static [u8], hash: u64, } - // PORT NOTE: was a `const` table in Zig; LazyLock hashes + // was a `const` table in Zig; LazyLock hashes // aren't const, so build at runtime. let headers_to_remove: [H; 4] = [ H { diff --git a/src/http/lshpack.rs b/src/http/lshpack.rs index 2d42a58cdd9a..2c3b4f8cb9fc 100644 --- a/src/http/lshpack.rs +++ b/src/http/lshpack.rs @@ -49,26 +49,11 @@ pub enum HpackError { #[error("UnableToEncode")] UnableToEncode, } -// TODO(port): impl From for bun_core::Error +bun_core::named_error_set!(HpackError); impl HPACK { pub const LSHPACK_MAX_HEADER_SIZE: usize = 65536; - pub fn init(max_capacity: u32) -> *mut HPACK { - // `lshpack_wrapper_init` is `safe fn`: its only precondition is non-null - // alloc/free callbacks, which the bare (non-Option) fn-ptr types enforce. - let ptr = lshpack_wrapper_init( - bun_alloc::mimalloc::mi_malloc, - bun_alloc::mimalloc::mi_free, - max_capacity as usize, - ); - if ptr.is_null() { - bun_core::out_of_memory(); - } - ptr - // TODO(port): wrap in an owning newtype with Drop instead of returning a raw *mut HPACK - } - /// DecodeResult name and value uses a thread_local shared buffer and should be copy/cloned before the next decode/encode call pub fn decode(&mut self, src: &[u8]) -> Result { let mut header = lshpack_header::default(); @@ -131,7 +116,7 @@ impl HPACK { dst_buffer_offset, ) }; - // PORT NOTE: Zig compared `offset <= 0` on a usize; only `== 0` is reachable. + // Zig compared `offset <= 0` on a usize; only `== 0` is reachable. if offset == 0 { return Err(HpackError::UnableToEncode); } @@ -146,14 +131,14 @@ impl HPACK { lshpack_wrapper_enc_set_max_capacity(self, max_capacity as c_uint); } - // PORT NOTE: Zig `destroy` (raw `*mut HPACK` teardown) is subsumed by the + // Zig `destroy` (raw `*mut HPACK` teardown) is subsumed by the // safe [`HpackHandle`] RAII wrapper below — every Rust owner holds an // `HpackHandle`, so the raw destructor is private to `HpackHandle::drop`. } -/// Owning handle for an `HPACK` instance returned by [`HPACK::init`]. +/// Owning handle for an `HPACK` instance. /// -/// `HPACK::init` allocates via the C wrapper (`lshpack_wrapper_init`, which +/// The instance is allocated via the C wrapper (`lshpack_wrapper_init`, which /// `mi_malloc`s the struct and `lshpack_{enc,dec}_init`s its internals). The /// matching teardown is `lshpack_wrapper_deinit`, which runs the lshpack /// cleanup hooks before freeing — **not** a bare `mi_free`. Wrapping the raw @@ -164,11 +149,17 @@ pub struct HpackHandle(core::ptr::NonNull); impl HpackHandle { #[inline] pub fn new(max_capacity: u32) -> Self { - // `HPACK::init` already panics (out_of_memory) on null. - Self( - core::ptr::NonNull::new(HPACK::init(max_capacity)) - .expect("lshpack_wrapper_init returned null"), - ) + // `lshpack_wrapper_init` is `safe fn`: its only precondition is non-null + // alloc/free callbacks, which the bare (non-Option) fn-ptr types enforce. + let ptr = lshpack_wrapper_init( + bun_alloc::mimalloc::mi_malloc, + bun_alloc::mimalloc::mi_free, + max_capacity as usize, + ); + let Some(ptr) = core::ptr::NonNull::new(ptr) else { + bun_core::out_of_memory(); + }; + Self(ptr) } } @@ -192,8 +183,8 @@ impl core::ops::DerefMut for HpackHandle { impl Drop for HpackHandle { #[inline] fn drop(&mut self) { - // SAFETY: `self.0` came from `lshpack_wrapper_init` (via `HPACK::init`) - // and `HpackHandle` is its unique owner, so this is the first and only + // SAFETY: `self.0` came from `lshpack_wrapper_init` and `HpackHandle` + // is its unique owner, so this is the first and only // teardown — runs `lshpack_{enc,dec}_cleanup` then frees. unsafe { lshpack_wrapper_deinit(self.0.as_ptr()) }; } @@ -213,7 +204,6 @@ unsafe impl Send for HpackHandle {} type LshpackWrapperAlloc = extern "C" fn(size: usize) -> *mut c_void; type LshpackWrapperFree = unsafe extern "C" fn(ptr: *mut c_void); -// TODO(port): move to bun_http_sys unsafe extern "C" { safe fn lshpack_wrapper_init( alloc: LshpackWrapperAlloc, diff --git a/src/http/ssl_config.rs b/src/http/ssl_config.rs index 9ff070f0d086..f6541be3c574 100644 --- a/src/http/ssl_config.rs +++ b/src/http/ssl_config.rs @@ -381,8 +381,9 @@ impl SSLConfig { } let p = core::mem::replace(&mut self.protos, core::ptr::null()); let bytes = cstr_bytes(p); - // TODO(port): bun.memory.dropSentinel — reuses the allocation in - // place; here we copy. PERF(port). + // Zig's `dropSentinel` reused the allocation in place; here we copy: + // `Box<[u8]>` must own a global-allocator allocation of exactly `len` + // bytes, which the NUL-terminated `dupe_z` allocation is not. let owned = bytes.to_vec().into_boxed_slice(); // SAFETY: `p` was `dupe_z`-allocated when this config was built and // taken (replaced with null) above — sole owner, NUL-terminated. @@ -517,7 +518,7 @@ fn clone_string(s: CStrPtr) -> CStrPtr { pub mod global_registry { use super::*; - // PORT NOTE: Zig used `ArrayHashMapUnmanaged<*SSLConfig, WeakPtr, MapContext>` + // Zig used `ArrayHashMapUnmanaged<*SSLConfig, WeakPtr, MapContext>` // where `MapContext` hashes/compares by *content* through the raw-pointer // key. That shape is UB in Rust: when an interned `Arc`'s strong count hits // 0, std `Arc` materializes a `&mut SSLConfig` (via `drop_in_place`) @@ -552,7 +553,7 @@ pub mod global_registry { let mut dispose_new: Option = None; let mut dispose_old_weak: Option = None; - // PORT NOTE: reshaped for borrowck — Zig returned directly while holding + // reshaped for borrowck — Zig returned directly while holding // the mutex, then ran `defer`s. We compute `result` in a block, drop // the guard, then dispose deferred values. let result = { diff --git a/src/http/websocket.rs b/src/http/websocket.rs index 621686f3248f..1cdd3e0f27b5 100644 --- a/src/http/websocket.rs +++ b/src/http/websocket.rs @@ -161,9 +161,6 @@ impl WebsocketHeader { self.0 = (self.0 & !(1u16 << Self::FINAL_SHIFT)) | ((v as u16) << Self::FINAL_SHIFT); } - // PORT NOTE: Zig `writer: anytype` called `writeInt(u16, ..)`. The byte-level - // `bun_io::Write` trait is not yet defined; use `std::io::Write` — - // `write_all` over a 2-byte big-endian buffer is identical. pub fn write_header(self, writer: &mut impl std::io::Write, n: usize) -> std::io::Result<()> { // packed structs are sometimes buggy // lets check it worked right diff --git a/src/http/zlib.rs b/src/http/zlib.rs index 61a61cbb9c69..7cc5e91d1a5b 100644 --- a/src/http/zlib.rs +++ b/src/http/zlib.rs @@ -1,6 +1,6 @@ use bun_core::MutableString; -// PORT NOTE: Zig used `bun.ObjectPool(MutableString, initMutableString, false, 4)` and +// Zig used `bun.ObjectPool(MutableString, initMutableString, false, 4)` and // recovered the node via `container_of`. `MutableString` is a foreign type so we // cannot impl `ObjectPoolType` for it directly (orphan rule); a `#[repr(transparent)]` // newtype lets us cast `*mut PooledMutableString` ↔ `*mut MutableString` at the API @@ -26,8 +26,8 @@ mod buffer_pool { bun_collections::object_pool!(pub BufferPool: PooledMutableString, global, 4); pub fn get() -> *mut MutableString { - // TODO(port): Zig returns `*MutableString` borrowed from a pool node; consider an RAII - // guard so callers don't hand-pair get/put. + // Callers hand-pair get/put (matching Zig's pool API): the pointer is + // stored in long-lived struct fields, so a scoped RAII guard does not fit. // SAFETY: `first()` returns a valid `*mut PooledMutableString` whose data is initialized // (INIT is Some); #[repr(transparent)] makes the cast to `*mut MutableString` sound. BufferPool::first().cast::() diff --git a/src/http_jsc/headers_jsc.rs b/src/http_jsc/headers_jsc.rs index fa257b57cba8..108553d9ebda 100644 --- a/src/http_jsc/headers_jsc.rs +++ b/src/http_jsc/headers_jsc.rs @@ -19,7 +19,7 @@ pub fn from_fetch_headers( fetch_headers: Option<&FetchHeaders>, body_content_type: Option<&[u8]>, ) -> Headers { - // PORT NOTE: `FetchHeaders::{count,fast_has_,copy_to}` take `&mut self` but + // `FetchHeaders::{count,fast_has_,copy_to}` take `&mut self` but // are read-only FFI shims; cast through `*mut` (matching the prior // `link_interface!` impl which did `from_ref(h).cast_mut()`). let h_ptr: Option<*mut FetchHeaders> = fetch_headers.map(|h| core::ptr::from_ref(h).cast_mut()); @@ -62,7 +62,7 @@ pub fn from_fetch_headers( headers.buf.reserve_exact(buf_len as usize); // SAFETY: capacity reserved above; bytes are fully initialized by copyTo / the copy below. unsafe { headers.buf.set_len(buf_len as usize) }; - // PORT NOTE: reshaped for borrowck — Zig took two column slices off one `sliced` view. + // reshaped for borrowck — Zig took two column slices off one `sliced` view. // The Rust `Slice::items` returns `&mut [F]` from `&self`; the two columns are // disjoint allocations so simultaneous access is sound, but borrowck can't see // that. Take raw column pointers up front and slice in scoped blocks. @@ -107,7 +107,7 @@ pub fn from_fetch_headers( /// Build a `WebCore::FetchHeaders` from `bun.http.Headers` storage. /// -/// PORT NOTE: `FetchHeaders` (opaque C++ handle) was moved into `bun_jsc`, so +/// `FetchHeaders` (opaque C++ handle) was moved into `bun_jsc`, so /// the prior dep-cycle on `bun_runtime` no longer applies. The C++ side /// receives raw `StringPointer` column pointers; `bun_http_types` and /// `bun_string` both re-export the canonical `bun_core::StringPointer`, so no @@ -127,7 +127,7 @@ pub fn to_fetch_headers( // entries; C++ reads exactly `count_` of each and does not retain the pointers. FetchHeaders::create( global, - // PORT NOTE: C++ side reads only; cast_mut() is safe (no mutation). + // C++ side reads only; cast_mut() is safe (no mutation). names.as_ptr().cast_mut(), values.as_ptr().cast_mut(), // Spec headers_jsc.zig:12 uses `ZigString.fromBytes` (scans for @@ -143,12 +143,10 @@ pub(crate) struct H2TestingAPIs; impl H2TestingAPIs { // Zig source has no attribute — generate-js2native.ts scans by signature shape. - // TODO(port): once a `#[bun_jsc::host_fn]` proc-macro lands, annotate this so the - // extern "C" thunk is emitted (currently no proc-macro crate exists). pub(crate) fn live_counts(global: &JSGlobalObject, _frame: &CallFrame) -> JsResult { use bun_http::h2_client; let obj = JSValue::create_empty_object(global, 2); - // PORT NOTE: Zig `.jsNumber(i32)` → `js_number_from_int32`; h2 atomics + // Zig `.jsNumber(i32)` → `js_number_from_int32`; h2 atomics // are `AtomicI32` (signed) so no widening. obj.put( global, @@ -170,15 +168,13 @@ impl H3TestingAPIs { /// Named distinctly from H2's `live_counts` because generate-js2native.ts /// mangles `[^A-Za-z]` to `_`, so `H2Client.zig` and `H3Client.zig` produce /// the same path prefix and the function name has to differ. - // TODO(port): once a `#[bun_jsc::host_fn]` proc-macro lands, annotate this so the - // extern "C" thunk is emitted (currently no proc-macro crate exists). pub(crate) fn quic_live_counts( global: &JSGlobalObject, _frame: &CallFrame, ) -> JsResult { use bun_http::h3_client; let obj = JSValue::create_empty_object(global, 2); - // PORT NOTE: h3 atomics are `AtomicU32`; widen to u64 for `js_number_from_uint64`. + // h3 atomics are `AtomicU32`; widen to u64 for `js_number_from_uint64`. obj.put( global, b"sessions", diff --git a/src/http_jsc/method_jsc.rs b/src/http_jsc/method_jsc.rs index 7b4f09b80889..5dc70a7505db 100644 --- a/src/http_jsc/method_jsc.rs +++ b/src/http_jsc/method_jsc.rs @@ -22,8 +22,7 @@ pub fn from_js(global_this: &JSGlobalObject, input: JSValue) -> JsResult { pub payload_length_frame_bytes: [u8; 8], pub payload_length_frame_len: u8, - // TODO(port): lifetime — managed by microtask queue, not deinit + // Non-owning; the allocation is managed by the microtask queue, not deinit. pub initial_data_handler: Option>>, pub event_loop: &'static EventLoop, pub deflate: Option>, @@ -113,7 +113,7 @@ pub struct WebSocket { /// When set, all I/O goes through the tunnel (TLS encryption/decryption). /// The tunnel handles the TLS layer, so this is used with ssl=false. /// - /// PORT NOTE: intrusive refcount is hand-rolled on `WebSocketProxyTunnel` + /// intrusive refcount is hand-rolled on `WebSocketProxyTunnel` /// (`ref_()`/`deref()`); stored as `NonNull` rather than `RefPtr` because /// the tunnel does not (yet) implement `bun_ptr::RefCounted`. Ownership /// semantics match `RefPtr`: assigning here implies a held ref, released @@ -355,7 +355,7 @@ impl WebSocket { } fn clear_receive_buffers(&mut self, free: bool) { - // PORT NOTE: Zig poked `head = 0; count = 0` directly; LinearFifo's + // Zig poked `head = 0; count = 0` directly; LinearFifo's // fields are private in Rust so discard everything readable instead // (same observable state — empty, head realigned to 0). self.receive_buffer @@ -370,7 +370,7 @@ impl WebSocket { } fn clear_send_buffers(&mut self, free: bool) { - // PORT NOTE: see clear_receive_buffers — discard instead of poking + // see clear_receive_buffers — discard instead of poking // private `head`/`count`. self.send_buffer.discard(self.send_buffer.readable_length()); if free { @@ -386,7 +386,7 @@ impl WebSocket { // Decompress the data let mut decompressed = deflate.rare_data.array_list(); - // PORT NOTE: `defer decompressed.deinit()` → Drop on Vec + // `defer decompressed.deinit()` → Drop on Vec if let Err(err) = deflate.decompress(data, &mut decompressed) { let error_code = match err { @@ -398,7 +398,7 @@ impl WebSocket { return; } - // PORT NOTE: reshaped for borrowck — drop deflate borrow before re-borrowing self + // reshaped for borrowck — drop deflate borrow before re-borrowing self let items = decompressed.as_slice(); self.dispatch_data(items, kind); } @@ -483,7 +483,7 @@ impl WebSocket { self.receive_body_remain = 0; if is_final { // Decompress the complete message - // PORT NOTE: take ownership of the fifo so the readable + // take ownership of the fifo so the readable // slice does not alias `&mut self` while dispatching // (PORTING.md §Forbidden: aliased-&mut). `dispatch_*` may // call `terminate → clear_data → clear_receive_buffers(true)` @@ -515,7 +515,7 @@ impl WebSocket { self.message_is_compressed = false; return data.len(); } else if data.is_empty() { - // PORT NOTE: take ownership of the fifo so the readable slice + // take ownership of the fifo so the readable slice // does not alias `&mut self` while dispatching (PORTING.md // §Forbidden: aliased-&mut). let buf = core::mem::replace( @@ -548,7 +548,7 @@ impl WebSocket { self.receive_pending_chunk_len = 0; self.receive_body_remain = 0; if is_final { - // PORT NOTE: take ownership of the fifo so the readable slice + // take ownership of the fifo so the readable slice // does not alias `&mut self` while dispatching (PORTING.md // §Forbidden: aliased-&mut). let buf = core::mem::replace( @@ -568,7 +568,7 @@ impl WebSocket { data.len() } - // PORT NOTE: takes a raw `*mut Self` instead of `&mut self` because + // takes a raw `*mut Self` instead of `&mut self` because // `handle_without_deinit()` re-enters this very function on the same // allocation (spec .zig:398-402 / 1242-1253). A live outer `&mut self` // across that re-entry would yield two `&mut WebSocket` to one allocation @@ -637,7 +637,7 @@ impl WebSocket { let mut last_receive_data_type = receiving_type; // Zig `defer { if terminated ... else ... }` → run at end of fn - // PORT NOTE: implemented as explicit epilogue after the loop below. + // implemented as explicit epilogue after the loop below. let mut header_bytes = [0u8; size_of::()]; @@ -881,7 +881,7 @@ impl WebSocket { break; } - // PORT NOTE: copy the ≤125-byte payload to a stack array so + // copy the ≤125-byte payload to a stack array so // the slice does not alias `&mut self` across `dispatch_data` // (PORTING.md §Forbidden: aliased-&mut). `dispatch_data` may // call `terminate → clear_data` which mutates `ping_frame_bytes`' @@ -930,7 +930,7 @@ impl WebSocket { break; } - // PORT NOTE: copy the ≤125-byte payload to a stack array so + // copy the ≤125-byte payload to a stack array so // the slice does not alias `&mut self` across `dispatch_data` // (PORTING.md §Forbidden: aliased-&mut). let mut pong_data_buf = [0u8; 125]; @@ -1002,7 +1002,7 @@ impl WebSocket { self.close_received = true; let ping_len = self.ping_len as usize; - // PORT NOTE: copy close_data out to avoid borrowck conflict with &mut self below + // copy close_data out to avoid borrowck conflict with &mut self below let mut close_data_buf = [0u8; 125]; close_data_buf[..ping_len] .copy_from_slice(&self.ping_frame_bytes[6..][..ping_len]); @@ -1055,7 +1055,7 @@ impl WebSocket { self.send_close_with_body(1000, Some(1005), None, 0); } - // PORT NOTE: Zig passed `socket` by value (a copy of `this.tcp`). Every + // Zig passed `socket` by value (a copy of `this.tcp`). Every // Rust caller would have passed `self.tcp`, and threading a `&Socket` // alongside `&mut self` is a Stacked-Borrows hazard (the receiver retag // covers `self.tcp` and invalidates any prior `&self.tcp`-derived pointer @@ -1115,8 +1115,8 @@ impl WebSocket { if should_compress { // For compressed messages, we need to compress the content first let temp_buffer: Option>; - // PORT NOTE: Zig used deflate.rare_data.arena(); in Rust we use global mimalloc. - // PERF(port): was rare_data arena allocator + // Zig used deflate.rare_data.arena(); in Rust we use global mimalloc + // (potential perf gap). let content_to_compress: &[u8] = match bytes { Copy::Utf16(utf16) => 'brk: { // Convert UTF16 to UTF8 for compression @@ -1153,7 +1153,8 @@ impl WebSocket { { // Compress the content let mut compressed: Vec = Vec::new(); - // PERF(port): was rare_data allocator + // Zig allocated this from deflate.rare_data's allocator; the Rust + // port uses the global allocator (potential perf gap). if self .deflate @@ -1232,7 +1233,7 @@ impl WebSocket { true } - // PORT NOTE: renamed from `sendBuffer` to avoid clash with `send_buffer` + // renamed from `sendBuffer` to avoid clash with `send_buffer` // field. Reshaped to take no slice argument: every caller in the Zig // passed `this.send_buffer.readableSlice(0)`, and laundering that slice // through `from_raw_parts` while holding `&mut self` is aliased-&mut UB @@ -1296,7 +1297,7 @@ impl WebSocket { return false; } - // PORT NOTE: Zig `@bitCast(@as(u16, 0))`; WebsocketHeader has no public + // Zig `@bitCast(@as(u16, 0))`; WebsocketHeader has no public // raw-bits ctor, so build the all-zero header via from_slice. let mut header = WebsocketHeader::from_slice([0, 0]); header.set_final(true); @@ -1310,7 +1311,7 @@ impl WebSocket { let ping_len = self.ping_len as usize; if ping_len > 0 { - // PORT NOTE: reshaped for borrowck — Mask::fill needs disjoint borrows of ping_frame_bytes + // reshaped for borrowck — Mask::fill needs disjoint borrows of ping_frame_bytes let (head, tail) = self.ping_frame_bytes.split_at_mut(6); let mask_buf: &mut [u8; 4] = (&mut head[2..6]) .try_into() @@ -1318,7 +1319,7 @@ impl WebSocket { let to_mask = &mut tail[..ping_len]; // SAFETY: input and output point to the same memory; Mask::fill supports in-place Mask::fill_in_place(&self.global_this, mask_buf, to_mask); - // PORT NOTE: copy the ≤(6+125)-byte frame to a stack array so the + // copy the ≤(6+125)-byte frame to a stack array so the // slice does not alias `&mut self` across `enqueue_encoded_bytes` // (PORTING.md §Forbidden: aliased-&mut). `enqueue_encoded_bytes` // may call `terminate → clear_data` while the laundered slice into @@ -1364,7 +1365,7 @@ impl WebSocket { } } let mut final_body_bytes = [0u8; 128 + 8]; - // PORT NOTE: Zig `@bitCast(@as(u16, 0))`; WebsocketHeader has no public + // Zig `@bitCast(@as(u16, 0))`; WebsocketHeader has no public // raw-bits ctor, so build the all-zero header via from_slice. let mut header = WebsocketHeader::from_slice([0, 0]); header.set_final(true); @@ -1516,7 +1517,7 @@ impl WebSocket { let opcode = Opcode::from_raw(op); // Cast the JSValue to a Blob. - // PORT NOTE: `bun_jsc::webcore::Blob` is an opaque C-ABI shim (real + // `bun_jsc::webcore::Blob` is an opaque C-ABI shim (real // layout lives in `bun_runtime::webcore::Blob`, a higher-tier crate). // `from_js`/`shared_view` trampoline through extern fns to avoid the // dep cycle — see `bun_jsc::webcore::Blob` impl block. @@ -1756,7 +1757,7 @@ impl WebSocket { payload_length_frame_bytes: [0u8; 8], payload_length_frame_len: 0, initial_data_handler: None, - // PORT NOTE: reshaped for borrowck — `vm.event_loop()` returns a + // reshaped for borrowck — `vm.event_loop()` returns a // `&'static`-tied borrow that would lock `vm` for the rest of the // fn; re-derive from `global_this` so `vm` stays usable below. // SAFETY: bun_vm() never returns null; event_loop ptr is live for VM lifetime. @@ -1782,10 +1783,10 @@ impl WebSocket { } } - // PORT NOTE: Zig `adoptGroup(tcp, group, kind, "tcp", ws)` reflected on + // Zig `adoptGroup(tcp, group, kind, "tcp", ws)` reflected on // the field name; Rust port takes a closure to write the new socket. let group = { - // PORT NOTE: reshaped for borrowck — `rare_data()` borrows `vm` + // reshaped for borrowck — `rare_data()` borrows `vm` // mutably and `ws_client_group` also wants a `vm` reference. let vm_ptr: *mut _ = vm; // SAFETY: `rare_data()` returns `&mut RareData` reached through @@ -1851,7 +1852,7 @@ impl WebSocket { ws_ref.initial_data_handler = NonNull::new(initial_data); // Use a higher-priority callback for the initial onData handler - // PORT NOTE: `queue_microtask_callback` takes an erased + // `queue_microtask_callback` takes an erased // `(*mut c_void, unsafe extern "C" fn(*mut c_void))`; cast both. global_this.queue_microtask_callback( initial_data.cast::(), @@ -1879,7 +1880,7 @@ impl WebSocket { // SAFETY: tunnel_ptr is a valid *WebSocketProxyTunnel from C++ with an // intrusive refcount. The caller retains its own ref; we bump to take // ownership (Zig: tunnel.ref()). - // PORT NOTE: Zig `tunnel.ref()` then store — bump the intrusive count + // Zig `tunnel.ref()` then store — bump the intrusive count // and store the raw owning handle (released in `clear_data`). let tunnel_owned: NonNull = { let p = tunnel_ptr.cast::(); @@ -1919,7 +1920,7 @@ impl WebSocket { payload_length_frame_bytes: [0u8; 8], payload_length_frame_len: 0, initial_data_handler: None, - // PORT NOTE: reshaped for borrowck — `vm.event_loop()` returns a + // reshaped for borrowck — `vm.event_loop()` returns a // `&'static`-tied borrow that would lock `vm` for the rest of the // fn; re-derive from `global_this` so `vm` stays usable below. // SAFETY: bun_vm() never returns null; event_loop ptr is live for VM lifetime. @@ -1963,7 +1964,7 @@ impl WebSocket { ws: NonNull::new(outgoing).map(|p| unsafe { CppWebSocketRef::new(p) }), })); ws_ref.initial_data_handler = NonNull::new(initial_data); - // PORT NOTE: `queue_microtask_callback` takes an erased + // `queue_microtask_callback` takes an erased // `(*mut c_void, unsafe extern "C" fn(*mut c_void))`; cast both. global_this.queue_microtask_callback( initial_data.cast::(), @@ -2051,7 +2052,7 @@ impl WebSocket { } } - // PORT NOTE: `deinit` is the IntrusiveRc destructor callback; not `impl Drop` because + // `deinit` is the IntrusiveRc destructor callback; not `impl Drop` because // self is heap-allocated via heap::alloc and crosses FFI as *mut c_void. unsafe fn deinit(this: *mut Self) { // SAFETY: called once when ref_count hits zero @@ -2094,7 +2095,7 @@ impl WebSocket { // ────────────────────────────────────────────────────────────────────────── // exportAll() — comptime @export with name concat // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: avoids the `paste` crate by passing the nine fully-qualified +// avoids the `paste` crate by passing the nine fully-qualified // `#[no_mangle]` idents at the call site (declare-site macro). Zig's // comptime `++` concat has no Rust equivalent for `#[no_mangle]` literals. macro_rules! export_websocket_client { @@ -2233,7 +2234,7 @@ impl InitialDataHandler { return; }; let ws_ptr = this_socket_ptr.as_ptr(); - // PORT NOTE: this fn is reachable re-entrantly from + // this fn is reachable re-entrantly from // `WebSocket::handle_data` while that frame may later form its own // `&mut *ws_ptr`, so never materialize a `&mut WebSocket` here — // touch fields via raw projection only. @@ -2342,7 +2343,7 @@ impl Mask { } /// In-place variant for when output and input alias the same buffer. - /// PORT NOTE: Zig's `fill` allowed output==input; Rust borrowck forbids + /// Zig's `fill` allowed output==input; Rust borrowck forbids /// `&mut [u8]` + `&[u8]` aliasing. Callers that masked in-place use this. pub(crate) fn fill_in_place( global_this: &JSGlobalObject, @@ -2542,7 +2543,7 @@ impl<'a> Copy<'a> { // 4 byte mask // 0, 2, 8 byte length - // PORT NOTE: Zig `@bitCast(@as(u16, 0))`; WebsocketHeader has no public + // Zig `@bitCast(@as(u16, 0))`; WebsocketHeader has no public // raw-bits ctor, so build the all-zero header via from_slice. let mut header = WebsocketHeader::from_slice([0, 0]); @@ -2564,7 +2565,7 @@ impl<'a> Copy<'a> { buf.len() ); - // PORT NOTE: reshaped for borrowck — split `buf` into three disjoint + // reshaped for borrowck — split `buf` into three disjoint // regions (header bytes / 4-byte mask / payload) so `write_header` and // `Mask::fill*` don't alias. Zig wrote through one pointer. let (head, to_mask_full) = buf.split_at_mut(content_offset); @@ -2583,8 +2584,6 @@ impl<'a> Copy<'a> { header.set_len(WebsocketHeader::pack_length( encode_into_result.written as usize, )); - // TODO(port): Zig used std.io.fixedBufferStream + header.writeHeader. - // WebsocketHeader::write_header should write into &mut head[..2+len_int]. header .write_header( &mut &mut header_part[..], @@ -2648,7 +2647,7 @@ impl<'a> Copy<'a> { _ => unreachable!(), } - // PORT NOTE: Zig `@bitCast(@as(u16, 0))`; WebsocketHeader has no public + // Zig `@bitCast(@as(u16, 0))`; WebsocketHeader has no public // raw-bits ctor, so build the all-zero header via from_slice. let mut header = WebsocketHeader::from_slice([0, 0]); @@ -2663,7 +2662,7 @@ impl<'a> Copy<'a> { buf.len() ); - // PORT NOTE: reshaped for borrowck — three disjoint regions (see `copy`). + // reshaped for borrowck — three disjoint regions (see `copy`). let (head, to_mask_full) = buf.split_at_mut(content_offset); let (header_part, mask_part) = head.split_at_mut(mask_offset); let mask_buf: &mut [u8; 4] = (&mut mask_part[..4]) diff --git a/src/http_jsc/websocket_client/CppWebSocket.rs b/src/http_jsc/websocket_client/CppWebSocket.rs index 602428111bd0..65c47ccca005 100644 --- a/src/http_jsc/websocket_client/CppWebSocket.rs +++ b/src/http_jsc/websocket_client/CppWebSocket.rs @@ -63,7 +63,7 @@ unsafe extern "C" { fn WebSocket__setProtocol(websocket_context: &CppWebSocket, protocol: *mut BunString); } -// PORT NOTE: receivers are `&self` (not `&mut self`) because `CppWebSocket` is +// Receivers are `&self` (not `&mut self`) because `CppWebSocket` is // an opaque C++ handle with no Rust-visible state; mutation happens entirely on // the C++ side. Callers hold `NonNull` and dispatch via shared // borrows (often while `&mut WebSocket` is also live), so `&mut self` @@ -176,7 +176,6 @@ impl CppWebSocket { } impl CppWebSocket { - // PORT NOTE: `ref` is a Rust keyword; using raw identifier to match Zig fn name. pub(crate) fn r#ref(&self) { bun_jsc::mark_binding!(); WebSocket__incrementPendingActivity(self); diff --git a/src/http_jsc/websocket_client/WebSocketDeflate.rs b/src/http_jsc/websocket_client/WebSocketDeflate.rs index e88488a808df..0ff598c8b8e3 100644 --- a/src/http_jsc/websocket_client/WebSocketDeflate.rs +++ b/src/http_jsc/websocket_client/WebSocketDeflate.rs @@ -109,13 +109,11 @@ pub struct PerMessageDeflate { pub compress_stream: zlib::z_stream, pub decompress_stream: zlib::z_stream, pub params: Params, - // PORT NOTE: Zig borrowed `&RareData` from VM `bun_jsc::RareData` (pooled - // libdeflate handles, shared across connections). `bun_jsc::RareData:: - // websocket_deflate()` currently returns an opaque placeholder (the real - // type is *this* `RareData`, which would be a dep cycle), so own a - // per-connection instance instead. - // PERF(port): per-connection libdeflate alloc — restore VM-pooled instance - // once `bun_jsc::rare_data::WebSocketDeflateRareData` is wired to this type. + // Zig borrowed `&RareData` from VM `bun_jsc::RareData` (pooled libdeflate + // handles, shared across connections). `bun_jsc::RareData::websocket_deflate()` + // returns an opaque placeholder (the real type is *this* `RareData`, which + // would be a dep cycle), so each connection owns a fresh instance instead — + // a per-connection libdeflate allocation, not a correctness divergence. pub rare_data: RareData, } @@ -159,16 +157,14 @@ impl PerMessageDeflate { params: Params, rare_data: &mut JscRareData, ) -> Result, bun_core::Error> { - // TODO(port): narrow error set let mut self_ = Box::new(Self { params, compress_stream: bun_core::ffi::zeroed::(), decompress_stream: bun_core::ffi::zeroed::(), - // TODO(port): bun_jsc::rare_data::WebSocketDeflateRareData — // `rare_data.websocket_deflate()` returns an opaque `{ _opaque: () }` - // placeholder in bun_jsc; the real type is `self::RareData` (this - // module), which bun_jsc cannot import without a dep cycle. Until a - // re-export shim lands, fall back to a fresh per-connection instance. + // placeholder in bun_jsc (the real type is `self::RareData`, which + // bun_jsc cannot import without a dep cycle), so use a fresh + // per-connection instance — see the `rare_data` field note. rare_data: { let _ = rare_data; RareData::default() diff --git a/src/http_jsc/websocket_client/WebSocketProxy.rs b/src/http_jsc/websocket_client/WebSocketProxy.rs index 062f7b042a45..b2681e92fa3e 100644 --- a/src/http_jsc/websocket_client/WebSocketProxy.rs +++ b/src/http_jsc/websocket_client/WebSocketProxy.rs @@ -14,13 +14,13 @@ pub(crate) struct WebSocketProxy { /// WebSocket upgrade request to send after CONNECT succeeds websocket_request_buf: Box<[u8]>, /// TLS tunnel for wss:// through HTTP proxy - // TODO(port): lifetime — intrusive refcount (Drop calls shutdown()+deref()); not in LIFETIMES.tsv + // Holds one intrusive ref; Drop calls shutdown()+deref(). tunnel: Option>, } impl WebSocketProxy { /// Initialize a new WebSocketProxy - // PORT NOTE: params are owned (Zig caller transfers allocator ownership; freed in deinit) + // params are owned (Zig caller transfers allocator ownership; freed in deinit) pub(crate) fn init( target_host: Box<[u8]>, target_is_https: bool, diff --git a/src/http_jsc/websocket_client/WebSocketProxyTunnel.rs b/src/http_jsc/websocket_client/WebSocketProxyTunnel.rs index 9e9a3406ad0b..4968c3f0adfd 100644 --- a/src/http_jsc/websocket_client/WebSocketProxyTunnel.rs +++ b/src/http_jsc/websocket_client/WebSocketProxyTunnel.rs @@ -151,7 +151,7 @@ impl WebSocketProxyTunnel { sni_hostname: &[u8], reject_unauthorized: bool, ) -> Result, bun_alloc::AllocError> { - // PORT NOTE: const-generic bool → variant selection. The pointer cast is + // const-generic bool → variant selection. The pointer cast is // identity when SSL matches the alias (HttpUpgradeClient = NewHttpUpgradeClient, // etc); `assume_ssl`/`assume_tcp` rebuild the handler around the same // `InternalSocket` so no `unsafe` is needed. @@ -196,7 +196,6 @@ impl WebSocketProxyTunnel { ssl_options: &SslConfig, initial_data: &[u8], ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set // Allow handshake to complete so we can access peer certificate for manual // hostname verification in onHandshake(). The actual reject_unauthorized // check uses self.reject_unauthorized field. @@ -235,7 +234,7 @@ impl WebSocketProxyTunnel { // Configure SNI with hostname. // - // PORT NOTE: the Zig spec does this inside `onOpen`, which `SslWrapper::start()` + // the Zig spec does this inside `onOpen`, which `SslWrapper::start()` // invokes immediately before `handle_traffic()`. We hoist it here because // `start()` holds `&mut SslWrapper` across the `on_open` dispatch, and any // read of `(*ctx).wrapper` from inside the callback would invalidate that @@ -292,7 +291,7 @@ impl WebSocketProxyTunnel { let _guard = unsafe { bun_ptr::ScopedRef::new(this) }; bun_core::scoped_log!(WebSocketProxyTunnel, "onOpen"); // SNI configuration is done in `start()` before the wrapper is driven; - // see PORT NOTE there. This callback intentionally does not touch + // see the note there. This callback intentionally does not touch // `(*this).wrapper` — the caller (`SslWrapper::start`) holds `&mut self` // over those bytes. let _ = this; @@ -512,7 +511,7 @@ impl WebSocketProxyTunnel { unsafe { let to_send = (*this).write_buffer.slice(); if !to_send.is_empty() { - // PORT NOTE: reshaped for borrowck — capture len before re-borrowing write_buffer + // reshaped for borrowck — capture len before re-borrowing write_buffer let to_send_len = to_send.len(); let written = (*this).socket.write(to_send); if written < 0 { @@ -569,7 +568,6 @@ impl WebSocketProxyTunnel { /// which forms `&mut *ctx`; this function therefore accesses `wrapper` via raw /// projection and never holds a `&mut Self` across the call. pub(crate) unsafe fn write(this: *mut Self, data: &[u8]) -> Result { - // TODO(port): narrow error set // SAFETY: caller contract — `this` is live; projection covers only `wrapper`. let wrapper_ptr = unsafe { ptr::addr_of_mut!((*this).wrapper) }; // SAFETY: deref of field projection; `this` is live. diff --git a/src/http_jsc/websocket_client/WebSocketUpgradeClient.rs b/src/http_jsc/websocket_client/WebSocketUpgradeClient.rs index 349de5853bbc..6ddce7b057db 100644 --- a/src/http_jsc/websocket_client/WebSocketUpgradeClient.rs +++ b/src/http_jsc/websocket_client/WebSocketUpgradeClient.rs @@ -88,6 +88,30 @@ enum State { Done, } +/// Owned +1 reference to a `us_ssl_ctx_t` (`SSL_CTX*`); releases the ref via +/// `SSL_CTX_free` on drop (BoringSSL decrements its internal refcount). +/// Plays the role of Zig's `SslCtxRef` for the upgrade-client lifecycle: +/// either dropped here, or transferred to the connected `WebSocket` via +/// `into_raw()` after the upgrade completes. +struct SslCtxOwned(*mut SslCtx); + +impl SslCtxOwned { + /// Transfer ownership of the retained ref to the caller without freeing. + fn into_raw(self) -> *mut SslCtx { + let p = self.0; + core::mem::forget(self); + p + } +} + +impl Drop for SslCtxOwned { + fn drop(&mut self) { + // SAFETY: `self.0` is an owned retained ref (returned with +1 by + // `ssl_ctx_cache_get_or_create`) that has not been transferred out. + unsafe { boringssl::c::SSL_CTX_free(self.0) }; + } +} + /// `NewHTTPUpgradeClient(comptime ssl: bool) type` — generic over `SSL`. /// /// `bun.ptr.RefCount(@This(), "ref_count", deinit, .{})` — intrusive single-thread @@ -101,7 +125,7 @@ pub struct HTTPClient { outgoing_websocket: Option<*mut CppWebSocket>, /// Owned request bytes. Freed via `clear_input`. input_body_buf: Vec, - // PORT NOTE: reshaped for borrowck — Zig `to_send: []const u8` is always a + // Reshaped for borrowck — Zig `to_send: []const u8` is always a // suffix of `input_body_buf`; stored here as the suffix length so we don't // hold a self-referential slice. to_send_len: usize, @@ -122,11 +146,8 @@ pub struct HTTPClient { /// `us_ssl_ctx_t` built from `ssl_config` when it carries a custom CA. /// Heap-allocated because ownership transfers to the connected /// `WebSocket` after the upgrade completes (so the `SSL_CTX` outlives - /// this struct). - // TODO(port): Zig held a strong `SslCtxRef` (RAII over `SSL_CTX_up_ref`/ - // `SSL_CTX_free`). No such wrapper exists in `bun_uws` yet; store the raw - // retained pointer and release in `clear_data`/Drop callers. - secure: Option<*mut SslCtx>, + /// this struct). RAII: dropping the wrapper releases the retained ref. + secure: Option, /// Expected Sec-WebSocket-Accept value for handshake validation per RFC 6455 §4.2.2. /// This is base64(SHA-1(Sec-WebSocket-Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")). @@ -139,33 +160,18 @@ pub struct HTTPClient { offered_permessage_deflate: bool, } -// Handler set referenced by `dispatch.zig` (kind = `.ws_client_upgrade[_tls]`). +// Handler set referenced by the dispatch table (kind = `.ws_client_upgrade[_tls]`). // The `register()` C++ round-trip that previously installed these on a // shared `us_socket_context_t` is gone — sockets are stamped with the -// kind at connect time and routed here statically. -// -// TODO(port): expose these as a `uws::SocketHandlerSet` const for the dispatch -// table; in Zig these were `pub const onOpen = handleOpen;` etc. +// kind at connect time and routed via the `RawSocketEvents` impl in +// `bun_runtime::socket::uws_handlers`, which forwards to the `pub +// handle_*` methods below. // -// These take `*mut Self` (not `&mut Self`) because uSockets dispatches them -// from the raw userdata pointer and several of them can free `Self` (via +// The handlers take `*mut Self` (not `&mut Self`) because uSockets dispatches +// them from the raw userdata pointer and several of them can free `Self` (via // `deref` reaching zero) or be re-entered synchronously by `tcp.close()` / // C++ callbacks. Holding a `&mut Self` function-argument across either of // those is UB under Stacked Borrows (argument protectors / aliased `&mut`). -impl HTTPClient { - pub const ON_OPEN: unsafe fn(*mut Self, Socket) = Self::handle_open; - pub const ON_CLOSE: unsafe fn(*mut Self, Socket, c_int, *mut c_void) = Self::handle_close; - pub const ON_DATA: unsafe fn(*mut Self, Socket, &[u8]) = Self::handle_data; - pub const ON_WRITABLE: unsafe fn(*mut Self, Socket) = Self::handle_writable; - pub const ON_TIMEOUT: unsafe fn(*mut Self, Socket) = Self::handle_timeout; - pub const ON_LONG_TIMEOUT: unsafe fn(*mut Self, Socket) = Self::handle_timeout; - pub const ON_CONNECT_ERROR: unsafe fn(*mut Self, Socket, c_int) = - Self::handle_connect_error; - pub const ON_END: unsafe fn(*mut Self, Socket) = Self::handle_end; - pub const ON_HANDSHAKE: unsafe fn(*mut Self, Socket, i32, uws::us_bun_verify_error_t) = - Self::handle_handshake; -} - impl HTTPClient { /// Zig: `meta.typeName(T)` keeps the full path because the name contains `(`. const TYPE_NAME: &'static str = if SSL { @@ -391,7 +397,7 @@ impl HTTPClient { using_proxy ); - // PORT NOTE: reshaped for borrowck — `rare_data()` borrows `vm` mutably and + // Reshaped for borrowck — `rare_data()` borrows `vm` mutably and // `ws_upgrade_group` also wants a `vm` reference. See websocket_client.rs. let group = { // SAFETY: `rare_data()` returns `&mut RareData` reached through a @@ -445,8 +451,8 @@ impl HTTPClient { }; // Owned ref; transferred to the connected WebSocket on // upgrade, freed in `deinit` if we never get that far. - client_ref.secure = Some(ctx); - break 'brk client_ref.secure; + client_ref.secure = Some(SslCtxOwned(ctx)); + break 'brk Some(ctx); } } // SAFETY: `vm_ptr` is the live per-thread VM; JS thread. @@ -591,14 +597,8 @@ impl HTTPClient { } // ssl_config: Option> — Drop runs SSLConfig::deinit + frees the box. self.ssl_config = None; - // secure: raw retained `SSL_CTX*` (no RAII wrapper yet — see field - // TODO(port)). Release the ref taken in `connect`. - if let Some(s) = self.secure.take() { - // SAFETY: `s` was returned by `create_ssl_client_context_for` - // (one owned ref) and has not been freed; SSL_CTX_free decrements - // BoringSSL's internal refcount. - unsafe { boringssl::c::SSL_CTX_free(s) }; - } + // secure: Option — Drop releases the ref taken in `connect`. + self.secure = None; } /// # Safety @@ -856,7 +856,7 @@ impl HTTPClient { } pub fn is_same_socket(&self, socket: Socket) -> bool { - // PORT NOTE: `InternalSocket` has no `PartialEq`; compare native handles. + // `InternalSocket` has no `PartialEq`; compare native handles. socket.get_native_handle() == self.tcp.get_native_handle() } @@ -973,7 +973,7 @@ impl HTTPClient { }; let bytes_read = usize::try_from(response.bytes_read).expect("int cast"); - // PORT NOTE: reshaped for borrowck — copy remain_buf out before mutating self. + // Reshaped for borrowck — copy remain_buf out before mutating self. let remain_buf: Vec = body[bytes_read..].to_vec(); // PERF(port): was zero-copy slice into self.body. // SAFETY: `me`'s last use is the `body` slice above (now copied out); @@ -1052,7 +1052,7 @@ impl HTTPClient { log!("Proxy tunnel established"); let bytes_read = usize::try_from(response.bytes_read).expect("int cast"); - // PORT NOTE: reshaped for borrowck — copy remain_buf before clearing self.body. + // Reshaped for borrowck — copy remain_buf before clearing self.body. let remain_buf: Vec = body[bytes_read..].to_vec(); // PERF(port): was zero-copy slice. @@ -1161,7 +1161,7 @@ impl HTTPClient { return; } - // PORT NOTE: reshaped for borrowck — re-borrow proxy after uses above. + // Reshaped for borrowck — re-borrow proxy after uses above. // SAFETY: re-derive a fresh `&mut`. let me = unsafe { &mut *this }; let Some(p) = &mut me.proxy else { @@ -1283,7 +1283,7 @@ impl HTTPClient { }; let bytes_read = usize::try_from(response.bytes_read).expect("int cast"); - // PORT NOTE: reshaped for borrowck — copy remain_buf out before mutating self. + // Reshaped for borrowck — copy remain_buf out before mutating self. let remain_buf: Vec = body[bytes_read..].to_vec(); // PERF(port): was zero-copy slice. // SAFETY: `me`'s last use is the `body` slice above (now copied out); @@ -1693,8 +1693,9 @@ impl HTTPClient { } else { None }, - // ownership transferred; suppress the drop above - saved_secure.take().map(|p| &mut *p), + // ownership transferred; `into_raw` suppresses the + // RAII release at fn end. + saved_secure.take().map(|s| &mut *s.into_raw()), ) }; } else { @@ -1719,12 +1720,8 @@ impl HTTPClient { } // Zig: `defer if (saved_secure) |s| bun.BoringSSL.c.SSL_CTX_free(s);` // Any arm above that didn't transfer ownership to `did_connect` left - // the retained `SSL_CTX*` in `saved_secure`; release it now. - if let Some(s) = saved_secure { - // SAFETY: `s` is the owned ref taken out of `self.secure` above; - // not aliased after this point. - unsafe { boringssl::c::SSL_CTX_free(s) }; - } + // the retained ref in `saved_secure`; RAII drop releases it now. + drop(saved_secure); } pub fn memory_cost(&self) -> usize { @@ -1850,7 +1847,7 @@ impl HTTPClient { /// (no allocation) or allocates a UTF-8 copy. The resulting slices are stored /// here so build_request_body / build_connect_request can index them by &[u8]. /// -// PORT NOTE: reshaped for borrowck — Zig stored parallel `name_slices` / +// Reshaped for borrowck — Zig stored parallel `name_slices` / // `value_slices` arrays of `[]const u8` borrowing into `slices`. That is // self-referential in Rust; instead store only the `Utf8Slice` array (len = // 2*count, names at even indices, values at odd) and yield pairs via `iter()`. @@ -2194,9 +2191,19 @@ fn compute_accept_value(key: &[u8]) -> [u8; 28] { // extern "C" export shims for the generic `connect`/`cancel`/`memoryCost`. // Zig's `exportAll()` does `@export(&connect, .{ .name = ... })` per `ssl`. // Rust cannot `#[no_mangle]` a generic, so monomorphize both here. -// TODO(port): full C-ABI parameter mapping for `connect` (Option<&T> niche, -// Option> niche, raw `*const BunString` arrays). Verify against the -// C++ caller in JSWebSocket.cpp / WebSocket.cpp. +// +// C-ABI mapping (verified against the declarations in +// src/jsc/bindings/headers.h and the call sites in WebSocket.cpp): +// - non-null `const BunString*` params (host/path/protocols) → `&BunString`; +// - nullable `const BunString*` params (proxyHost/proxyAuthorization/ +// targetAuthorization/unixSocketPath, passed as `nullptr` or `&local`) +// → `Option<&BunString>` (guaranteed null-pointer niche); +// - `BunString*` array + `size_t` count pairs → `*const BunString` + `usize` +// (count may be 0 with a dangling/null begin(); never dereferenced then); +// - `void* sslConfig` (ownership transferred, boxed by +// `Bun__WebSocket__parseSSLConfig`) → `Option>` +// (null-pointer niche; Box matches the transferred ownership). +// Keep these signatures in sync with headers.h if either side changes. // ────────────────────────────────────────────────────────────────────────── macro_rules! export_http_client { @@ -2275,7 +2282,7 @@ macro_rules! export_http_client { }; }; } -// PORT NOTE: `${concat(...)}` metavar-expr is unstable; hand-expand the two +// `${concat(...)}` metavar-expr is unstable; hand-expand the two // instantiations by passing the pre-concatenated idents. export_http_client!( diff --git a/src/http_types/ETag.rs b/src/http_types/ETag.rs index 24222cac362f..4d9d757d9b3a 100644 --- a/src/http_types/ETag.rs +++ b/src/http_types/ETag.rs @@ -1,6 +1,5 @@ use bun_core::strings; -// PORT NOTE: Zig anonymous return struct `{ tag: []const u8, is_weak: bool }`. // Borrows from the input slice; not a persistent heap struct. struct Parsed<'a> { tag: &'a [u8], @@ -16,8 +15,8 @@ fn parse(tag_str: &[u8]) -> Parsed<'_> { if str.starts_with(b"W/") { is_weak = true; str = &str[2..]; - // PORT NOTE: Zig `std.mem.trimLeft(u8, str, " \t")` — bun_string has no - // multi-char trim_left; inline it (trailing was already stripped above). + // bun_string has no multi-char trim_left; inline it (trailing + // whitespace was already stripped above). while let [b' ' | b'\t', rest @ ..] = str { str = rest; } @@ -39,8 +38,7 @@ fn weak_match(tag1: &[u8], is_weak1: bool, tag2: &[u8], is_weak2: bool) -> bool tag1 == tag2 } -pub fn append_to_headers(bytes: &[u8], headers: &mut Headers) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set +pub fn append_to_headers(bytes: &[u8], headers: &mut Headers) { let hash: u64 = xxhash64(0, bytes); let mut etag_buf = [0u8; 40]; @@ -54,7 +52,6 @@ pub fn append_to_headers(bytes: &[u8], headers: &mut Headers) -> Result<(), bun_ }; let etag_str = &etag_buf[..len]; headers.append(b"etag", etag_str); - Ok(()) } #[inline] @@ -151,12 +148,11 @@ impl HeaderEntryColumns for HeaderEntryList { pub struct Headers { pub entries: HeaderEntryList, pub buf: Vec, - // PORT NOTE: Zig stored `std.mem.Allocator param`; non-AST crate → - // global mimalloc, field dropped (PORTING.md §allocators). + // No allocator field: non-AST crate → global mimalloc (PORTING.md §allocators). } impl Clone for Headers { - // PORT NOTE: Zig `!Headers`; only fallible calls were allocations — abort on OOM. + // The only fallible calls are allocations — abort on OOM. fn clone(&self) -> Headers { Headers { entries: self @@ -185,7 +181,7 @@ impl Headers { None } - // PORT NOTE: was `!void`; only `try` sites were allocations — abort on OOM. + // The only fallible calls are allocations — abort on OOM. pub fn append(&mut self, name: &[u8], value: &[u8]) { let mut offset: u32 = u32::try_from(self.buf.len()).unwrap(); self.buf.reserve(name.len() + value.len()); @@ -210,8 +206,6 @@ impl Headers { .unwrap_or_else(|_| bun_alloc::out_of_memory()); } - // PORT NOTE: Zig `deinit()` — handled by Drop on Vec/MultiArrayList. - pub fn get_content_disposition(&self) -> Option<&[u8]> { self.get(b"content-disposition") } diff --git a/src/http_types/Method.rs b/src/http_types/Method.rs index 9f02d2335c0c..91a9a0222041 100644 --- a/src/http_types/Method.rs +++ b/src/http_types/Method.rs @@ -49,11 +49,6 @@ pub enum Method { pub type Set = EnumSet; -// PORT NOTE: Zig's private `with_body`/`with_request_body` EnumSet consts are -// folded directly into `has_body()`/`has_request_body()` as `matches!` — -// `EnumSet::remove` is not const on stable, and the consts were never read -// outside those two predicates. - impl Method { /// Port of Zig `@tagName(method)` — uppercase HTTP method token. `M_SEARCH` /// renders as `"M-SEARCH"` (the wire form, matching the Zig enum name diff --git a/src/http_types/MimeType.rs b/src/http_types/MimeType.rs index 0d0434a56b68..99f097caae20 100644 --- a/src/http_types/MimeType.rs +++ b/src/http_types/MimeType.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use bun_core::strings; -// PORT NOTE (cyclebreak): `by_loader` needs `bun_ast::Loader`, but +// Cyclebreak: `by_loader` needs `bun_ast::Loader`, but // adding that dep creates a cargo cycle // (http_types → options_types → zlib → io → uws_sys → http_types). The Loader // enum is `#[repr(u8)]` with stable discriminants (pinned by @@ -27,10 +27,13 @@ mod loader_disc { pub use super::mime_type_list_enum::MimeTypeList as Table; use bun_collections::StringHashMap; -// PORT NOTE: `Table` variant names in Zig are raw MIME-type strings +// `Table` variant names in Zig are raw MIME-type strings // (e.g. `@"application/json"`), which are not valid Rust identifiers. -// `mime_type_list_enum.rs` exposes `const fn from_mime_literal(&'static str)`; -// `t!("...")` resolves to the corresponding `Table` value at compile time. +// `mime_type_list_enum.rs` exposes `const fn from_mime_literal(&'static str)`, +// an UNCHECKED literal wrapper: a typo'd literal still compiles and simply +// never matches anything at runtime (comparison is string equality, not Zig's +// enum compare). The checked, packed-enum form is pending the codegen `.rs` +// backend — see the header of `mime_type_list_enum.rs`. macro_rules! t { ($s:literal) => { Table::from_mime_literal($s) @@ -68,8 +71,9 @@ impl Compact { <&'static str>::from(self.value), ); - // TODO(port): Zig matches on `Table` enum variants directly; we compare against - // `t!` placeholders because variant idents are not yet defined (see top-of-file note). + // Zig matches on `Table` enum variants directly; here `t!` wraps an + // unchecked `&'static str` literal and the compares are runtime string + // equality (see the macro definition above for the caveats). let v = self.value; if v == t!("application/webassembly") { return WASM; @@ -158,7 +162,6 @@ pub enum Category { impl Category { pub fn from_table(entry: Table) -> Category { - // TODO(port): see top-of-file note re: Table variant idents. if entry == t!("text/javascript") || entry == t!("application/javascript") || entry == t!("application/javascript; charset=utf-8") @@ -338,7 +341,7 @@ impl MimeType { } pub fn init(str_: &[u8], dupe: bool, allocated: Option<&mut bool>) -> MimeType { - // PORT NOTE: Zig signature is `(str_, ?Allocator param, allocated: ?*bool)`. + // Zig signature is `(str_, ?Allocator param, allocated: ?*bool)`. // Allocator presence == "dupe the input"; replaced with `dupe: bool` (see §Allocators). let mut str = str_; if let Some(slash) = str.iter().position(|&b| b == b'/') { @@ -474,20 +477,16 @@ impl MimeType { } #[inline] - fn maybe_dupe(s: &[u8], dupe: bool) -> Cow<'static, [u8]> { - if dupe { - Cow::Owned(s.to_vec()) - } else { - // TODO(port): Zig borrows the input slice here (zero-copy). A non-'static - // borrow would need a struct lifetime param — copying for now. - // PERF(port): was zero-copy borrow - Cow::Owned(s.to_vec()) - } + fn maybe_dupe(s: &[u8], _dupe: bool) -> Cow<'static, [u8]> { + // Zig borrowed the input slice in the no-dupe case (zero-copy). A + // non-'static borrow would need a lifetime parameter on `MimeType`, so + // both cases copy here; never launder the borrow to 'static instead. + Cow::Owned(s.to_vec()) } } // TODO: improve this -// PORT NOTE (cyclebreak): takes the `#[repr(u8)]` discriminant of +// Cyclebreak: takes the `#[repr(u8)]` discriminant of // `bun_ast::Loader` to avoid a same-tier cargo cycle (see // `loader_disc` at top of file). Callers: `by_loader(loader as u8, ext)`. pub fn by_loader(loader: u8, ext: &[u8]) -> MimeType { @@ -519,7 +518,7 @@ pub fn by_name(name: &[u8]) -> MimeType { MimeType::init(name, false, None) } -// PORT NOTE: phf_map! rejects duplicate keys at compile time. The Zig source +// phf_map! rejects duplicate keys at compile time. The Zig source // contained duplicate entries for "tsx", "yaml", "yml" (Zig ComptimeStringMap // silently kept the first occurrence) — later duplicates dropped below. pub(crate) static EXTENSIONS: phf::Map<&'static [u8], Table> = phf::phf_map! { diff --git a/src/http_types/URLPath.rs b/src/http_types/URLPath.rs index 9e377b8dea48..a401a52fa35a 100644 --- a/src/http_types/URLPath.rs +++ b/src/http_types/URLPath.rs @@ -40,7 +40,7 @@ impl URLPath { } } -// PORT NOTE: Zig uses two threadlocal fixed `[1024]u8`/`[16384]u8` buffers and +// Design note: Zig uses two threadlocal fixed `[1024]u8`/`[16384]u8` buffers and // decodes in-place via `fixedBufferStream`, then returns slices into that // threadlocal storage. A growable shared buffer cannot uphold that contract in // Rust (the next `parse()` may reallocate it and dangle every prior URLPath), @@ -49,8 +49,7 @@ impl URLPath { // percent-encoded path, which is the rare case. // PERF(port): was zero-alloc fixed buffers — profile if it shows up on a hot path. -pub fn parse(possibly_encoded_pathname_: &[u8]) -> Result { - // TODO(port): narrow error set +pub fn parse(possibly_encoded_pathname_: &[u8]) -> Result { let mut decoded_pathname: &[u8] = possibly_encoded_pathname_; let mut decoded_storage: Option> = None; let mut needs_redirect = false; diff --git a/src/http_types/h2.rs b/src/http_types/h2.rs index 18ae4f984d9c..af0b685f35b6 100644 --- a/src/http_types/h2.rs +++ b/src/http_types/h2.rs @@ -20,15 +20,14 @@ pub const MAX_STREAM_ID: u32 = i32::MAX as u32; /// `std.math.maxInt(u24)` pub const MAX_FRAME_SIZE: u32 = 0x00FF_FFFF; pub const DEFAULT_WINDOW_SIZE: u32 = u16::MAX as u32; -/// PORT NOTE: Zig type was `u24`; Rust has no `u24`, so widened to `u32`. pub const DEFAULT_MAX_FRAME_SIZE: u32 = 16384; // ─── frame type / flags ───────────────────────── // -// PORT NOTE: Zig `enum(u8) { …, _ }` is non-exhaustive (any u8 is a valid -// value). A `#[repr(u8)]` Rust enum is UB for unknown discriminants received -// off the wire, so callers dispatch on the raw `u8` (`FrameHeader.type_`) and -// only ever use this enum for *outbound* frame construction (`X as u8`). +// Frame types are non-exhaustive on the wire (any u8 is a valid value). A +// `#[repr(u8)]` Rust enum is UB for unknown discriminants received off the +// wire, so callers dispatch on the raw `u8` (`FrameHeader.type_`) and only +// ever use this enum for *outbound* frame construction (`X as u8`). #[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq)] @@ -130,7 +129,7 @@ pub fn u32_from_bytes(src: &[u8]) -> u32 { /// Zig: `packed struct(u32) { reserved: bool = false, uint31: u31 = 0 }`. /// -/// PORT NOTE (intentional divergence): Zig's `toUInt32()` is `@bitCast` of +/// Intentional divergence from the Zig original: Zig's `toUInt32()` is `@bitCast` of /// `packed struct(u32){ reserved: bool, uint31: u31 }`, which on little-endian /// places `reserved` in bit 0 and yields `(uint31 << 1) | reserved`. That is a /// latent RFC 7540 §6.3 bug in Zig's deprecated PRIORITY path — the wire @@ -201,7 +200,7 @@ impl StreamPriority { pub fn from(dst: &mut StreamPriority, src: &[u8]) { bytemuck::bytes_of_mut(dst).copy_from_slice(src); // std.mem.byteSwapAllFields(StreamPriority, dst) — `weight: u8` is a no-op. - // PORT NOTE: brace-expr `{packed.field}` performs an unaligned copy; + // Brace-expr `{packed.field}` performs an unaligned copy; // assignment to a packed field is an unaligned store. No `unsafe`. dst.stream_identifier = u32::swap_bytes(dst.stream_identifier); } diff --git a/src/ini/lib.rs b/src/ini/lib.rs index 078753188a5c..417f136df2b5 100644 --- a/src/ini/lib.rs +++ b/src/ini/lib.rs @@ -1,8 +1,9 @@ #![warn(unused_must_use)] // ────────────────────────────────────────────────────────────────────────── -// Pieces that transitively need the JS-AST (`Expr`/`E::Object`/`Rope`) or the -// schema (`BunInstall`/`NpmRegistry`) are gated behind `// TODO(port):` -// markers pointing at the missing lower-tier symbol. +// The remaining `'static` lifetime erasures and raw-pointer borrow splits in +// this file are documented at each site; removing them is tracked by the +// bun_ini Parser lifetime-restructure work item (external arena, split `env` +// lifetime, `Source` lifetime threading in bun_ast). // ────────────────────────────────────────────────────────────────────────── use core::fmt; @@ -269,13 +270,13 @@ mod draft { pub env: &'a mut DotEnvLoader<'a>, } - // PORT NOTE: Zig `prepareStr` switches its *return type* on a comptime enum + // Zig `prepareStr` switches its *return type* on a comptime enum // param (`.section -> *Rope`, `.key -> []const u8`, `.value -> Expr`). Rust // const generics cannot select a return type, so we keep a single // `prepare_str::()` body for diffability and wrap the result in // `PrepareResult`. Callers unwrap with `.into_*()`. // - // PORT NOTE: `#[derive(ConstParamTy)]` requires nightly `adt_const_params`. + // `#[derive(ConstParamTy)]` requires nightly `adt_const_params`. // Dropped to a runtime arg (the body never uses USAGE in a type position). // PERF(port): was comptime monomorphization. #[derive(PartialEq, Eq, Clone, Copy)] @@ -361,7 +362,7 @@ mod draft { let Some(close_bracket_idx) = line.iter().position(|&b| b == b']') else { // Zig: `orelse continue` — skip the whole line break 'treat_as_key; - // PORT NOTE: reshaped — Zig `continue` from inside labeled block; + // Reshaped — Zig `continue` from inside labeled block; // we set treat_as_key=false and fall through to `continue` below. }; // Make sure the rest is just whitespace @@ -563,7 +564,7 @@ mod draft { usage: Usage, bump: &'a Arena, ropealloc: &'a Arena, - val_: &[u8], + val_: &'a [u8], offset_: i32, ) -> OOM> { let mut offset = offset_; @@ -875,9 +876,9 @@ mod draft { ))); } if usage == Usage::Key { - // TODO(port): lifetime — `val` borrows `val_` (caller line slice); - // Zig returns it directly. Dupe into the bump for now. - return Ok(PrepareResult::Key(bump.alloc_slice_copy(val))); + // `val` is a subslice of `val_: &'a [u8]`; return the borrow + // directly (matches Zig). + return Ok(PrepareResult::Key(val)); } Ok(PrepareResult::Section(Self::str_to_rope(ropealloc, val)?)) } @@ -889,11 +890,11 @@ mod draft { /// - ${VAR} - if VAR is undefined, leave as "${VAR}" (no expansion) /// - ${VAR?} - if VAR is undefined, expand to empty string /// - Backslash escaping is already handled by JSON parsing - fn expand_env_vars(&mut self, bump: &'a Arena, val: &[u8]) -> OOM<&'a [u8]> { + fn expand_env_vars(&mut self, bump: &'a Arena, val: &'a [u8]) -> OOM<&'a [u8]> { // Quick check if there are any env vars to expand if bun_core::index_of(val, b"${").is_none() { - // TODO(port): lifetime — Zig returns `val` directly (arena-borrowed). - return Ok(bump.alloc_slice_copy(val)); + // Nothing to expand: return the borrow directly (matches Zig). + return Ok(val); } let mut result = ArenaVec::::with_capacity_in(val.len(), bump); @@ -1167,7 +1168,7 @@ mod draft { if let Some(keyexpr) = prop.key { if let Some(key) = keyexpr.as_utf8_string_literal() { if bun_core::has_prefix(key, b"//") { - // PORT NOTE: Zig builds this list at comptime by reversing + // Zig builds this list at comptime by reversing // `std.meta.fieldNames(Item.Opt)` so that `_authToken` is // matched before `_auth`. We hard-code the reversed order. const OPTNAMES: &[(&[u8], ConfigOpt)] = &[ @@ -1563,7 +1564,7 @@ mod draft { count }; - // PORT NOTE: Zig's `defer install.scoped = registry_map;` is a shallow + // Zig's `defer install.scoped = registry_map;` is a shallow // write-back at scope end while later code keeps mutating `registry_map`. // Reshaped for borrowck: the single write-back happens at the bottom of // `load_npmrc` after the registry-configuration block. @@ -1600,7 +1601,7 @@ mod draft { break 'out; } - // PORT NOTE: `URL<'a>` borrows its input. The Zig `default_registry_url` + // `URL<'a>` borrows its input. The Zig `default_registry_url` // points into `install.default_registry.url` while the loop below // mutates that same field; copy the two fields we compare against so // the borrow ends before the `install.default_registry` mutation. @@ -1741,7 +1742,7 @@ mod draft { } } - // PORT NOTE: Zig iterated `registry_map.scopes` and looked up `url_map[k]` + // Zig iterated `registry_map.scopes` and looked up `url_map[k]` // by key. In Rust `keys()`/`values_mut()` on the same map alias; since // `url_map` was filled in lockstep with `registry_map.scopes` (same // ArrayHashMap insertion order), zip its values directly instead. @@ -1800,9 +1801,14 @@ mod draft { drop(url_map); } - // TODO(port): Zig's `defer install.scoped = registry_map;` (in the scope-processing - // block) writes back the *final* registry_map after the registry-configuration block - // mutates it. Mirror that here. + // Zig's `defer install.scoped = registry_map;` takes a shallow copy at + // the end of the scope-processing block; the later registry-config loop + // mutates the scope *values* in place through shared heap storage, so + // the deferred copy observes those mutations. Moving the single + // write-back here (after all mutation) yields the same final state on + // the success path. (Zig's defer also fired on mid-block OOM; here an + // OOM `?` leaves `install.scoped` as `None`, which is moot — install + // aborts on OOM.) install.scoped = Some(registry_map); Ok(()) diff --git a/src/install/ConfigVersion.rs b/src/install/ConfigVersion.rs index 053e28446957..d5344069a8a3 100644 --- a/src/install/ConfigVersion.rs +++ b/src/install/ConfigVersion.rs @@ -1,4 +1,4 @@ -// PORT NOTE: the install crate's JSON layer routes through `bun_ast::js_ast` +// The install crate's JSON layer routes through `bun_ast::js_ast` // (see `crate::bun_json`), not the full `bun_js_parser` AST. `from_expr` is // only ever fed nodes from the lockfile JSON parse, so type against that. use bun_ast::{Expr, ExprData}; diff --git a/src/install/NetworkTask.rs b/src/install/NetworkTask.rs index 26b2faff4486..fa65f618c4be 100644 --- a/src/install/NetworkTask.rs +++ b/src/install/NetworkTask.rs @@ -46,7 +46,7 @@ pub struct NetworkTask { // Self-referential: borrows `url_buf` / leaked header content owned by // sibling fields, so the lifetime is erased to `'static`. // - // PORT NOTE: `MaybeUninit` because the slot comes from `HiveArrayFallback` + // `MaybeUninit` because the slot comes from `HiveArrayFallback` // as *uninitialized* memory (often zero-page on first mmap, but not // guaranteed — `get()`'s heap fallback is `Box::new_uninit()`) and is // overwritten by plain `=` in `for_manifest`/`for_tarball`. Zig has no @@ -63,8 +63,9 @@ pub struct NetworkTask { pub unsafe_http_client: MaybeUninit>, pub response: HTTPClientResult<'static>, pub task_id: crate::package_manager_task::Id, - // TODO(port): owned in `for_manifest` (toOwnedSlice) but borrowed from - // `tarball.url` in `for_tarball`; Zig leaks/aliases — verify ownership in Phase B. + // Owned in both `for_manifest` (toOwnedSlice) and `for_tarball`. Zig + // aliased `tarball.url` in the latter; owning here avoids a self-reference + // into `callback` at the cost of one copy per tarball download. pub url_buf: Box<[u8]>, pub retried: u16, // Zig: `std.mem.Allocator param` — dropped (global mimalloc); see §Allocators. @@ -76,7 +77,7 @@ pub struct NetworkTask { pub package_manager: bun_ptr::ParentRef, pub callback: Callback, /// Key in patchedDependencies in package.json - // PORT NOTE: `'static` because NetworkTask is stored lifetime-less in + // `'static` because NetworkTask is stored lifetime-less in // `PreallocatedNetworkTasks`; PatchTask's `'a` is a BACKREF on pub apply_patch_task: Option>, pub next: bun_threading::Link, @@ -87,7 +88,7 @@ pub struct NetworkTask { pub tarball_stream: Option>, /// Extract `Task` pre-created on the main thread so the HTTP thread can /// schedule it on the worker pool as soon as the first body chunk arrives. - // PORT NOTE: `'static` matches `PreallocatedTaskStore = + // `'static` matches `PreallocatedTaskStore = // HiveArrayFallback, 64>` which this slot is borrowed from // and returned to (`discard_unused_streaming_state`). pub streaming_extract_task: *mut Task<'static>, @@ -114,9 +115,9 @@ unsafe impl bun_threading::Linked for NetworkTask { } } -/// Zig: `union(Task.Tag)` — tag type is `Task.Tag`. -// TODO(port): ensure discriminants match `crate::task::Tag` ordering for any -// code that transmutes between them. +/// Zig: `union(Task.Tag)` — variants mirror `crate::package_manager_task::Tag` +/// in the same order. Nothing transmutes between the two (all consumers +/// `match` on this enum), so the ordering is documentation, not an invariant. pub enum Callback { PackageManifest { loaded_manifest: Option, @@ -133,11 +134,22 @@ pub enum Callback { pub struct DedupeMapEntry { pub is_required: bool, } -// Zig: `std.HashMap(Task.Id, DedupeMapEntry, IdentityContext(Task.Id), 80)` -// TODO(port): IdentityContext (hash = value bits) + 80% load factor — verify -// `bun_collections::HashMap` exposes an identity hasher, or newtype `task::Id` -// with a pass-through `Hash` impl. -pub(crate) type DedupeMap = HashMap; +/// `Id` is already a wyhash output, so identity hashing matches Zig's +/// `IdentityContext(Task.Id)` (hash = value bits) and avoids re-hashing. +impl bun_collections::IdentityHash for crate::package_manager_task::Id { + #[inline] + fn identity_hash(self) -> u64 { + self.get() + } +} + +// Zig: `std.HashMap(Task.Id, DedupeMapEntry, IdentityContext(Task.Id), 80)`. +// `bun_collections::HashMap` uses the same 80% max load factor for all maps. +pub(crate) type DedupeMap = HashMap< + crate::package_manager_task::Id, + DedupeMapEntry, + bun_collections::IdentityContext, +>; impl NetworkTask { /// Access the HTTP client after `for_manifest`/`for_tarball` (or `notify`'s @@ -177,7 +189,7 @@ impl NetworkTask { unsafe { self.package_manager.assume_mut() } } - // PORT NOTE: signature matches `HTTPClientResultCallback::new::`'s + // Signature matches `HTTPClientResultCallback::new::`'s // `fn(*mut T, *mut AsyncHTTP, HTTPClientResult<'_>)` shape so it can be // installed directly without a separate trampoline. fn notify( @@ -346,7 +358,7 @@ const DEFAULT_HEADERS_BUF: &str = concat!( const EXTENDED_HEADERS_BUF: &str = concat!("Accept", "application/json, */*"); fn append_auth(header_builder: &mut HeaderBuilder, scope: &npm::registry::Scope) { - // PORT NOTE: Zig `appendFmt("Authorization", "Bearer {s}", .{scope.token})` + // Zig `appendFmt("Authorization", "Bearer {s}", .{scope.token})` // writes raw bytes; routing through `format_args!`/`BStr` Display would be // lossy for non-UTF-8 tokens (U+FFFD expands 1→3 bytes) and overrun the // exact byte count reserved by `count_auth`. Use raw-byte append. @@ -621,14 +633,13 @@ impl NetworkTask { // HTTP client below (`ManuallyDrop`), so it genuinely outlives this frame. let headers_buf: &'static [u8] = unsafe { bun_ptr::detach_lifetime(header_builder.content.written_slice()) }; - // PORT NOTE: Zig has no destructors — `header_builder.content` is - // intentionally leaked (ownership transfers to the HTTP client). - // Forget it so `StringBuilder::drop` doesn't free the buffer that - // `headers_buf` / `last_modified` now alias. + // `header_builder.content` is intentionally leaked (ownership + // transfers to the HTTP client; Zig has no destructors). Forget it so + // `StringBuilder::drop` doesn't free the buffer that `headers_buf` / + // `last_modified` now alias. let _ = ManuallyDrop::new(core::mem::take(&mut header_builder.content)); let completion_callback = self.get_completion_callback(); - // TODO(port): narrow error set - // PORT NOTE: MaybeUninit overwrite — see field doc; old slot value is + // MaybeUninit overwrite — see field doc; old slot value is // either uninitialized (fresh hive slot) or a stale bitwise copy from // `notify`, neither of which is safe/meaningful to drop. self.unsafe_http_client = MaybeUninit::new(AsyncHTTP::init( @@ -686,7 +697,7 @@ impl NetworkTask { } pub fn get_completion_callback(&mut self) -> HTTPClientResultCallback { - // PORT NOTE: Zig `Callback.New(*NetworkTask, notify).init(this)` is a + // Zig `Callback.New(*NetworkTask, notify).init(this)` is a // comptime type-erased thunk generator. `HTTPClientResultCallback::new` // performs the same erasure over a `fn(*mut T, *mut AsyncHTTP, _)`. HTTPClientResultCallback::new::(self, Self::notify) @@ -746,8 +757,8 @@ impl NetworkTask { pm.lockfile.buffers.string_bytes.as_slice(), )?) } else { - // TODO(port): Zig aliases `tarball.url` here without copying; - // `url_buf: Box<[u8]>` forces an allocation. Revisit ownership. + // Zig aliases `tarball.url` here without copying; owning the copy + // avoids a self-reference into `callback` (see `url_buf` field doc). Box::<[u8]>::from(tarball_url) }; self.callback = Callback::Extract(tarball_); @@ -816,10 +827,10 @@ impl NetworkTask { header_buf = unsafe { bun_ptr::detach_lifetime(header_builder.content.written_slice()) }; } - // PORT NOTE: Zig has no destructors — `header_builder.content` is - // intentionally leaked (ownership transfers to the HTTP client). - // Forget it so `StringBuilder::drop` doesn't free the buffer that - // `header_buf` now aliases. + // `header_builder.content` is intentionally leaked (ownership + // transfers to the HTTP client; Zig has no destructors). Forget it so + // `StringBuilder::drop` doesn't free the buffer that `header_buf` now + // aliases. let _ = ManuallyDrop::new(core::mem::take(&mut header_builder.content)); // SAFETY: lifetime extension — `url_buf` is a heap allocation owned by @@ -865,7 +876,7 @@ impl NetworkTask { } let completion_callback = self.get_completion_callback(); - // PORT NOTE: MaybeUninit overwrite — see field doc; old slot value is + // MaybeUninit overwrite — see field doc; old slot value is // either uninitialized (fresh hive slot) or a stale bitwise copy from // `notify`, neither of which is safe/meaningful to drop. self.unsafe_http_client = MaybeUninit::new(AsyncHTTP::init( diff --git a/src/install/PackageInstall.rs b/src/install/PackageInstall.rs index e03ffc64510c..65a5a398c40a 100644 --- a/src/install/PackageInstall.rs +++ b/src/install/PackageInstall.rs @@ -20,9 +20,7 @@ use crate::{ TruncatedPackageNameHash, bun_fs, bun_json, buntaghashbuf_make, initialize_store, resolution, }; -// TODO(port): `std.fs.Dir` is used pervasively here; Zig has a TODO to switch to `bun.FD.Dir`. -// Mapped to `bun_sys::Dir` (an Fd-backed dir handle). Method names (.close(), .fd, -// .makeOpenPath, .deleteTree, .realpath, .makeDirZ) are assumed on that type. +// Zig's `std.fs.Dir` maps to `bun_sys::Dir` (an Fd-backed dir handle). bun_output::declare_scope!(install, hidden); @@ -158,7 +156,7 @@ impl Method { pub(crate) fn is_supported(self) -> bool { #[cfg(target_os = "macos")] return Self::macos()[self]; - // PORT NOTE: Zig `Environment.isLinux` (os.tag == .linux) includes the Android ABI, + // Zig `Environment.isLinux` (os.tag == .linux) includes the Android ABI, // whereas Rust's `target_os = "linux"` does not — list `android` explicitly. #[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))] return Self::linux()[self]; @@ -280,10 +278,10 @@ impl PackageInstall<'_> { struct InstallDirState { cached_package_dir: Dir, - // PORT NOTE: `Walker` has no `Default`; Zig used `undefined`. Wrap in Option. + // `Walker` has no `Default`; Zig used `undefined`. Wrap in Option. walker: Option, subdir: Dir, - // PORT NOTE: Zig's `buf: bun.windows.WPathBuffer = undefined` is in-place + // Zig's `buf: bun.windows.WPathBuffer = undefined` is in-place // (result-location, no zero-fill). A by-value `WPathBuffer` here would // memset+move ~128 KB through `Default::default()` per package. Use the // thread-local pool guard (heap-backed, uninit) so construction is O(1) @@ -292,7 +290,7 @@ struct InstallDirState { buf: bun_paths::w_path_buffer_pool::Guard, #[cfg(windows)] buf2: bun_paths::w_path_buffer_pool::Guard, - // PORT NOTE: Zig kept `to_copy_buf: []u16` as a self-referential slice into + // Zig kept `to_copy_buf: []u16` as a self-referential slice into // `buf`. The Rust port only ever read its `.len()` to recover the offset, so // store the offset directly — no self-referential raw fat pointer needed. #[cfg(windows)] @@ -542,7 +540,7 @@ impl HardLinkWindowsInstallTask { // whose internal Release/Acquire on the task queue is what publishes the // first-call `MaybeUninit::write` below. // - // PORT NOTE: Zig re-assigns the whole struct each call (`queue = Queue{...}`). + // Zig re-assigns the whole struct each call (`queue = Queue{...}`). // We must NOT do that: even though `copy()` always reaches `queue.wait()` // before returning (see the loop_err handling there), `WaitGroup::wait()` can // return while the last worker is still inside `WaitGroup::finish()` *after* @@ -706,7 +704,7 @@ impl UninstallTask { // SAFETY: task points to the `task` field of an UninstallTask. let uninstall_task: *mut Self = unsafe { bun_core::from_field_ptr!(Self, task, task) }; - // PORT NOTE: declared *before* the Box is reclaimed so it drops *after* the + // declared *before* the Box is reclaimed so it drops *after* the // Box — Rust drops locals in reverse declaration order. Zig's `defer task.deinit()` // runs before `defer { decrementPendingTasks(); wake() }`, i.e. the task is freed // before the main thread can observe pending_tasks==0. @@ -814,7 +812,7 @@ impl<'a> PackageInstall<'a> { fn verify_git_resolution(&mut self, repo: &Repository, root_node_modules_dir: &Dir) -> bool { let dest_len = self.destination_dir_subpath.len(); let suffix: &[u8] = &[SEP, b'.', b'b', b'u', b'n', b'-', b't', b'a', b'g']; - // PORT NOTE: reshaped for borrowck — write into buf via raw indices. + // Reshaped for borrowck — write into buf via raw indices. self.destination_dir_subpath_buf[dest_len..dest_len + suffix.len()].copy_from_slice(suffix); self.destination_dir_subpath_buf[dest_len + SEP_STR.len() + b".bun-tag".len()] = 0; // SAFETY: NUL written above. @@ -934,7 +932,7 @@ impl<'a> PackageInstall<'a> { total += read; mutable.expand_to_capacity(); - // PORT NOTE: reshaped for borrowck — recompute remain after grow. + // Reshaped for borrowck — recompute remain after grow. remain = &mut mutable.list[total..]; if remain.len() < 1024 { @@ -993,7 +991,7 @@ impl<'a> PackageInstall<'a> { initialize_store(); - // PORT NOTE: Zig passed `allocator` (= `bun.default_allocator`, the global + // Zig passed `allocator` (= `bun.default_allocator`, the global // mimalloc heap). `Arena::new()` here was creating + destroying a fresh // `mi_heap` per package — measurable on no-op installs (~390× heap churn // on create-next/elysia). `borrowing_default()` wraps `mi_heap_main()` @@ -1009,7 +1007,7 @@ impl<'a> PackageInstall<'a> { if package_json_checker.parse_expr().is_err() { return false; } - // PORT NOTE: reshaped for borrowck — `log` is exclusively borrowed by the + // Reshaped for borrowck — `log` is exclusively borrowed by the // checker's lexer; route the read through `lexer.log_mut()` (single // provenance chain, see PackageJSONVersionChecker doc). if package_json_checker.lexer.log_mut().errors > 0 || !package_json_checker.has_found_name { @@ -1080,7 +1078,6 @@ impl<'a> PackageInstall<'a> { walker_.resolve_unknown_entry_types = true; fn copy(destination_dir_: &Dir, walker: &mut Walker) -> Result { - // TODO(port): narrow error set let mut real_file_count: u32 = 0; let mut stackpath = [0u8; path::MAX_PATH_BYTES]; while let Some(entry) = walker.next()? { @@ -1212,7 +1209,7 @@ impl<'a> PackageInstall<'a> { Err(err) => return InstallResult::fail(err, Step::OpeningCacheDir, None), }; - // PORT NOTE: `bun.OSPathLiteral("node_modules")` — u8 on posix / u16 on windows. + // `bun.OSPathLiteral("node_modules")` — u8 on posix / u16 on windows. #[cfg(windows)] const NODE_MODULES_LIT: &OSPathSlice = &[ b'n' as u16, @@ -1260,7 +1257,7 @@ impl<'a> PackageInstall<'a> { ) { Ok(d) => d, Err(err) => { - // PORT NOTE: Zig closed cached_package_dir/walker explicitly here because the + // Zig closed cached_package_dir/walker explicitly here because the // caller's `defer state.deinit()` is placed AFTER the `is_fail()` early-return. // In Rust, Drop on the caller's `state` runs unconditionally on that early // return, so explicit close here would double-close. Drop handles it. @@ -1293,7 +1290,7 @@ impl<'a> PackageInstall<'a> { } else { bun_core::err!("NameTooLong") }; - // PORT NOTE: Drop on caller's `state` closes cached_package_dir; explicit close + // Drop on caller's `state` closes cached_package_dir; explicit close // here would double-close (see posix branch above for full rationale). return InstallResult::fail(err, Step::OpeningDestDir, None); } @@ -1332,11 +1329,11 @@ impl<'a> PackageInstall<'a> { } else { bun_core::err!("NameTooLong") }; - // PORT NOTE: Drop on caller's `state` closes cached_package_dir; explicit close + // Drop on caller's `state` closes cached_package_dir; explicit close // here would double-close (see posix branch above for full rationale). return InstallResult::fail(err, Step::CopyingFiles, None); } - // PORT NOTE: borrowck — Zig held `cache_path = buf2[0..len]` while mutating + // borrowck — Zig held `cache_path = buf2[0..len]` while mutating // buf2; index by `cache_path_length` directly so no shared borrow is live. state.to_copy_buf2_off = if state.buf2[cache_path_length - 1] != u16::from(b'\\') { state.buf2[cache_path_length] = u16::from(b'\\'); @@ -1364,7 +1361,7 @@ impl<'a> PackageInstall<'a> { #[cfg(not(windows))] type WinOffset = (); - // PORT NOTE: reshaped for borrowck — Zig passed two overlapping slices into the + // Reshaped for borrowck — Zig passed two overlapping slices into the // same buffer (`head` is the whole buffer, `to_copy_into` is its tail). Creating // two live `&mut [u16]` that alias is UB in Rust, so pass head buffer + tail // offset and reslice inside. @@ -1377,7 +1374,6 @@ impl<'a> PackageInstall<'a> { to_copy_into2_offset: WinOffset, head2: WinSlice<'_>, ) -> Result { - // TODO(port): narrow error set #[cfg(not(windows))] let mut real_file_count: u32 = 0; #[cfg(windows)] @@ -1620,7 +1616,7 @@ impl<'a> PackageInstall<'a> { #[cfg(not(windows))] type WinOffset = (); - // PORT NOTE: reshaped for borrowck — Zig passed two overlapping slices into the + // Reshaped for borrowck — Zig passed two overlapping slices into the // same buffer (`head` is the whole buffer, `to_copy_into` is its tail). Creating // two live `&mut [u16]` that alias is UB in Rust, so pass head buffer + tail // offset and reslice inside. @@ -1632,7 +1628,6 @@ impl<'a> PackageInstall<'a> { to_copy_into2_offset: WinOffset, head2: WinSlice<'_>, ) -> Result { - // TODO(port): narrow error set let mut real_file_count: u32 = 0; #[cfg(not(windows))] let _ = (to_copy_into1_offset, head1, to_copy_into2_offset, head2); @@ -1640,7 +1635,7 @@ impl<'a> PackageInstall<'a> { let _ = destination_dir; #[cfg(windows)] let queue = HardLinkWindowsInstallTask::init_queue(); - // PORT NOTE: on Windows, tasks already pushed to `queue` are running on + // on Windows, tasks already pushed to `queue` are running on // worker threads; an early `?` here would return before `queue.wait()`, // letting the caller re-enter `init_queue()` and reset the WaitGroup // while workers are still inside `complete_one()` (data race on the @@ -1841,7 +1836,7 @@ impl<'a> PackageInstall<'a> { #[cfg(not(windows))] type Head2Char = u8; - // PORT NOTE: reshaped for borrowck — Zig passed two overlapping slices into the + // Reshaped for borrowck — Zig passed two overlapping slices into the // same buffer (`head` is the whole buffer, `to_copy_into` is its tail). Creating // two live `&mut` that alias is UB in Rust, so pass head buffer + tail offset // and reslice inside. @@ -1853,7 +1848,6 @@ impl<'a> PackageInstall<'a> { to_copy_into2_offset: usize, head2: &mut [Head2Char], ) -> Result { - // TODO(port): narrow error set #[cfg(not(windows))] let mut real_file_count: u32 = 0; #[cfg(windows)] @@ -2168,7 +2162,7 @@ impl<'a> PackageInstall<'a> { self.uninstall_before_install(destination_dir); } - // PORT NOTE: Zig `std.fs.path.dirname` returns null when there is no directory + // Zig `std.fs.path.dirname` returns null when there is no directory // component; mirror that with an Option around resolve_path::dirname. let dirname_slice = path::resolve_path::dirname::(dest_path.as_bytes()); @@ -2182,9 +2176,9 @@ impl<'a> PackageInstall<'a> { let mut to_buf = PathBuffer::uninit(); // Zig: `this.cache_dir.realpath(symlinked_path, &to_buf)` — open the target relative // to cache_dir, then resolve its canonical path. - // PORT NOTE: reshaped from an IIFE — returning a borrow of `to_buf` from an + // Reshaped from an IIFE — returning a borrow of `to_buf` from an // `FnMut` closure is rejected by borrowck, so inline the open/getFdPath/close. - // PORT NOTE: Zig `std.fs.Dir.realpath` returns std-style error names + // Zig `std.fs.Dir.realpath` returns std-style error names // (`error.FileNotFound`, `error.AccessDenied`, …), not raw errno tags. // `bun_sys::Error::into()` would yield `ENOENT`/`EACCES`, so map the // openat errno to the std name to preserve the user-visible error tag @@ -2338,7 +2332,7 @@ impl<'a> PackageInstall<'a> { }; let target = path::resolve_path::relative(dest_dir_path, to_path); - // PORT NOTE: `symlinkat` takes `&ZStr` for both target and dest; build NUL-terminated + // `symlinkat` takes `&ZStr` for both target and dest; build NUL-terminated // copies in stack buffers (Zig used `std.posix.symlinkat` which does this internally). let mut target_buf = PathBuffer::uninit(); target_buf[..target.len()].copy_from_slice(target); @@ -2433,7 +2427,7 @@ impl<'a> PackageInstall<'a> { }); let cache_dir_subpath_without_patch_hash = &self.cache_dir_subpath.as_bytes()[..idx]; - // PORT NOTE: Zig wrote into the global `bun.path.join_buf` thread-local; use a + // Zig wrote into the global `bun.path.join_buf` thread-local; use a // stack PathBuffer here instead (same size, no shared state). let mut join_buf = PathBuffer::uninit(); join_buf[..cache_dir_subpath_without_patch_hash.len()] diff --git a/src/install/PackageInstaller.rs b/src/install/PackageInstaller.rs index 6db646995ee1..9778e845c68a 100644 --- a/src/install/PackageInstaller.rs +++ b/src/install/PackageInstaller.rs @@ -16,7 +16,7 @@ use crate::bun_fs::FileSystem; use crate::bun_progress::{Node as ProgressNode, Progress}; use crate::lifecycle_script_runner::LifecycleScriptSubprocess; -// PORT NOTE: `Lockfile` here is the in-crate `crate::lockfile::Lockfile` (the +// `Lockfile` here is the in-crate `crate::lockfile::Lockfile` (the // struct `PackageManager.lockfile` actually carries). `lockfile_real` is still // imported for `tree::Id` / `Tree` / `DependencySlice` / `package::*`, all of // which are the same types re-exported through `crate::lockfile`. @@ -71,7 +71,7 @@ pub struct PackageInstaller<'a> { pub force_install: bool, pub root_node_modules_folder: Dir, pub summary: &'a mut package_install::Summary, - // PORT NOTE: Zig also stored `options: *const Options` (a BACKREF into + // Zig also stored `options: *const Options` (a BACKREF into // `(*manager).options`). Dropped — every caller reads via // `self.manager().options` so the shared borrow stays a child of the live // `&mut PackageManager` Unique tag rather than a sibling raw. @@ -179,7 +179,6 @@ impl NodeModulesFolder { root_node_modules_dir: &Dir, file_path: &ZStr, ) -> Result { - // TODO(port): narrow error set let file = self.open_file(root_node_modules_dir, file_path)?; let res = file.read_to_end_small(); let _ = file.close(); // close error is non-actionable (Zig parity: discarded) @@ -197,7 +196,6 @@ impl NodeModulesFolder { root_node_modules_dir: &Dir, file_path: &ZStr, ) -> Result { - // TODO(port): narrow error set if self.path.len() + file_path.len() * 2 < MAX_PATH_BYTES { // If we do not run the risk of ENAMETOOLONG, then let's just avoid opening the extra directories altogether. match self.open_file_without_opening_directories(root_node_modules_dir, file_path) { @@ -221,10 +219,9 @@ impl NodeModulesFolder { } pub(crate) fn open_dir(&self, root: &Dir) -> Result { - // TODO(port): narrow error set #[cfg(unix)] { - // PORT NOTE: std.posix.toPosixPath — copies into a NUL-terminated PathBuffer + // std.posix.toPosixPath — copies into a NUL-terminated PathBuffer let mut path_buf = PathBuffer::uninit(); let path_z = bun_paths::resolve_path::z(self.path.as_slice(), &mut path_buf); return root @@ -250,7 +247,6 @@ impl NodeModulesFolder { } pub(crate) fn make_and_open_dir(&mut self, root: &Dir) -> Result { - // TODO(port): narrow error set let out = 'brk: { #[cfg(unix)] { @@ -303,7 +299,7 @@ pub struct TreeContext { pub(crate) type TreeContextId = lockfile::tree::Id; -// PORT NOTE: TreeContext::deinit dropped — Vec and Bin::PriorityQueue impl Drop. +// TreeContext::deinit dropped — Vec and Bin::PriorityQueue impl Drop. pub(crate) enum LazyPackageDestinationDir<'a> { /// Non-owning view of a directory handle the caller owns. @@ -322,7 +318,6 @@ pub(crate) enum LazyPackageDestinationDir<'a> { impl<'a> LazyPackageDestinationDir<'a> { #[allow(dead_code)] pub(crate) fn get_dir(&mut self) -> Result { - // TODO(port): narrow error set match self { LazyPackageDestinationDir::Dir(fd) => Ok(*fd), LazyPackageDestinationDir::Owned(dir) => Ok(dir.fd()), @@ -432,7 +427,7 @@ impl<'a> PackageInstaller<'a> { /// Increments the number of installed packages for a tree id and runs available scripts /// if the tree is finished. - // PORT NOTE: Zig parametrised this on `comptime should_install_packages: bool`. + // Zig parametrised this on `comptime should_install_packages: bool`. // Rust can't pass `!CONST_PARAM` as a const-generic arg on stable, and the // bool only gates a single call below, so it's a runtime arg here. pub(crate) fn increment_tree_install_count( @@ -482,7 +477,7 @@ impl<'a> PackageInstaller<'a> { let mut link_target_buf = PathBuffer::uninit(); let mut link_dest_buf = PathBuffer::uninit(); let mut link_rel_buf = PathBuffer::uninit(); - // PORT NOTE: reshaped for borrowck — pass tree_id, re-borrow tree inside. + // reshaped for borrowck — pass tree_id, re-borrow tree inside. self.link_tree_bins( tree_id, link_target_buf.as_mut_slice(), @@ -501,7 +496,7 @@ impl<'a> PackageInstaller<'a> { pub(crate) fn link_tree_bins( &mut self, - // PORT NOTE: zig passes `tree: *TreeContext` + `tree_id`; reshaped to take only + // zig passes `tree: *TreeContext` + `tree_id`; reshaped to take only // `tree_id` and re-borrow `&mut self.trees[tree_id]` to satisfy borrowck. tree_id: TreeContextId, link_target_buf: &mut [u8], @@ -514,7 +509,7 @@ impl<'a> PackageInstaller<'a> { let string_buf = lockfile.buffers.string_bytes.as_slice(); let mut node_modules_path: AbsPath = AbsPath::from(self.node_modules.path.as_slice()).unwrap_or_oom(); - // PORT NOTE: `defer node_modules_path.deinit()` — AbsPath impls Drop. + // `defer node_modules_path.deinit()` — AbsPath impls Drop. let pkgs = lockfile.packages.slice(); let pkg_name_hashes = pkgs.items_name_hash(); @@ -539,7 +534,7 @@ impl<'a> PackageInstaller<'a> { let mut target_package_name = package_name_; let mut can_retry_without_native_binlink_optimization = false; let target_node_modules_path_opt: Option = None; - // PORT NOTE: `defer if (target_node_modules_path_opt) |*path| path.deinit()` — Option drops. + // `defer if (target_node_modules_path_opt) |*path| path.deinit()` — Option drops. 'native_binlink_optimization: { if !manager.postinstall_optimizer.is_native_binlink_enabled() { @@ -602,7 +597,7 @@ impl<'a> PackageInstaller<'a> { }; loop { - // PORT NOTE: reshaped for borrowck — Zig aliases the same `*AbsPath` for + // reshaped for borrowck — Zig aliases the same `*AbsPath` for // both `node_modules_path` (mut) and `target_node_modules_path` (read-only) // when no replacement is set. Derive both from a single `*mut` so the // read pointer shares the write reference's provenance (a `*const` taken @@ -684,7 +679,7 @@ impl<'a> PackageInstaller<'a> { let trees_len = self.trees.len(); for tree_id in 0..trees_len { - // PORT NOTE: reshaped for borrowck — index instead of `for (self.trees, 0..) |*tree, tree_id|`. + // reshaped for borrowck — index instead of `for (self.trees, 0..) |*tree, tree_id|`. if self.trees[tree_id].binaries.count() > 0 { self.seen_bin_links.clear(); self.node_modules.path.truncate( @@ -728,7 +723,7 @@ impl<'a> PackageInstaller<'a> { let optional = self.pending_lifecycle_scripts[i].optional; if self.can_run_scripts(tree_id) { let entry = self.pending_lifecycle_scripts.swap_remove(i); - // PORT NOTE: reshaped for borrowck — `package_name` is `Box<[u8]>`; + // reshaped for borrowck — `package_name` is `Box<[u8]>`; // clone it for the error message since `entry.list` is moved into `spawn`. let name: Box<[u8]> = entry.list.package_name.clone(); let output_in_foreground = false; @@ -785,17 +780,15 @@ impl<'a> PackageInstaller<'a> { &mut self, log_level: Options::LogLevel, ) { - // TODO(refactor): defer save/restore of self.node_modules / self.current_tree_id. - // Zig does a struct-copy of NodeModulesFolder (ptr+len+cap) and restores on scope - // exit. In Rust this needs `core::mem::take` + scopeguard, but scopeguard cannot - // capture `&mut self` alongside the loop body's `&mut self`. Could hoist into - // a helper that takes the saved values by move and restores after the loop. + // Manual save/restore of self.node_modules / self.current_tree_id (Zig restored + // via `defer`): a guard cannot capture `&mut self` alongside the loop body's + // `&mut self`. The function is infallible and its single exit below restores. let prev_node_modules = core::mem::take(&mut self.node_modules); let prev_tree_id = self.current_tree_id; let trees_len = self.trees.len(); for i in 0..trees_len { - // PORT NOTE: reshaped for borrowck — index instead of iter_mut. + // reshaped for borrowck — index instead of iter_mut. if FORCE || Self::can_install_package_for_tree( &self.completed_trees, @@ -810,7 +803,7 @@ impl<'a> PackageInstaller<'a> { // going up ensures we will reach any trees that will be able to install // packages upon completing the current tree // - // PORT NOTE: spec iterates `tree.pending_installs.items` by struct + // spec iterates `tree.pending_installs.items` by struct // copy (each `context.path` is the same allocation that lives in // `pending_installs`) and `defer clearRetainingCapacity()` at the end. // Drain by move (`mem::take`) to transfer ownership without the @@ -849,7 +842,7 @@ impl<'a> PackageInstaller<'a> { } pub(crate) fn complete_remaining_scripts(&mut self, log_level: Options::LogLevel) { - // PORT NOTE: reshaped for borrowck — drain by move since loop body needs `&mut + // reshaped for borrowck — drain by move since loop body needs `&mut // self.manager` and `spawn_package_lifecycle_scripts` consumes the list. Zig // iterated by struct copy and never re-read `pending_lifecycle_scripts` after. for entry in core::mem::take(&mut self.pending_lifecycle_scripts) { @@ -944,7 +937,7 @@ impl<'a> PackageInstaller<'a> { /// A tree can start installing packages when the parent has installed all its packages. If the parent /// isn't finished, we need to wait because it's possible a package installed in this tree will be deleted by the parent. - // PORT NOTE: free fn (not `&self`) so callers can pass disjoint borrows + // free fn (not `&self`) so callers can pass disjoint borrows // (`&self.completed_trees` + `&self.lockfile().buffers.trees`) without // tripping borrowck on the whole-`self` reborrow. pub(crate) fn can_install_package_for_tree( @@ -963,7 +956,7 @@ impl<'a> PackageInstaller<'a> { true } - // PORT NOTE: `pub fn deinit` dropped. All owned fields (`pending_lifecycle_scripts: Vec`, + // `pub fn deinit` dropped. All owned fields (`pending_lifecycle_scripts: Vec`, // `completed_trees: Bitset`, `trees: Box<[TreeContext]>`, `tree_ids_to_trees_the_id_depends_on`, // `node_modules`, `trusted_dependencies_from_update_requests`) impl Drop. Borrowed fields // (`manager`, `lockfile`, etc.) are not freed. @@ -992,7 +985,7 @@ impl<'a> PackageInstaller<'a> { let new = Bitset::init_empty(self.lockfile().packages.len()).unwrap_or_oom(); let old = core::mem::replace(&mut self.successfully_installed, new); old.copy_into(&mut self.successfully_installed); - // PORT NOTE: `defer old.deinit(bun.default_allocator)` — Bitset impls Drop. + // `defer old.deinit(bun.default_allocator)` — Bitset impls Drop. } } @@ -1033,10 +1026,11 @@ impl<'a> PackageInstaller<'a> { if let Some(removed) = self.manager_mut().task_queue.fetch_remove(&task_id) { let callbacks = removed.value; - // PORT NOTE: `defer callbacks.deinit(this.manager.allocator)` — Vec drops. + // `defer callbacks.deinit(this.manager.allocator)` — Vec drops. - // TODO(port): defer save/restore of self.node_modules / self.current_tree_id. - // See install_available_packages for the same issue. + // Manual save/restore of self.node_modules / self.current_tree_id (Zig used + // `defer`; see install_available_packages). Infallible body — both exit + // paths below restore the saved values. let prev_node_modules = core::mem::take(&mut self.node_modules); let prev_tree_id = self.current_tree_id; @@ -1061,7 +1055,7 @@ impl<'a> PackageInstaller<'a> { let callback_package_id = self.lockfile().buffers.resolutions.as_slice()[context.dependency_id as usize]; self.node_modules.tree_id = context.tree_id; - // PORT NOTE: zig assigns `context.path` (ArrayList struct copy). + // zig assigns `context.path` (ArrayList struct copy). // `DependencyInstallContext.path: Vec` — clone since `cb` is `&`. self.node_modules.path.clone_from(&context.path); self.current_tree_id = context.tree_id; @@ -1116,7 +1110,7 @@ impl<'a> PackageInstaller<'a> { let mut temp = PackageScripts::default(); let mut temp_lockfile = Lockfile::default(); temp_lockfile.init_empty(); - // PORT NOTE: `defer temp_lockfile.deinit()` — Lockfile impls Drop. + // `defer temp_lockfile.deinit()` — Lockfile impls Drop. let mut string_builder = temp_lockfile.string_builder(); let log = self.manager().log_mut(); if let Err(err) = temp.fill_from_package_json(&mut string_builder, log, folder_path) { @@ -1142,14 +1136,14 @@ impl<'a> PackageInstaller<'a> { match resolution_tag { resolution::Tag::Git | resolution::Tag::Github | resolution::Tag::Root => { - // PORT NOTE: zig `inline for (Lockfile.Scripts.names) |hook| { @field(...) }`. + // zig `inline for (Lockfile.Scripts.names) |hook| { @field(...) }`. // The `FIELD_NAMES` table lists each script field accessor. for &(_, accessor) in PackageScripts::FIELD_NAMES.iter() { count += (!accessor(&scripts).is_empty()) as usize; } } _ => { - // PORT NOTE: zig `inline for (.{"preinstall","install","postinstall"})` over @field. + // zig `inline for (.{"preinstall","install","postinstall"})` over @field. count += (!scripts.preinstall.is_empty()) as usize; count += (!scripts.install.is_empty()) as usize; count += (!scripts.postinstall.is_empty()) as usize; @@ -1182,7 +1176,7 @@ impl<'a> PackageInstaller<'a> { pkg_name: String, resolution: &Resolution, ) { - // PORT NOTE: reshaped for borrowck — `string_bytes` is not mutated during install, + // reshaped for borrowck — `string_bytes` is not mutated during install, // so capture a raw slice once to avoid repeatedly re-borrowing `self.lockfile` // across `&mut self` method calls below (Zig accessed `lockfile.buffers.string_bytes` // freely inline). SAFETY: `buffers.string_bytes` is append-only and never freed @@ -1215,7 +1209,7 @@ impl<'a> PackageInstaller<'a> { return; } - // PORT NOTE: `PackageInstall` stores both `destination_dir_subpath: &mut ZStr` + // `PackageInstall` stores both `destination_dir_subpath: &mut ZStr` // and `destination_dir_subpath_buf: &mut [u8]` aliasing the same bytes (Zig // slices don't enforce noalias). Derive BOTH from a single `*mut PathBuffer` // so neither `&mut` invalidates the other under stacked-borrows. @@ -1307,7 +1301,7 @@ impl<'a> PackageInstaller<'a> { ); }; - // PORT NOTE: reshaped for borrowck — `PackageInstall` borrows several `self.*` + // reshaped for borrowck — `PackageInstall` borrows several `self.*` // fields while subsequent code also accesses `self.manager` / `self.node_modules` // / `self.lockfile` mutably (Zig aliased freely). Detach the borrows via a // `ParentRef` so `installer`'s lifetime is independent of `&mut self`. @@ -1326,7 +1320,7 @@ impl<'a> PackageInstaller<'a> { // field outlives `installer`. `destination_dir_subpath` above derives from the // same raw pointer, so this `&mut` does not invalidate it under stacked-borrows. destination_dir_subpath_buf: unsafe { (*subpath_buf_ptr).as_mut_slice() }, - // PORT NOTE: zig `arena: this.lockfile.allocator` dropped — global mimalloc. + // zig `arena: this.lockfile.allocator` dropped — global mimalloc. package_name: pkg_name, patch: patch_patch.map(|_| package_install::Patch { contents_hash: patch_contents_hash.unwrap(), @@ -1542,7 +1536,7 @@ impl<'a> PackageInstaller<'a> { } resolution::Tag::Github => { let url = self.manager_mut().alloc_github_url(resolution.github()); - // PORT NOTE: `defer this.manager.allocator.free(url)` — url: Vec drops. + // `defer this.manager.allocator.free(url)` — url: Vec drops. match package_manager::enqueue_tarball_for_download( self.manager_mut(), dependency_id, @@ -1823,7 +1817,7 @@ impl<'a> PackageInstaller<'a> { { let mut folder_path = AutoAbsPath::from(self.node_modules.path.as_slice()).unwrap_or_oom(); - // PORT NOTE: `defer folder_path.deinit()` — AbsPath impls Drop. + // `defer folder_path.deinit()` — AbsPath impls Drop. folder_path .append(alias.slice(string_buf!())) .unwrap_or_oom(); @@ -1972,7 +1966,7 @@ impl<'a> PackageInstaller<'a> { // - Access Denied because this specific package is unwritable // in the case of the former, the logs are extremely noisy, so we // will exit early, otherwise set a flag to not re-stat - // PORT NOTE: zig fn-local `const Singleton = struct { var node_modules_is_ok = false; }` + // zig fn-local `const Singleton = struct { var node_modules_is_ok = false; }` // — translated to a module-level static since Rust lacks fn-local mutable statics. static NODE_MODULES_IS_OK: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false); @@ -2091,7 +2085,7 @@ impl<'a> PackageInstaller<'a> { .unwrap_or_oom(); } - // PORT NOTE: reshaped for borrowck — `LazyPackageDestinationDir` borrows + // reshaped for borrowck — `LazyPackageDestinationDir` borrows // `&self.node_modules`, but this else-branch never reads `destination_dir` // (it only `close()`s it at the end, which is a no-op for `NodeModulesPath`). // Detach via raw ptr so subsequent `&mut self` calls type-check. @@ -2101,7 +2095,7 @@ impl<'a> PackageInstaller<'a> { root_node_modules_dir: self.root_node_modules_folder.fd(), }; - // PORT NOTE: `defer { destination_dir.close(); }` + `defer increment_tree_install_count`. + // `defer { destination_dir.close(); }` + `defer increment_tree_install_count`. // No early returns in this branch, so manual calls at end are equivalent. let dep = &self.lockfile().buffers.dependencies.as_slice()[dependency_id as usize]; @@ -2202,7 +2196,7 @@ impl<'a> PackageInstaller<'a> { } } - // PORT NOTE: `destination_dir` is `LazyPackageDestinationDir::NodeModulesPath` + // `destination_dir` is `LazyPackageDestinationDir::NodeModulesPath` // holding `&self.node_modules`. `increment_tree_install_count` takes // `&mut self` and (via `link_tree_bins`) reads `self.node_modules.path`, // which would alias the borrow held by `destination_dir`. Close it first diff --git a/src/install/PackageManager.rs b/src/install/PackageManager.rs index 15cfcdcc8365..e4cac9a21a56 100644 --- a/src/install/PackageManager.rs +++ b/src/install/PackageManager.rs @@ -24,7 +24,7 @@ use bun_paths::{DELIMITER, PathBuffer, SEP, SEP_STR}; use bun_semver as Semver; use bun_sys::{self, Fd}; use bun_threading::{ThreadPool, UnboundedQueue, thread_pool}; -use bun_transpiler::{self as transpiler, Transpiler}; +use bun_transpiler as transpiler; use bun_url::URL; /// Port of `bun.LazyBool` (bun.zig:2234) — caches the result of a getter the @@ -65,7 +65,6 @@ impl LazyBool bool> { // `bun_runtime::api::bun::process` and *reads* the same flag. use bun_spawn::process::WaiterThread; -// TODO(b0): RunCommand arrives from move-in (bun_runtime::cli::RunCommand → install). use crate::RunCommand; /// `Command::Context` shim — Zig's `Command.Context` (= `*ContextData`) lives in @@ -212,12 +211,6 @@ Learn more about these at https://bun.com/docs/cli/pm. } } -// `bun.resolver.DirInfo` — only stored as a raw pointer in -// `ScriptRunEnvironment.root_dir_info`. `bun_resolver` is a lower tier than -// `bun_install` (already a transitive dep via `bun_transpiler` → `bun_bundler`), -// so import the real type directly instead of an opaque ZST stub. -use bun_resolver::dir_info::DirInfo; - use crate::lockfile_real::package as Package; use crate::package_manager_task as Task; use crate::resolvers::folder_resolver::FolderResolution; @@ -362,9 +355,9 @@ pub struct PackageManager { // iterations — use `ast_arena` instead. The manager is a leaked singleton, so // this arena has process lifetime, matching the Zig allocator's semantics. pub ast_arena: bun_alloc::Arena, - // TODO(port): lifetime — LIFETIMES.tsv classifies this BORROW_PARAM → `&'a mut bun_ast::Log` - // (struct gets `<'a>`). Kept as raw ptr because PackageManager is a leaked singleton stored - // in a `static`; threading `<'a>` through the global holder is a TODO(refactor). + // Raw ptr rather than `&'a mut bun_ast::Log`: PackageManager is a leaked singleton + // stored in a `static`, which cannot carry a lifetime parameter. Invariant: the + // pointed-to Log must outlive every use of the singleton. pub log: *mut bun_ast::Log, pub resolve_tasks: ResolveTaskQueue, pub timestamp_for_manifest_cache_control: u32, @@ -377,7 +370,7 @@ pub struct PackageManager { pub env: Option>>, pub progress: Progress, pub downloads_node: Option<*mut ProgressNode>, // BORROW_FIELD — points into self.progress - pub scripts_node: Option>, // UNKNOWN — points to caller stack-local // TODO(port): lifetime + pub scripts_node: Option>, // points to a caller stack-local Progress node; only valid while that caller frame is live pub progress_name_buf: [u8; 768], pub progress_name_buf_dynamic: Vec, pub cpu_count: u32, @@ -439,8 +432,6 @@ pub struct PackageManager { pub node_gyp_tempdir_name: Box<[u8]>, - pub env_configure: Option, - pub lockfile: Box, // OWNED pub options: Options, @@ -644,11 +635,6 @@ pub enum TrackInstalledBin { Basename(Box<[u8]>), } -pub struct ScriptRunEnvironment { - pub root_dir_info: Option>, // UNKNOWN — struct appears unused // TODO(port): lifetime - pub transpiler: Transpiler<'static>, -} - // MOVE_DOWN: data struct + accessors live in `bun_install_types::WakeHandler` // (single definition the resolver also stores). The `handler` second arg is // erased to `*mut c_void` there because that crate cannot name @@ -806,11 +792,10 @@ mod holder { use super::PackageManager; use bun_dotenv as dot_env; // OWNED — global singleton, leaked. - // PORT NOTE: LIFETIMES.tsv prescribes `OnceLock>` for Holder.ptr, but + // LIFETIMES.tsv prescribes `OnceLock>` for Holder.ptr, but // Zig uses `var ptr: *PackageManager = undefined` then assigns via allocatePackageManager() // and later writes `manager.* = ...` in-place. OnceLock> can't express - // allocate-then-fill (no `&mut` after set). Keep a raw ptr for now. - // TODO(port): in-place init — reconcile with OnceLock>. + // allocate-then-fill (no `&mut` after set), so a raw ptr is used instead. // PORTING.md §Global mutable state: ptr written once on main thread, read // from worker threads → AtomicPtr (Release/Acquire pairs the publish). pub(super) static RAW_PTR: core::sync::atomic::AtomicPtr = @@ -823,8 +808,8 @@ mod holder { // so the pair is self-referential and cannot live in `OnceLock` (which only yields `&T`). // Mirrors Zig's `ctx.allocator.create(dot_env::Map)` / `create(dot_env::Loader)` — owned by // the singleton, never freed. Avoids `Box::leak` per PORTING.md §Forbidden. - // TODO(port): retype `dot_env::Loader.map` to `Box` so this becomes an owned field - // (`Box`) on `PackageManager` and these statics disappear. + // (These statics could go away if `dot_env::Loader.map` were retyped to an owned + // `Box`, making this an owned `Box` field on `PackageManager`.) // Write-once during single-threaded init; never read afterwards (kept only // to anchor the allocation). `AtomicCell<*mut T>` — payload is `Copy` and // pointer-sized, so `.store()` is a safe Release write (no `RacyCell` @@ -870,7 +855,7 @@ impl PackageManager { /// Zig: `pm.lockfile.loadFromCwd(pm, allocator, log, attempt_loading_from_other_lockfile)`. /// - /// PORT NOTE: reshaped for borrowck — the Zig call passes `pm` as a separate + /// Reshaped for borrowck — the Zig call passes `pm` as a separate /// argument while the receiver borrows `pm.lockfile`, which is a /// self-referential split borrow in Rust. Encapsulated here so callers stay /// in safe code: the returned `LoadResult` mutably borrows `self` for its @@ -924,8 +909,7 @@ impl PackageManager { ctx: Command::Context, log_level: package_manager_options::LogLevel, ) -> Result<&mut transpiler::Transpiler<'static>, Error> { - // TODO(port): narrow error set - // PORT NOTE: Zig `bun.once` caches the `Transpiler` value and returns it on + // Zig `bun.once` caches the `Transpiler` value and returns it on // subsequent calls. `Transpiler` is non-`Copy` (and self-referential via // `linker.options = &options`), so cache by pointer in a process-static. // SAFETY: `PackageManager` is a leaked singleton; main-thread-only call site. @@ -960,7 +944,7 @@ impl PackageManager { #[inline] pub fn is_continuous_integration(&mut self) -> bool { - // PORT NOTE: Zig `LazyBool.get` recovers `*PackageManager` via + // Zig `LazyBool.get` recovers `*PackageManager` via // `@fieldParentPtr("ci_mode", self)`. Rust has no field-parent-pointer, // so pass the parent explicitly. `ci_mode.value` is a `Cell` so a // shared `&self` projection suffices — both the receiver `&self.ci_mode` @@ -1043,7 +1027,7 @@ impl PackageManager { is_done_fn: fn(&mut C) -> bool, ) { Output::flush(); - // PORT NOTE: Zig `sleepUntil(closure: anytype, isDoneFn)` passes a `*Closure` and + // Zig `sleepUntil(closure: anytype, isDoneFn)` passes a `*Closure` and // a fn-pointer that mutates it. `AnyEventLoop::tick_raw` takes the type-erased // `(*mut c_void, fn(*mut c_void) -> bool)`; trampoline through a small wrapper so // `is_done_fn` receives `&mut C` and can drive `run_tasks` / record `err`. @@ -1086,8 +1070,7 @@ impl PackageManager { } pub fn ensure_temp_node_gyp_script(&mut self) -> Result<(), Error> { - // TODO(port): narrow error set - // PORT NOTE: Zig `bun.once` caches the `()` result. The body itself is + // Zig `bun.once` caches the `()` result. The body itself is // already idempotent (early-returns when `node_gyp_tempdir_name` is // non-empty), so a simple `AtomicBool` ran-flag matches semantics. // NB: not `bun_core::run_once!` — body is fallible and the contract is @@ -1144,7 +1127,7 @@ fn configure_env_for_scripts_run( // to do that, we re-use the code from bun run // this is expensive, it traverses the entire directory tree going up to the root // so we really only want to do it when strictly necessary - // PORT NOTE: `var this_transpiler: Transpiler = undefined` — Zig leaves it uninit and + // `var this_transpiler: Transpiler = undefined` — Zig leaves it uninit and // RunCommand.configureEnvForRun fully initializes via out-param. Transpiler is NOT // all-zero-valid POD, so `zeroed()` is wrong; use MaybeUninit and assume_init after fill. let mut this_transpiler_slot = @@ -1473,7 +1456,6 @@ pub fn init( cli: CommandLineArguments, subcommand: Subcommand, ) -> Result<(&'static mut PackageManager, Box<[u8]>), Error> { - // TODO(port): narrow error set if cli.global { // Zig: `if (ctx.install) |opts|` — non-consuming peek. `ctx.install` is // `Option>` borrowed via `&mut ContextData`; reborrow with @@ -1659,7 +1641,7 @@ pub fn init( original_package_json_path = ZStr::from_buf(&original_package_json_path_buf[..], new_path_len); let child_cwd = &original_package_json_path.as_bytes()[..this_cwd.len()]; - // PORT NOTE: reshaped — Zig uses withoutSuffixComptime(.., sep_str ++ "package.json") + // Reshaped — Zig uses withoutSuffixComptime(.., sep_str ++ "package.json") // Check if this is a workspace; if so, use root package if subcommand.should_chdir_to_root() { @@ -2005,7 +1987,7 @@ pub fn init( wr!(log, ctx.log); wr!(root_dir, entries_option); wr!(ast_arena, bun_alloc::Arena::new()); - // PORT NOTE: reborrow `&mut *env` so the local stays usable for + // reborrow `&mut *env` so the local stays usable for // the post-construction `BUN_MANIFEST_CACHE` / `options.load` // reads (Zig PackageManager.zig:910 keeps using `env` after // storing it in the struct). `BackRef` stores a raw pointer — @@ -2077,7 +2059,6 @@ pub fn init( wr!(total_scripts, 0); wr!(root_lifecycle_scripts, None); wr!(node_gyp_tempdir_name, Box::default()); - wr!(env_configure, None); wr!(preinstall_state, Vec::new()); wr!(postinstall_optimizer, Default::default()); wr!(global_link_dir, None); @@ -2122,7 +2103,7 @@ pub fn init( let uws_loop = unsafe { &mut *uws_loop }; EventLoopHandle::from_any(&mut manager.event_loop).set_as_parent_of(uws_loop); } - // PORT NOTE: Zig `manager.lockfile = try ctx.allocator.create(Lockfile)` — + // Zig `manager.lockfile = try ctx.allocator.create(Lockfile)` — // folded into the struct literal above (`Box::new(Lockfile::default())`) so // we never construct a zeroed `Lockfile` only to drop it. @@ -2458,7 +2439,7 @@ pub(crate) fn init_with_runtime_once( wr!(log, std::ptr::from_mut(log)); wr!(root_dir, root_dir); wr!(ast_arena, bun_alloc::Arena::new()); - // PORT NOTE: reborrow `&mut *env` so the local stays usable for + // reborrow `&mut *env` so the local stays usable for // the post-construction `BUN_MANIFEST_CACHE` / `options.load` // reads (Zig PackageManager.zig:1072 keeps using `env` after // storing it in the struct). `BackRef` stores a raw pointer — @@ -2531,7 +2512,6 @@ pub(crate) fn init_with_runtime_once( wr!(total_scripts, 0); wr!(root_lifecycle_scripts, None); wr!(node_gyp_tempdir_name, Box::default()); - wr!(env_configure, None); wr!(preinstall_state, Vec::new()); wr!(postinstall_optimizer, Default::default()); wr!(global_link_dir, None); @@ -2564,7 +2544,7 @@ pub(crate) fn init_with_runtime_once( // the `&mut PackageManager` validity invariant now holds for the post-init // body (Zig PackageManager.zig:1031 onward). let manager = unsafe { &mut *manager_ptr }; - // PORT NOTE: Zig `manager.lockfile = try allocator.create(Lockfile)` — + // Zig `manager.lockfile = try allocator.create(Lockfile)` — // folded into the struct literal above (`Box::new(Lockfile::default())`). if Output::enable_ansi_colors_stderr() { @@ -2621,11 +2601,11 @@ pub(crate) fn init_with_runtime_once( // `manager.lockfile = load.lockfile` self-assignment is a no-op in the Rust // shape (`load_from_cwd` mutates `*manager.lockfile` in place and returns a // borrow of it), so `Ok` keeps the loaded value as-is. - // PORT NOTE: Zig calls `manager.lockfile.loadFromCwd(manager, …)` — a + // Zig calls `manager.lockfile.loadFromCwd(manager, …)` — a // self-aliasing receiver+argument pair Rust forbids. Split-borrow by // temporarily moving the boxed lockfile out so the `&mut PackageManager` // passed in does not alias the `&mut Lockfile` receiver. - // PORT NOTE: `root_dir` was moved into `*manager` above (the field is + // `root_dir` was moved into `*manager` above (the field is // an unbounded `&mut DirEntry`, so the local reborrow is for `'static` and the // original binding is dead). Read it back through `manager.root_dir`. if manager.root_dir.has_comptime_query(b"bun.lockb") { diff --git a/src/install/PackageManager/CommandLineArguments.rs b/src/install/PackageManager/CommandLineArguments.rs index 7bfdaac38c70..adce0c247e18 100644 --- a/src/install/PackageManager/CommandLineArguments.rs +++ b/src/install/PackageManager/CommandLineArguments.rs @@ -17,8 +17,6 @@ use bun_core::strings; use bun_core::{Global, Output}; use bun_install::npm as Npm; use bun_paths::{self as Path, PathBuffer}; -// TODO(port): PackageManagerCommand arrives from move-in -// (bun_runtime::cli::package_manager_command::PackageManagerCommand → install::PackageManager::CommandLineArguments). use crate::package_manager_real::PackageManagerCommand; use std::sync::OnceLock; @@ -44,7 +42,7 @@ type ParamType = clap::Param; // (`INSTALL_PARAMS`, …) are baked into rodata with zero runtime init. use bun_clap::concat_params; -// PORT NOTE: Zig builds the `--backend` param spec via comptime string `++` against +// Zig builds the `--backend` param spec via comptime string `++` against // `platform_specific_backend_label`. `clap::param!` is a proc-macro that requires a // *literal* token (it parses the spec at compile time), so `const_format::concatcp!` // can't feed it. Instead we cfg-select the fully-expanded literal per platform — @@ -354,8 +352,8 @@ static WHY_PARAMS: &[ParamType] = concat_params![ // NOTE: `string` (= `[]const u8`) fields here are slices into process argv (owned by `clap::Args` // which itself lives for the program duration). They are never freed. Mapped to `&'static [u8]` -// per PORTING.md (no `deinit`, never `allocator.free`d). TODO(refactor): thread an explicit -// lifetime if `clap::Args` ever becomes scoped. +// per PORTING.md (no `deinit`, never `allocator.free`d). An explicit lifetime would only +// become necessary if `clap::Args` ever becomes scoped. // // `Clone` mirrors Zig value-copy semantics — `updatePackageJSONAndInstall` // passes `cli` by value into `PackageManager.init` while retaining its own @@ -974,7 +972,6 @@ Full documentation is available at https://bun.com/docs/cli/pm#scan. } } - // TODO(port): narrow error set pub fn parse(subcommand: Subcommand) -> Result { // PERF(port): was comptime monomorphization on `subcommand` — profile if hot Output::set_is_verbose(Output::is_verbose()); @@ -1003,7 +1000,7 @@ Full documentation is available at https://bun.com/docs/cli/pm#scan. let mut diag = clap::Diagnostic::default(); - // PORT NOTE: Zig kept `args` (and its arena) alive for the program duration — + // Zig kept `args` (and its arena) alive for the program duration — // `cli` stores slices into it. Park the parsed `Args` in a process-global // `OnceLock` so outer slice borrows (`positionals()`, `options()`) are // `'static`; inner `&[u8]` are argv-backed and already `'static`. CLI args diff --git a/src/install/PackageManager/PackageJSONEditor.rs b/src/install/PackageManager/PackageJSONEditor.rs index e442503f5215..c778d27a892f 100644 --- a/src/install/PackageManager/PackageJSONEditor.rs +++ b/src/install/PackageManager/PackageJSONEditor.rs @@ -254,7 +254,7 @@ pub(crate) fn edit_update_no_args( // Zig: `const allocator = manager.allocator;` — process-lifetime arena for AST // nodes that must outlive `Expr.Data.Store.reset()`. See `PackageManager.ast_arena`. - // PORT NOTE: reshaped for borrowck — `arena` is a disjoint-field borrow held across + // `arena` is a disjoint-field borrow held across // the `&mut manager.updating_packages` accesses below. let arena = &manager.ast_arena; @@ -318,7 +318,7 @@ pub(crate) fn edit_update_no_args( } let key_str = key.as_utf8_string_literal().expect("unreachable"); - // PORT NOTE: reshaped for borrowck — capture the literal as an owned + // Capture the literal as an owned // copy before borrowing `manager.updating_packages` mutably. let version_literal_owned = Box::<[u8]>::from(version_literal); let entry = manager.updating_packages.get_or_put(key_str)?; @@ -548,7 +548,7 @@ pub(crate) fn edit( // Zig: `const allocator = manager.allocator;` — process-lifetime arena for AST // nodes that must outlive `Expr.Data.Store.reset()`. See `PackageManager.ast_arena`. - // PORT NOTE: reshaped for borrowck — `arena` is a disjoint-field borrow held across + // `arena` is a disjoint-field borrow held across // the `&mut manager.{updating_packages,trusted_deps_to_add_to_package_json}` accesses below. let arena = &manager.ast_arena; @@ -573,7 +573,6 @@ pub(crate) fn edit( for item in arr.items.slice() { if let bun_ast::ExprData::EString(s) = &item.data { if s.eql_bytes(trusted_package_name) { - // PORT NOTE: reshaped for borrowck — drop return value (was allocator.free) let _ = manager.trusted_deps_to_add_to_package_json.swap_remove(i); break; @@ -619,7 +618,7 @@ pub(crate) fn edit( break 'add_packages_to_update; } - // PORT NOTE: reshaped for borrowck — capture an + // Capture an // owned copy of the literal before borrowing // `manager.updating_packages` mutably. let version_literal_owned = @@ -632,7 +631,7 @@ pub(crate) fn edit( break 'add_packages_to_update; } - // PORT NOTE: Zig leaves `entry.value_ptr.*` + // The Zig original leaves `entry.value_ptr.*` // undefined across the `npm:`-alias bailout // below (Zig:435), which is later read by // `fetchSwapRemove` — UB. `get_or_put` here diff --git a/src/install/PackageManager/PackageManagerDirectories.rs b/src/install/PackageManager/PackageManagerDirectories.rs index 48ab3bcd6685..7fdaddad6006 100644 --- a/src/install/PackageManager/PackageManagerDirectories.rs +++ b/src/install/PackageManager/PackageManagerDirectories.rs @@ -181,7 +181,7 @@ pub fn get_cache_directory_and_abs_path(this: &mut PackageManager) -> (Fd, AbsPa #[inline] pub fn get_temporary_directory(this: &mut PackageManager) -> &'static TemporaryDirectory { - // PORT NOTE: Zig used `bun.once(...)`; `bun_core::OnceT>` can't + // Zig used `bun.once(...)`; `bun_core::OnceT>` can't // accept a non-`'static` `&mut PackageManager` argument, so use `OnceLock` // directly and split get/set so the closure doesn't need to capture `this`. if let Some(td) = GET_TEMPORARY_DIRECTORY_ONCE.get() { @@ -247,7 +247,6 @@ fn get_temporary_directory_run(manager: &mut PackageManager) -> TemporaryDirecto let tmpname = FileSystem::tmpname(b"hm", &mut tmpbuf, bun_core::fast_random()).expect("unreachable"); - // TODO(port): std.time.Timer — using bun_core::time::Timer placeholder let mut timer = if manager.options.log_level != LogLevel::Silent { Some(bun_core::time::Timer::start().expect("unreachable")) } else { @@ -707,7 +706,7 @@ pub fn cached_npm_package_folder_name_print<'a>( cached_npm_package_folder_print_basename(buf, name, version, None, include_version_number) .as_bytes() .len(); - // PORT NOTE: reshaped for borrowck — resume the cursor at the basename's + // reshaped for borrowck — resume the cursor at the basename's // tail instead of holding the returned `&ZStr` across the re-borrow. let scope_url = scope.url.url(); let mut w = ByteCursor { @@ -914,7 +913,6 @@ pub fn path_for_cached_npm_path<'a>( package_name: &[u8], version: Semver::Version, ) -> Result<&'a mut [u8], Error> { - // TODO(port): narrow error set let mut cache_path_buf = PathBuffer::uninit(); let cache_path = cached_npm_package_folder_name_print( @@ -925,7 +923,7 @@ pub fn path_for_cached_npm_path<'a>( None, ); let cache_path_len = cache_path.as_bytes().len(); - // PORT NOTE: reshaped for borrowck — drop borrow before mutating buffer + // reshaped for borrowck — drop borrow before mutating buffer if cfg!(debug_assertions) { debug_assert!(cache_path_buf[package_name.len()] == b'@'); @@ -975,13 +973,12 @@ pub fn path_for_resolution<'a>( resolution: &Resolution, buf: &'a mut PathBuffer, ) -> Result<&'a mut [u8], Error> { - // TODO(port): narrow error set // const folder_name = this.cachedNPMPackageFolderName(name, version); match resolution.tag { ResolutionTag::Npm => { let npm = *resolution.npm(); let package_name_ = this.lockfile.packages.items_name()[package_id as usize]; - // PORT NOTE: borrowck — `path_for_cached_npm_path` reborrows `this` + // borrowck — `path_for_cached_npm_path` reborrows `this` // mutably (for `get_cache_directory`), so the `&this.lockfile` // borrow can't be held across it. Copy the name out first. let package_name = this.lockfile.str(&package_name_).to_vec(); @@ -1069,7 +1066,7 @@ pub fn compute_cache_dir_and_subpath<'a>( ResolutionTag::Symlink => { let directory = global_link_dir(manager); - // PORT NOTE: borrowck — `global_link_dir_path` below reborrows + // borrowck — `global_link_dir_path` below reborrows // `manager` mutably, so copy the symlink target out of the lockfile // string buffer first instead of holding a slice across that call. let folder = resolution @@ -1110,7 +1107,6 @@ pub fn compute_cache_dir_and_subpath<'a>( // ─────────────────────────── package.json / lockfile ────────────────────────── pub fn attempt_to_create_package_json_and_open() -> Result { - // TODO(port): narrow error set let package_json_file = match Dir::cwd().create_file_z( z_static(b"package.json\0"), sys::CreateFlags { @@ -1131,7 +1127,6 @@ pub fn attempt_to_create_package_json_and_open() -> Result { } pub fn attempt_to_create_package_json() -> Result<(), Error> { - // TODO(port): narrow error set let file = attempt_to_create_package_json_and_open()?; let _ = file.close(); // close error is non-actionable (Zig parity: discarded) Ok(()) @@ -1142,7 +1137,7 @@ pub fn save_lockfile( load_result: &LoadResult, save_format: LockfileFormat, had_any_diffs: bool, - // TODO(dylan-conway): this and `packages_len_before_install` can most likely be deleted + // NOTE(dylan-conway): this and `packages_len_before_install` can most likely be deleted // now that git dependnecies don't append to lockfile during installation. lockfile_before_install: &Lockfile, packages_len_before_install: usize, @@ -1199,7 +1194,7 @@ pub fn save_lockfile( return Ok(()); } - // PORT NOTE: Zig held `*Progress.Node` across the body; `Progress::start` + // Zig held `*Progress.Node` across the body; `Progress::start` // returns `&mut Node` borrowing `this.progress`, which would conflict with // the `&mut this` reborrows below. Stash as a raw pointer (mirrors Zig's // non-exclusive `*Node`; the node lives inside `this.progress.root`). @@ -1263,12 +1258,11 @@ pub fn save_lockfile( pub fn update_lockfile_if_needed( manager: &mut PackageManager, - // PORT NOTE: Zig passed `Lockfile.LoadResult` by value (large structs are + // Zig passed `Lockfile.LoadResult` by value (large structs are // passed by const-ref under Zig's ABI). The Rust caller continues using // `load_result` after this call, so take it by shared reference. load_result: &LoadResult, ) -> Result<(), Error> { - // TODO(port): narrow error set if let LoadResult::Ok(ok) = load_result { if ok.serializer_result.packages_need_update { let mut slice = manager.lockfile.packages.slice(); @@ -1283,11 +1277,10 @@ pub fn update_lockfile_if_needed( } pub fn write_yarn_lock(this: &mut PackageManager) -> Result<(), Error> { - // TODO(port): narrow error set let mut tmpname_buf = [0u8; 512]; tmpname_buf[0..8].copy_from_slice(b"tmplock-"); - // PORT NOTE: `FileSystem.RealFS.Tmpfile` (fs.zig) — POSIX path never used + // `FileSystem.RealFS.Tmpfile` (fs.zig) — POSIX path never used // its `*RealFS` arg; Windows opens via `get_default_temp_dir`. let mut tmpfile = bun_resolver::fs::RealFsTmpfile::default(); let mut secret = [0u8; 32]; @@ -1323,12 +1316,12 @@ pub fn write_yarn_lock(this: &mut PackageManager) -> Result<(), Error> { lockfile: &this.lockfile, options: &this.options, successfully_installed: None, - // PORT NOTE: Zig leaves `.updates` at its default `&.{}`. `Yarn::print` + // Zig leaves `.updates` at its default `&.{}`. `Yarn::print` // never reads `updates`, but pass an empty slice to match the spec // exactly rather than `&this.update_requests`. updates: &[], }; - // PORT NOTE: Zig used `file.writerStreaming(&[4096]u8)`. `bun_sys::File` + // Zig used `file.writerStreaming(&[4096]u8)`. `bun_sys::File` // has no `bun_io::Write` impl (and `bun_sys` ⊥ `bun_io`), so buffer the // entire output in a `Vec` (impls `bun_io::Write`) and flush once. let mut buf: Vec = Vec::with_capacity(4096); diff --git a/src/install/PackageManager/PackageManagerEnqueue.rs b/src/install/PackageManager/PackageManagerEnqueue.rs index 4c30e0a030f1..8a474892d4d6 100644 --- a/src/install/PackageManager/PackageManagerEnqueue.rs +++ b/src/install/PackageManager/PackageManagerEnqueue.rs @@ -38,7 +38,7 @@ use bun_install::{ invalid_package_id, }; -// PORT NOTE: Zig accesses `PackageManager.verbose_install` (a `pub var`); the +// Zig accesses `PackageManager.verbose_install` (a `pub var`); the // Rust port stores it as a process-global. The associated fn lives on the real // `PackageManager` impl; pull it into scope as a free name so the comptime-ish // `verbose_install()` call sites read the same. @@ -48,7 +48,7 @@ fn verbose_install() -> bool { PackageManager::verbose_install() } -// PORT NOTE: `PatchTask.callback` discriminant — routed to the real +// `PatchTask.callback` discriminant — routed to the real // `patch_install::Callback` enum (CalcHash / Apply). // `SuccessFn` / `FailFn` are bare `fn(&mut PackageManager, ...)` pointers; the @@ -109,7 +109,7 @@ pub fn enqueue_dependency_list( // through "dependencies", skip it if end - begin > 1 && lockfile.buffers.dependencies[0].behavior.is_peer() { let mut peer_i: usize = 0; - // PORT NOTE: reshaped for borrowck — index into the slice instead of holding &mut across loop + // reshaped for borrowck — index into the slice instead of holding &mut across loop while lockfile.buffers.dependencies[peer_i].behavior.is_peer() { let mut dep_i: usize = (end - 1) as usize; let mut dep = lockfile.buffers.dependencies[dep_i].clone(); @@ -143,7 +143,7 @@ pub fn enqueue_dependency_list( dependency::version::Tag::Folder => bun_fmt::PathSep::Auto, _ => bun_fmt::PathSep::Any, }; - // PORT NOTE: `format_args!` borrows temporaries — bind the + // `format_args!` borrows temporaries — bind the // formatter first so it outlives the macro expansion. let realname = dependency.realname(); let path_fmt = bun_fmt::fmt_path_u8( @@ -153,7 +153,6 @@ pub fn enqueue_dependency_list( escape_backslashes: false, }, ); - // TODO(port): logger note API — Zig passes (fmt, args) tuple separately let log = this.log_mut(); if dependency.behavior.is_optional() || dependency.behavior.is_peer() { log.add_warning_with_note( @@ -212,7 +211,7 @@ pub fn enqueue_tarball_for_download( patch_name_and_version_hash, crate::network_task::Authorization::NoAuthorization, )? { - // PORT NOTE: reshaped for borrowck — `task: &mut NetworkTask` borrows + // reshaped for borrowck — `task: &mut NetworkTask` borrows // `*this` (pool slot); reborrow as raw so `this.network_tarball_batch` // is reachable. let task: *mut NetworkTask = task; @@ -233,7 +232,7 @@ pub fn enqueue_tarball_for_reading( resolution: &Resolution, task_context: TaskCallbackContext, ) { - // PORT NOTE: reshaped for borrowck — `path` borrows + // reshaped for borrowck — `path` borrows // `this.lockfile.buffers.string_bytes`; detach the slice lifetime so the // `&mut PackageManager` reborrow for `enqueue_local_tarball` below does // not conflict (Zig passes the aliased `*PackageManager` freely). @@ -281,7 +280,7 @@ pub fn enqueue_git_for_checkout( // active union field. Copy out so the value no longer borrows // `*resolution` while `*this` is mutably reborrowed below. let repository: Repository = *resolution.git(); - // PORT NOTE: reshaped for borrowck — `url`/`resolved` borrow + // reshaped for borrowck — `url`/`resolved` borrow // `this.lockfile.buffers.string_bytes`; detach the slice lifetimes so the // `&mut PackageManager` reborrows for the enqueue callees below do not // conflict (Zig passes the aliased `*PackageManager` freely). @@ -419,7 +418,7 @@ pub fn enqueue_package_for_download( patch_name_and_version_hash, crate::network_task::Authorization::AllowAuthorization, )? { - // PORT NOTE: reshaped for borrowck — see `enqueue_tarball_for_download`. + // reshaped for borrowck — see `enqueue_tarball_for_download`. let task: *mut NetworkTask = task; // SAFETY: `task` is the unique handle to a freshly-vended pool slot. unsafe { (*task).schedule(&mut this.network_tarball_batch) }; @@ -516,7 +515,7 @@ pub fn enqueue_dependency_to_root( // in Rust we just declare the closure struct directly. struct Closure { err: Option, - // PORT NOTE: raw `*mut` (Zig `*PackageManager`) — `sleep_until` + // raw `*mut` (Zig `*PackageManager`) — `sleep_until` // also receives this pointer, so `&mut` here would alias. manager: *mut PackageManager, } @@ -674,7 +673,6 @@ pub fn enqueue_dependency_with_main_and_success_fn( // an explicit flag instead. is_root: bool, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set if dependency.behavior.is_optional_peer() { return Ok(()); } @@ -1000,7 +998,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( ); } } else if version.tag.is_npm() { - // PORT NOTE: reshaped for borrowck — `name_str` borrows + // reshaped for borrowck — `name_str` borrows // `this.lockfile.buffers.string_bytes`. Route the whole // branch through a raw root so the slice and the // `&mut PackageManager` calls below can coexist (Zig @@ -1110,7 +1108,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( return Ok(()); } } - // PORT NOTE: reshaped for borrowck — `find_result` + // reshaped for borrowck — `find_result` // borrows `loaded_manifest`; route the manifest // through a `BackRef` so the `&mut PackageManager` // call below doesn't conflict. `loaded_manifest` @@ -1219,7 +1217,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( return Ok(()); } - // PORT NOTE: reshaped for borrowck — `alias`/`url` borrow + // reshaped for borrowck — `alias`/`url` borrow // `this.lockfile.buffers.string_bytes`; detach the slice // lifetimes so the `&mut PackageManager` reborrows for the // enqueue callees below do not conflict. @@ -1350,7 +1348,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( } else { TaskCallbackContext::Dependency(id) }; - // PORT NOTE: reshaped for borrowck — `entry` mutably borrows + // reshaped for borrowck — `entry` mutably borrows // `this.task_queue`; scope it tightly so the calls below can // reborrow `*this`. { @@ -1386,14 +1384,14 @@ pub fn enqueue_dependency_with_main_and_success_fn( None, crate::network_task::Authorization::NoAuthorization, )? { - // PORT NOTE: reshaped for borrowck — see `enqueue_tarball_for_download`. + // reshaped for borrowck — see `enqueue_tarball_for_download`. let nt: *mut NetworkTask = network_task; enqueue_network_task(this, nt); } Ok(()) } dependency::version::Tag::Symlink | dependency::version::Tag::Workspace => { - // PORT NOTE: Zig used `inline .symlink, .workspace => |dependency_tag|` to capture + // Zig used `inline .symlink, .workspace => |dependency_tag|` to capture // the comptime tag; we check `version.tag` at runtime instead. let dependency_tag = version.tag; @@ -1414,26 +1412,6 @@ pub fn enqueue_dependency_with_main_and_success_fn( Err(err) => return Err(err), }; - const WORKSPACE_NOT_FOUND_FMT: &str = concat!( - "Workspace dependency \"{name}\" not found\n", - "\n", - "Searched in {search_path}\n", - "\n", - "Workspace documentation: https://bun.com/docs/install/workspaces\n", - "\n", - ); - const LINK_NOT_FOUND_FMT: &str = concat!( - "Package \"{name}\" is not linked\n", - "\n", - "To install a linked package:\n", - " bun link my-pkg-name-from-package-json\n", - "\n", - "Tip: the package name is from package.json, which can differ from the folder name.\n", - "\n", - ); - // TODO(port): named-argument format strings — Zig used `{[name]s}` / `{[search_path]f}`; - // logger API in Rust may need positional args instead. - if let Some(result) = _result { // First time? if result.is_first_time { @@ -1483,7 +1461,6 @@ pub fn enqueue_dependency_with_main_and_success_fn( None, bun_ast::Loc::EMPTY, format_args!( - // TODO(port): WORKSPACE_NOT_FOUND_FMT with named args "Workspace dependency \"{}\" not found\n\nSearched in {}\n\nWorkspace documentation: https://bun.com/docs/install/workspaces\n\n", bstr::BStr::new(this.lockfile.str(&name)), PackageWorkspaceSearchPathFormatter { manager: this, version, quoted: true }, @@ -1495,7 +1472,6 @@ pub fn enqueue_dependency_with_main_and_success_fn( None, bun_ast::Loc::EMPTY, format_args!( - // TODO(port): LINK_NOT_FOUND_FMT with named args "Package \"{}\" is not linked\n\nTo install a linked package:\n bun link my-pkg-name-from-package-json\n\nTip: the package name is from package.json, which can differ from the folder name.\n\n", bstr::BStr::new(this.lockfile.str(&name)), ), @@ -1525,7 +1501,6 @@ pub fn enqueue_dependency_with_main_and_success_fn( ); } } - let _ = (WORKSPACE_NOT_FOUND_FMT, LINK_NOT_FOUND_FMT); Ok(()) } dependency::version::Tag::Tarball => { @@ -1545,7 +1520,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( return Ok(()); } - // PORT NOTE: reshaped for borrowck — `url` borrows `string_bytes`; + // reshaped for borrowck — `url` borrows `string_bytes`; // detach the slice lifetime so the `&mut PackageManager` reborrows // for the enqueue callees below do not conflict. // SAFETY: the enqueue callees copy `url` into the filename store @@ -1575,7 +1550,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( } else { TaskCallbackContext::Dependency(id) }; - // PORT NOTE: reshaped for borrowck — scope `entry` tightly. + // reshaped for borrowck — scope `entry` tightly. { let entry = this .task_queue @@ -1616,7 +1591,7 @@ pub fn enqueue_dependency_with_main_and_success_fn( this.task_batch.push(ThreadPool::Batch::from(task)); } dependency::tarball::Uri::Remote(_) => { - // PORT NOTE: `generate_network_task_for_tarball` returns + // `generate_network_task_for_tarball` returns // `&'a mut NetworkTask` tied to `this`; coerce to `*mut` // immediately so the `&mut *this` borrow ends before // `enqueue_network_task(this, …)` reborrows it (NLL). @@ -2036,8 +2011,7 @@ fn get_or_put_resolved_package_with_find_result( // PERF(port): was comptime monomorphization — profile in Phase B success_fn: SuccessFn, ) -> Result, bun_core::Error> { - // TODO(port): narrow error set - // PORT NOTE: reshaped for borrowck — `is_root_dependency(&self, &mut PackageManager, …)` + // reshaped for borrowck — `is_root_dependency(&self, &mut PackageManager, …)` // borrows `this.lockfile` and `this` at once. Split via raw root. let should_update = { let this_ptr: *mut PackageManager = this; @@ -2057,7 +2031,7 @@ fn get_or_put_resolved_package_with_find_result( // Was this package already allocated? Let's reuse the existing one. // - // PORT NOTE (determinism): Zig passes `version` here unconditionally, so a + // Determinism: Zig passes `version` here unconditionally, so a // peer like `>= 1.0.2` can collapse onto whichever sibling-appended entry // (e.g. `1.0.9`) happens to be highest in the index *at this instant* — a // network-order artefact that the `^1.0.2` peer-hoisting test already @@ -2097,7 +2071,7 @@ fn get_or_put_resolved_package_with_find_result( } // appendPackage sets the PackageID on the package - // PORT NOTE: reshaped for borrowck — `from_npm` takes both `&mut PackageManager` + // reshaped for borrowck — `from_npm` takes both `&mut PackageManager` // and `&mut Lockfile`, which alias through `this.lockfile`. Split via raw root // (Zig passes both freely). let this_ptr: *mut PackageManager = this; @@ -2125,7 +2099,7 @@ fn get_or_put_resolved_package_with_find_result( // by-value above). unsafe { &mut *(*this_ptr).lockfile }.mark_exact_pin(package.meta.id); } - // PORT NOTE: Zig used `defer successFn(...)`. Use scopeguard so success_fn runs on every + // Zig used `defer successFn(...)`. Use scopeguard so success_fn runs on every // return below (including the `?` paths). The guard owns the raw pointer so the // `this` reborrow below doesn't conflict with the closure capture. let mut guard = scopeguard::guard((this_ptr, package.meta.id), |(this_ptr, pkg_id)| { @@ -2135,7 +2109,7 @@ fn get_or_put_resolved_package_with_find_result( }); // SAFETY: see above — sole live `&mut PackageManager` until scope exit. let this: &mut PackageManager = unsafe { &mut *guard.0 }; - // PORT NOTE: Zig `defer` (not errdefer) — scopeguard runs on ALL exits, never disarmed. + // Zig `defer` (not errdefer) — scopeguard runs on ALL exits, never disarmed. // non-null if the package is in "patchedDependencies" let mut name_and_version_hash: Option = None; @@ -2241,7 +2215,6 @@ fn get_or_put_resolved_package( // PERF(port): was comptime monomorphization — profile in Phase B success_fn: SuccessFn, ) -> Result, bun_core::Error> { - // TODO(port): narrow error set if install_peer && behavior.is_peer() { if let Some(index) = this.lockfile.package_index.get(&name_hash) { let resolutions = this.lockfile.packages.items_resolution(); @@ -2409,7 +2382,7 @@ fn get_or_put_resolved_package( } // Resolve the version from the loaded NPM manifest - // PORT NOTE: reshaped for borrowck — `name_str`/`manifest` borrow + // reshaped for borrowck — `name_str`/`manifest` borrow // `*this`; route through a raw root so the `&mut PackageManager` // calls below can coexist (Zig passes the aliased `*PackageManager`). // Snapshot the disk-fallback scalars *before* establishing @@ -2573,7 +2546,7 @@ fn get_or_put_resolved_package( } }; - // PORT NOTE: reshaped for borrowck — `manifest`/`find_result` + // reshaped for borrowck — `manifest`/`find_result` // borrow `this.manifests`; detach via `BackRef` so the `&mut *this` // call can proceed (`this.manifests` is not mutated by the callee). let manifest_ref: bun_ptr::BackRef = @@ -2599,7 +2572,7 @@ fn get_or_put_resolved_package( let res: FolderResolutionValue = 'res: { if this.lockfile.is_workspace_dependency(dependency_id) { // relative to cwd - // PORT NOTE: reshaped for borrowck — `folder_path` borrows + // reshaped for borrowck — `folder_path` borrows // `string_bytes`; detach the slice lifetime so the // `&mut PackageManager` reborrow for `get_or_put` below // does not conflict. @@ -2645,7 +2618,7 @@ fn get_or_put_resolved_package( { // only need name and path - // PORT NOTE: copy the two slices out of `string_bytes` + // copy the two slices out of `string_bytes` // before creating the builder — `StringBuilder::allocate` // may grow the buffer and invalidate borrows into it, so // owned copies are required regardless of borrowck. @@ -2705,7 +2678,7 @@ fn get_or_put_resolved_package( .get(&name_hash) .copied() .unwrap_or_else(|| *version.workspace()); - // PORT NOTE: reshaped for borrowck — `workspace_path` may borrow + // reshaped for borrowck — `workspace_path` may borrow // `string_bytes`; detach the slice lifetime so the // `&mut PackageManager` reborrow for `get_or_put` below does not // conflict. @@ -2750,7 +2723,7 @@ fn get_or_put_resolved_package( } } dependency::version::Tag::Symlink => { - // PORT NOTE: reshaped for borrowck — `link_dir` / `symlink_path` + // reshaped for borrowck — `link_dir` / `symlink_path` // borrow into `*this`; detach their lifetimes so the // `&mut PackageManager` reborrow for `get_or_put` does not // conflict. diff --git a/src/install/PackageManager/PackageManagerLifecycle.rs b/src/install/PackageManager/PackageManagerLifecycle.rs index e4c9398fcba7..2c138433fe3d 100644 --- a/src/install/PackageManager/PackageManagerLifecycle.rs +++ b/src/install/PackageManager/PackageManagerLifecycle.rs @@ -35,7 +35,7 @@ pub struct LifecycleScriptTimeLog { } pub struct LifecycleScriptTimeLogEntry { - // PORT NOTE: Zig borrowed the lockfile string buffer (`string`). The Rust + // Zig borrowed the lockfile string buffer (`string`). The Rust // `LifecycleScriptSubprocess.package_name` is owned (`Box<[u8]>`) and freed // on `destroy`, so the log entry must own its copy to avoid a dangling // borrow. The list is at most a few dozen entries per install. @@ -48,7 +48,6 @@ pub struct LifecycleScriptTimeLogEntry { impl LifecycleScriptTimeLog { pub fn append_concurrent(&mut self, entry: LifecycleScriptTimeLogEntry) { self.mutex.lock(); - // TODO(port): consider `Mutex>` so the guard scopes the borrow self.list.push(entry); self.mutex.unlock(); } @@ -100,10 +99,10 @@ impl PackageManager { // expandToCapacity + @memset(.., .unknown) self.preinstall_state .resize(self.preinstall_state.capacity(), PreinstallState::Unknown); - let _ = offset; // PORT NOTE: resize already fills [offset..] with Unknown + let _ = offset; // resize already fills [offset..] with Unknown } - /// PORT NOTE: Zig `setPreinstallState(this, package_id, lockfile, value)` — the + /// Zig `setPreinstallState(this, package_id, lockfile, value)` — the /// separate `lockfile` parameter only feeds `lockfile.packages.len` into /// `ensurePreinstallStateListCapacity`. Every Rust caller passes /// `self.lockfile` (or an alias of it), which would alias `&mut self`; the @@ -122,7 +121,7 @@ impl PackageManager { self.preinstall_state[package_id as usize] } - /// PORT NOTE: Zig `determinePreinstallState(manager, pkg, lockfile, …)` — the + /// Zig `determinePreinstallState(manager, pkg, lockfile, …)` — the /// separate `lockfile` parameter is always `manager.lockfile` at every call /// site in the Rust port; collapsed onto `self.lockfile` to avoid the /// `&mut self` / `&self.lockfile` aliasing borrowck rejects. @@ -271,7 +270,7 @@ impl PackageManager { } pub fn tick_lifecycle_scripts(&mut self) { - // PORT NOTE: reshaped for borrowck — `self.event_loop.tick_once(self)` + // reshaped for borrowck — `self.event_loop.tick_once(self)` // would borrow `self` twice. Erase `self` to a raw context pointer // first; `tick_once` only forwards it opaquely to task callbacks. let ctx = std::ptr::from_mut::(self).cast::(); @@ -281,7 +280,7 @@ impl PackageManager { pub fn sleep(&mut self) { self.report_slow_lifecycle_scripts(); Output::flush(); - // PORT NOTE: see `tick_lifecycle_scripts` — `is_done` callback reborrows + // see `tick_lifecycle_scripts` — `is_done` callback reborrows // `self` (the struct that owns `event_loop`), so use the raw-pointer // `tick_raw` variant which only holds `&mut event_loop` between // `is_done` calls. @@ -405,7 +404,6 @@ impl PackageManager { foreground: bool, install_ctx: Option>, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let log_level = self.options.log_level; let mut any_scripts = false; for maybe_item in list.items.iter() { @@ -420,7 +418,7 @@ impl PackageManager { self.ensure_temp_node_gyp_script()?; - // PORT NOTE: `list` is moved into `spawn_package_scripts` below; copy + // `list` is moved into `spawn_package_scripts` below; copy // `cwd` out so the PATH builder can borrow it independently. let cwd_owned: Vec = list.cwd.as_bytes().to_vec(); let cwd: &[u8] = &cwd_owned; @@ -430,7 +428,7 @@ impl PackageManager { let mut script_env = env_loader.map.clone_with_allocator()?; // `defer script_env.map.deinit()` — handled by Drop - // PORT NOTE: `script_env.put` below needs `&mut`; copy PATH out so the + // `script_env.put` below needs `&mut`; copy PATH out so the // shared borrow does not span it. let original_path: Vec = script_env.get(b"PATH").unwrap_or(b"").to_vec(); diff --git a/src/install/PackageManager/PackageManagerOptions.rs b/src/install/PackageManager/PackageManagerOptions.rs index 76b01df8f740..f40a0877ec6b 100644 --- a/src/install/PackageManager/PackageManagerOptions.rs +++ b/src/install/PackageManager/PackageManagerOptions.rs @@ -1,7 +1,6 @@ use bun_core::ZStr; use bun_core::{Output, env_var}; use bun_paths::PathBuffer; -// TODO(port): move to _sys / verify crate path for schema API use crate::bun_schema::api as Api; use super::Subcommand; @@ -9,10 +8,9 @@ use super::command_line_arguments::{self, CommandLineArguments}; use bun_dotenv::Loader as DotEnvLoader; use bun_install::{Features, Npm}; -// PORT NOTE: `string` fields are `[]const u8` borrowed from CLI args / bunfig config, +// `string` fields are `[]const u8` borrowed from CLI args / bunfig config, // which live for the process lifetime. There is no `deinit` on Options. Mapped to // `&'static [u8]` per PORTING.md (no lifetime params on structs). -// TODO(port): lifetime — if any source is not truly 'static, add a lifetime parameter. pub struct Options { pub log_level: LogLevel, @@ -103,7 +101,7 @@ impl Default for Options { explicit_global_directory: b"", bin_path: bun_paths::path_literal!("node_modules/.bin"), did_override_default_scope: false, - // PORT NOTE: Zig had `= undefined`; always assigned in `load()` before read. + // Zig had `= undefined`; always assigned in `load()` before read. scope: Npm::registry::Scope::default(), registries: Npm::registry::Map::default(), cache_directory: b"", @@ -131,7 +129,8 @@ impl Default for Options { json_output: false, max_retry_count: 5, min_simultaneous_requests: 4, - // TODO(port): no default in Zig — caller must supply at construction + // Placeholder only — every constructor supplies the real value + // (`cli.concurrent_scripts` or `cpu_count * 2`); Zig had no default here. max_concurrent_lifecycle_scripts: 0, publish_config: PublishConfig::default(), ca: Box::default(), @@ -158,7 +157,7 @@ impl Default for Options { } } -// PORT NOTE: was an anonymous `union(enum)` field type in Zig. +// was an anonymous `union(enum)` field type in Zig. pub enum PatchFeatures { Nothing, Patch, @@ -181,7 +180,7 @@ pub enum Access { } impl Access { - // PORT NOTE: was `bun.ComptimeEnumMap(Access)`; ≤8 entries → plain match on &[u8]. + // was `bun.ComptimeEnumMap(Access)`; ≤8 entries → plain match on &[u8]. pub fn from_str(str: &[u8]) -> Option { match str { b"public" => Some(Access::Public), @@ -208,7 +207,7 @@ pub enum AuthType { } impl AuthType { - // PORT NOTE: was `bun.ComptimeEnumMap(AuthType)`; ≤8 entries → plain match on &[u8]. + // was `bun.ComptimeEnumMap(AuthType)`; ≤8 entries → plain match on &[u8]. pub fn from_str(str: &[u8]) -> Option { match str { b"legacy" => Some(AuthType::Legacy), @@ -282,7 +281,7 @@ pub struct Update { pub peer: bool, } -// PORT NOTE: `std.fs.cwd().makeOpenPath` → `bun_sys::Dir::cwd().make_open_path()` +// `std.fs.cwd().makeOpenPath` → `bun_sys::Dir::cwd().make_open_path()` // (mkdir -p + open dir). Return type was `!std.fs.Dir`; callers store the raw // `Fd` (`options.global_bin_dir: Fd`), so unwrap to `.fd`. pub fn open_global_dir(explicit_global_dir: &[u8]) -> Result { @@ -373,7 +372,7 @@ pub(crate) fn open_global_bin_dir( )) } -// PORT NOTE: Zig borrowed `[]const u8` from `Api.BunInstall` (process-lifetime +// Zig borrowed `[]const u8` from `Api.BunInstall` (process-lifetime // arena). Rust `BunInstall` owns `Box<[u8]>`; Options stores `&'static [u8]` // per the "no struct lifetime params" porting convention. Park a clone for the // lifetime of the install command (matches Zig's never-reset config arena) via @@ -397,7 +396,7 @@ impl Options { subcommand: Subcommand, ) -> Result<(), bun_alloc::AllocError> { let mut base = Api::NpmRegistry::default(); - // PORT NOTE: reshaped for borrowck — Zig captures `*Api.BunInstall` twice via `if (bun_install_) |config|`. + // reshaped for borrowck — Zig captures `*Api.BunInstall` twice via `if (bun_install_) |config|`. let bun_install_ref = bun_install_; if let Some(config) = bun_install_ref { if let Some(registry) = &config.default_registry { @@ -411,10 +410,10 @@ impl Options { if base.url.is_empty() { base.url = Npm::registry::DEFAULT_URL.as_bytes().into(); } - // PORT NOTE: Zig passes `base` by-value (struct copy); clone so the + // Zig passes `base` by-value (struct copy); clone so the // `base.url` fallback below in the scoped-registry loop stays valid. self.scope = Npm::registry::Scope::from_api(b"", base.clone(), env)?; - // PORT NOTE: Zig `defer { this.did_override_default_scope = ... }` moved to end of fn; + // Zig `defer { this.did_override_default_scope = ... }` moved to end of fn; // on the OOM error path the field is irrelevant (process aborts). if let Some(config) = bun_install_ref { @@ -586,7 +585,7 @@ impl Options { ]; let mut did_set = false; - // PORT NOTE: was `inline for`; homogeneous elements → plain for. + // was `inline for`; homogeneous elements → plain for. for registry_key in REGISTRY_KEYS { if !did_set { if let Some(registry_) = env.get(registry_key) { @@ -605,7 +604,7 @@ impl Options { } else { Box::default() }; - // PORT NOTE: was `std.mem.zeroes(Api.NpmRegistry)`; zeroed slices are + // was `std.mem.zeroes(Api.NpmRegistry)`; zeroed slices are // invalid in Rust — use Default (empty strings) which is semantically equivalent. let api_registry = Api::NpmRegistry { url: registry_.into(), @@ -628,7 +627,7 @@ impl Options { ]; let mut did_set = false; - // PORT NOTE: was `inline for`; homogeneous elements → plain for. + // was `inline for`; homogeneous elements → plain for. for registry_key in TOKEN_KEYS { if !did_set { if let Some(registry_) = env.get(registry_key) { @@ -648,7 +647,7 @@ impl Options { } if let Some(retry_count) = env.get(b"BUN_CONFIG_HTTP_RETRY_COUNT") { - // PORT NOTE: Zig `parseInt(u16, str, 10) catch null` — `Result` → `.ok()`. + // Zig `parseInt(u16, str, 10) catch null` — `Result` → `.ok()`. if let Ok(int) = bun_core::parse_int::(retry_count, 10) { self.max_retry_count = int; } @@ -904,7 +903,7 @@ impl Options { self.enable.set(Enable::FORCE_SAVE_LOCKFILE, false); } - // PORT NOTE: moved from `defer { ... }` after scope assignment (see note above). + // moved from `defer { ... }` after scope assignment (see note above). self.did_override_default_scope = self.scope.url_hash != *Npm::registry::DEFAULT_URL_HASH; Ok(()) diff --git a/src/install/PackageManager/PackageManagerResolution.rs b/src/install/PackageManager/PackageManagerResolution.rs index e40202bae7f1..9faa3fdfa072 100644 --- a/src/install/PackageManager/PackageManagerResolution.rs +++ b/src/install/PackageManager/PackageManagerResolution.rs @@ -102,7 +102,7 @@ impl PackageManager { return None; } - // PORT NOTE: reshaped for borrowck — Zig calls + // reshaped for borrowck — Zig calls // `this.manifests.byNameHash(this, …, .load_from_memory, …)`, // which in Rust would require simultaneous `&mut self.manifests` // (receiver) and `&mut self` (arg). The memory-only path touches @@ -145,7 +145,6 @@ impl PackageManager { tags_buf: &mut Vec, package_name: &[u8], ) -> Result, bun_core::Error> { - // TODO(port): narrow error set let mut list: Vec = Vec::new(); // Zig: `getCacheDirectory().openDir(package_name, .{ .iterate = true })`. let cache_dir = super::get_cache_directory(self); @@ -306,7 +305,7 @@ impl PackageManager { } pub fn assign_resolution(&mut self, dependency_id: DependencyID, package_id: PackageID) { - // PORT NOTE: reshaped for borrowck — capture lengths before mutable borrows. + // reshaped for borrowck — capture lengths before mutable borrows. if cfg!(debug_assertions) { debug_assert!( (dependency_id as usize) < self.lockfile.buffers.resolutions.as_slice().len() @@ -327,7 +326,7 @@ impl PackageManager { } pub fn assign_root_resolution(&mut self, dependency_id: DependencyID, package_id: PackageID) { - // PORT NOTE: reshaped for borrowck — capture lengths before mutable borrows. + // reshaped for borrowck — capture lengths before mutable borrows. if cfg!(debug_assertions) { debug_assert!( (dependency_id as usize) < self.lockfile.buffers.resolutions.as_slice().len() diff --git a/src/install/PackageManager/PopulateManifestCache.rs b/src/install/PackageManager/PopulateManifestCache.rs index 3e437f62e5db..7e38132a50ee 100644 --- a/src/install/PackageManager/PopulateManifestCache.rs +++ b/src/install/PackageManager/PopulateManifestCache.rs @@ -51,7 +51,7 @@ fn start_manifest_task( needs_extended_manifest: bool, ) -> Result<(), StartManifestTaskError> { let task_id = Task::Id::for_manifest(pkg_name); - // PORT NOTE: Zig passes the *raw packed-struct bit* `dep.behavior.optional` + // Zig passes the *raw packed-struct bit* `dep.behavior.optional` // — not `Behavior.isOptional()` (which is `optional && !peer`). For // optional-peer deps the raw bit is `true` but `is_optional()` is `false`, // which would flip both the dedupe-map `is_required` bookkeeping and @@ -63,14 +63,13 @@ fn start_manifest_task( } manager.start_progress_bar_if_none(); - // PORT NOTE: reshaped for borrowck — Zig writes the whole struct via `.* = .{}` + // reshaped for borrowck — Zig writes the whole struct via `.* = .{}` // and reads `manager` again for `scopeForPackageName`. `get_network_task()` // borrows `&mut manager.preallocated_network_tasks`, so compute everything // that needs `&manager` *before* taking that borrow, then populate the pool // slot through a raw pointer (matches `runTasks::generate_network_task_for_tarball`). let scope = bun_ptr::BackRef::new(manager.scope_for_package_name(pkg_name)); // Backref address only — stored, not dereffed in this function. - // TODO(port): lifetime — BACKREF. let manager_backref: *mut PackageManager = manager; // Take the pool slot as a raw pointer so borrowck releases `manager` for the @@ -123,10 +122,9 @@ pub fn populate_manifest_cache( manager: &mut PackageManager, packages: Packages<'_>, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let log_level = manager.options.log_level; - // PORT NOTE: heavy borrowck overlap — Zig holds slices into + // heavy borrowck overlap — Zig holds slices into // `manager.lockfile` while the loop body calls `&mut`-taking methods on // `manager`. The lockfile lives in `Box` (stable address) and is // not resized by anything below, so derive the slices through a raw @@ -283,7 +281,7 @@ pub fn populate_manifest_cache( if run_tasks::pending_task_count(manager) > 0 { struct RunClosure { - // PORT NOTE: Zig stores `*PackageManager` non-exclusively; + // Zig stores `*PackageManager` non-exclusively; // `sleep_until` also receives this raw pointer, so storing // `&mut PackageManager` here would alias under Stacked Borrows. manager: *mut PackageManager, @@ -296,7 +294,7 @@ pub fn populate_manifest_cache( // callback, so this is the unique live borrow. let manager = unsafe { &mut *closure.manager }; let log_level = manager.options.log_level; - // PORT NOTE: void RunTasksCallbacks — `extract_ctx` is unit. Do NOT pass + // void RunTasksCallbacks — `extract_ctx` is unit. Do NOT pass // `manager` as both receiver and ctx (aliased &mut). Zig passed // `(comptime *PackageManager, closure.manager)`; the generic context // pair collapses to `&mut ()` in Rust. diff --git a/src/install/PackageManager/ProgressStrings.rs b/src/install/PackageManager/ProgressStrings.rs index 1f34b41cadaf..10dd1b7f0b05 100644 --- a/src/install/PackageManager/ProgressStrings.rs +++ b/src/install/PackageManager/ProgressStrings.rs @@ -10,8 +10,8 @@ use super::PackageManager; pub struct ProgressStrings; impl ProgressStrings { - // PORT NOTE: base *_NO_EMOJI_ / *_EMOJI consts stay &str because concatcp! requires str - // inputs; derived consts and fn returns are &[u8] per type-map ([]const u8 → &[u8]). + // The base *_NO_EMOJI_ / *_EMOJI consts stay &str because concatcp! requires str + // inputs; derived consts and fn returns are &[u8]. pub const DOWNLOAD_NO_EMOJI_: &'static str = "Resolving"; const DOWNLOAD_NO_EMOJI: &'static [u8] = concatcp!(ProgressStrings::DOWNLOAD_NO_EMOJI_, "\n").as_bytes(); @@ -139,7 +139,7 @@ impl PackageManager { pub fn start_progress_bar(&mut self) { self.progress.supports_ansi_escape_codes = Output::enable_ansi_colors_stderr(); - // PORT NOTE: `Progress::start` returns `&mut Node` borrowing `self.progress`; + // `Progress::start` returns `&mut Node` borrowing `self.progress`; // decay to a raw ptr immediately so the exclusive borrow ends before we // re-borrow `&mut self` for `set_node_name` / `progress.refresh()`. let node: *mut ProgressNode = self.progress.start(ProgressStrings::download(), 0); diff --git a/src/install/PackageManager/UpdateRequest.rs b/src/install/PackageManager/UpdateRequest.rs index 41d512c8dbf2..e877217e15cf 100644 --- a/src/install/PackageManager/UpdateRequest.rs +++ b/src/install/PackageManager/UpdateRequest.rs @@ -14,7 +14,8 @@ use bun_install::{ // `lockfile.packages.items_name()` is provided by an extension trait on // `MultiArrayList` (Zig: `lockfile.packages.items(.name)`). pub struct UpdateRequest { - // TODO(port): lifetime — Zig leaks these (no deinit); using &'static for now + // Zig intentionally leaks these (no deinit); CLI positionals are + // process-lifetime, hence `&'static`. pub name: &'static [u8], pub name_hash: PackageNameHash, pub version: dependency::Version, @@ -32,8 +33,9 @@ pub struct UpdateRequest { pub package_id: PackageID, pub is_aliased: bool, pub failed: bool, - /// This must be cloned to handle when the AST store resets - // TODO(port): lifetime — ARENA-owned (AST Expr.Data store); raw ptr per LIFETIMES.tsv + /// This must be cloned to handle when the AST store resets. + /// ARENA-owned (AST `Expr.Data` store) — raw pointer per LIFETIMES.tsv; + /// only valid while the store that allocated it is alive. pub e_string: Option<*mut js_ast::E::String>, } @@ -139,8 +141,6 @@ impl UpdateRequest { Self::parse_with_error(pm, log, positionals, update_requests, subcommand, true) .unwrap_or_else(|_| Global::crash()) } - - // TODO(port): narrow error set — only `UnrecognizedDependencyFormat` is returned pub fn parse_with_error<'a>( mut pm: Option<&mut PackageManager>, log: &mut Log, @@ -232,7 +232,6 @@ impl UpdateRequest { return Err(bun_core::err!("UnrecognizedDependencyFormat")); }; - // TODO(port): Dependency.Version tag/value layout — Zig uses separate .tag + .value union if alias.is_some() && version.tag == dependency::version::Tag::Git { if let Some(ver) = Dependency::parse_with_optional_tag( placeholder, diff --git a/src/install/PackageManager/WorkspacePackageJSONCache.rs b/src/install/PackageManager/WorkspacePackageJSONCache.rs index f2cfaa3bbdfa..b40dfa9f3f1b 100644 --- a/src/install/PackageManager/WorkspacePackageJSONCache.rs +++ b/src/install/PackageManager/WorkspacePackageJSONCache.rs @@ -76,7 +76,7 @@ impl MapEntry { pub type Map = StringHashMap; -// PORT NOTE: Zig `JSON.parsePackageJSONUTF8WithOpts` takes `comptime opts: +// Zig `JSON.parsePackageJSONUTF8WithOpts` takes `comptime opts: // js_lexer.JSONOptions`; the Rust port (`bun_parsers::json`) spells those // out as 8 const-generic bools. The only field this module varies at runtime // is `guess_indentation` (because `GetJSONOptions` was demoted from comptime @@ -137,7 +137,6 @@ pub enum GetResult<'a> { impl<'a> GetResult<'a> { pub fn unwrap(self) -> Result<&'a mut MapEntry, Error> { - // TODO(port): narrow error set match self { GetResult::Entry(entry) => Ok(entry), GetResult::ReadErr(err) => Err(err), @@ -177,7 +176,7 @@ impl WorkspacePackageJSONCache { &buf[..abs_package_json_path.len()] }; - // PORT NOTE: reshaped for borrowck — Zig `getOrPut` reserves a slot + // reshaped for borrowck — Zig `getOrPut` reserves a slot // first and `remove`s on failure while still holding `entry.value_ptr`. // Rust cannot hold the entry borrow across `self.map.remove`, so check // membership up front and only insert into the map after a successful @@ -255,7 +254,7 @@ impl WorkspacePackageJSONCache { &buf[..text.len()] }; - // PORT NOTE: reshaped for borrowck — see `get_with_path` above. + // reshaped for borrowck — see `get_with_path` above. if self.map.contains_key(path) { return GetResult::Entry(self.map.get_mut(path).unwrap()); } diff --git a/src/install/PackageManager/install_with_manager.rs b/src/install/PackageManager/install_with_manager.rs index dae6ffbad2a4..252a50fb57e1 100644 --- a/src/install/PackageManager/install_with_manager.rs +++ b/src/install/PackageManager/install_with_manager.rs @@ -50,7 +50,6 @@ pub fn install_with_manager( root_package_json_path: &ZStr, original_cwd: &[u8], ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let log_level = manager.options.log_level; // Start resolving DNS for the default registry immediately. @@ -68,7 +67,7 @@ pub fn install_with_manager( } } - // PORT NOTE: reshaped for borrowck — Zig passes `manager`, `manager.lockfile`, + // reshaped for borrowck — Zig passes `manager`, `manager.lockfile`, // and `manager.log` to `loadFromCwd` simultaneously. Route through a single // raw provenance root so the three reborrows share a tag. let load_result: lockfile::LoadResult = if manager.options.do_.load_lockfile() { @@ -181,13 +180,13 @@ pub fn install_with_manager( } }; - // PORT NOTE: Zig copies `Source` by value; in Rust it's not `Copy`, so + // Zig copies `Source` by value; in Rust it's not `Copy`, so // clone it (cheap — `Source` is a few `Box<[u8]>` handles) so the // `&mut *mgr` reborrow below doesn't conflict with the cache borrow. let source_copy = root_package_json_entry.source.clone(); let mut resolver: () = (); - // PORT NOTE: Zig passes `manager`, `manager.log` and a fresh + // Zig passes `manager`, `manager.log` and a fresh // stack `lockfile` simultaneously. Route through raw ptrs so // borrowck doesn't see overlapping `&mut PackageManager` / // `&mut Lockfile` (Zig `*T` semantics). @@ -214,7 +213,7 @@ pub fn install_with_manager( .into_boxed_slice(); // @memset already done via vec! init - // PORT NOTE: Zig passes `manager`, `manager.log`, `manager.lockfile` and a + // Zig passes `manager`, `manager.log`, `manager.lockfile` and a // fresh `lockfile` simultaneously. Route through raw ptrs to satisfy // borrowck; `Diff::generate` is ported in lockfile_real::package. manager.summary = { @@ -352,7 +351,7 @@ pub fn install_with_manager( builder, )?; - // PORT NOTE: `ArrayHashMap::clone()` is an inherent fallible method (Zig: + // `ArrayHashMap::clone()` is an inherent fallible method (Zig: // `try trusted_dependencies.clone(allocator)`), not the `Clone` trait, so // `Option::clone` won't see it — map by hand. *lf.trusted_dependencies = match &lockfile.trusted_dependencies { @@ -363,13 +362,13 @@ pub fn install_with_manager( lf.dependencies.reserve(len as usize); lf.resolutions.reserve(len as usize); - // PORT NOTE: copy `old_resolutions` to a temporary Vec — + // copy `old_resolutions` to a temporary Vec — // the slice indexes into `buffers.resolutions`, which we're // about to grow via spare-capacity writes / `set_len` below. let old_resolutions: Vec = old_resolutions_list.get(lf.resolutions).to_vec(); - // PORT NOTE: Zig slices raw spare capacity via `.items.ptr[off .. off + len]`, + // Zig slices raw spare capacity via `.items.ptr[off .. off + len]`, // `@memset`s it (no drop), then extends `.items.len`. `extend_from_fn` // mirrors that: writes into `spare_capacity_mut()` then bumps `len`, so we // never form `&mut [T]` over uninitialized storage and never drop garbage. @@ -432,10 +431,10 @@ pub fn install_with_manager( let pkg_name_and_version_hash = *key; debug_assert!(value.patchfile_hash_is_null); let gop = lf.patched_dependencies.entry(pkg_name_and_version_hash); - // PORT NOTE: ArrayHashMap getOrPut semantics → entry API approximation + // ArrayHashMap getOrPut semantics → entry API approximation match gop { bun_collections::array_hash_map::MapEntry::Vacant(v) => { - // PORT NOTE: `PatchedDep` has private padding/hash fields, + // `PatchedDep` has private padding/hash fields, // so the `..Default::default()` struct-update form is rejected // outside its module. Build via `default()` + field stores. let mut new = crate::lockfile_real::PatchedDep::default(); @@ -603,7 +602,7 @@ pub fn install_with_manager( manager.log_mut().reset(); // This operation doesn't perform any I/O, so it should be relatively cheap. - // PORT NOTE: Zig copies the `*Lockfile` pointer, leaving `manager.lockfile` intact so both + // Zig copies the `*Lockfile` pointer, leaving `manager.lockfile` intact so both // old and new lockfiles are live for the later `eql(lockfile_before_clean, ...)` checks. // In Rust `manager.lockfile: Box` would move; compute the new lockfile first, then // `mem::replace` so `lockfile_before_clean` owns the old box and `manager.lockfile` the new. @@ -667,7 +666,7 @@ pub fn install_with_manager( root_scripts.append_to_lockfile(&mut manager.lockfile); } { - // PORT NOTE: reshaped for borrowck — Zig holds shared slices into + // reshaped for borrowck — Zig holds shared slices into // `packages.items(.resolution/.meta/.scripts)` while pushing into // `manager.lockfile.scripts`. Field-level split borrow keeps the two // disjoint columns alive simultaneously without raw-pointer routing. @@ -924,7 +923,7 @@ impl RunTasksCallbacks for InstallWaitCallbacks { } struct RunAndWaitClosure { - // PORT NOTE: Zig stores `*PackageManager` here while the caller also holds the same + // Zig stores `*PackageManager` here while the caller also holds the same // pointer to call `sleepUntil`. Storing `&mut PackageManager` would alias the outer // borrow in `run_and_wait`. Keep a raw pointer; `run_and_wait` derives this pointer // first and then reborrows *through it* for the `sleep_until` receiver, so both the @@ -954,7 +953,7 @@ impl this.drain_dependency_list(); - // PORT NOTE: void RunTasksCallbacks — Zig passes an anon struct with + // void RunTasksCallbacks — Zig passes an anon struct with // `void` hooks and `progress_bar = true`. The Rust trait dispatch needs a // concrete `RunTasksCallbacks` impl; `extract_ctx` collapses to `()` so we // do NOT pass `this` as both receiver and ctx (would alias `&mut`). @@ -1119,7 +1118,7 @@ fn print_summary_tree( install_summary: &PackageInstallSummary, log_level: Options::LogLevel, ) -> Result<(), bun_core::Error> { - // PORT NOTE: reshaped for borrowck — Zig builds `Printer` borrowing + // reshaped for borrowck — Zig builds `Printer` borrowing // `this.lockfile` / `this.options` while also passing `this` (the // PackageManager) to `Tree::print`. Route through a single `*mut // PackageManager` provenance root and reborrow disjoint fields @@ -1348,7 +1347,7 @@ fn add_dependency_error( dependency: &Dependency, err: bun_core::Error, ) { - // PORT NOTE: reshaped for borrowck — capture the realname slice before + // reshaped for borrowck — capture the realname slice before // taking `&mut` on `manager.log` (Zig held both via shared `*` pointers). let realname = dependency.realname(); let path = manager.lockfile.str(&realname).to_vec(); @@ -1444,7 +1443,7 @@ fn report_lockfile_load_error( #[inline(never)] fn record_updating_package_versions(manager: &mut PackageManager) { // existing lockfile, get the original version is updating - // PORT NOTE: reshaped for borrowck — Zig holds `*Lockfile` while + // reshaped for borrowck — Zig holds `*Lockfile` while // also mutating `manager.updating_packages`. Field-level split // borrow keeps the disjoint columns alive without raw pointers. let lockfile: &Lockfile = &manager.lockfile; @@ -1805,7 +1804,7 @@ fn write_yarn_lock_with_progress( manager: &mut PackageManager, log_level: Options::LogLevel, ) -> Result<(), bun_core::Error> { - // PORT NOTE: reshaped for borrowck — Zig holds `*Progress.Node` (returned by + // reshaped for borrowck — Zig holds `*Progress.Node` (returned by // `progress.start`) across `writeYarnLock(manager)`. `Progress::start` returns // `&mut self.root`, so re-access it via `manager.progress.root` after the // `&mut manager` borrow ends instead of keeping a live `&mut Node`. @@ -1852,7 +1851,7 @@ fn run_root_lifecycle_scripts( // have finished, and lockfiles have been saved let optional = false; let output_in_foreground = true; - // PORT NOTE: Zig passes `scripts.*` (deref-copy of the List). + // Zig passes `scripts.*` (deref-copy of the List). // `spawn_package_lifecycle_scripts` consumes by-value; `.take()` // moves it out (Zig only frees `package_name` afterwards, which is // owned by the List in Rust and drops with it). diff --git a/src/install/PackageManager/patchPackage.rs b/src/install/PackageManager/patchPackage.rs index 4a87fdd677cb..7f69463eae8c 100644 --- a/src/install/PackageManager/patchPackage.rs +++ b/src/install/PackageManager/patchPackage.rs @@ -51,7 +51,6 @@ pub fn do_patch_commit( let mut folder_path_buf = PathBuffer::uninit(); let mut lockfile: Box = Box::default(); let log = manager.log_mut(); - // TODO(port): narrow error set match lockfile.load_from_cwd::(Some(manager), log) { lockfile::LoadResult::NotFound => { Output::err_generic( @@ -101,7 +100,7 @@ pub fn do_patch_commit( .root_package_id .get(&lockfile, manager.workspace_name_hash); let not_in_workspace_root = workspace_package_id != 0; - // PORT NOTE: reshaped for borrowck — owned buffer kept separately so `argument` can borrow it + // reshaped for borrowck — owned buffer kept separately so `argument` can borrow it let argument_owned: Option>; let argument: &[u8] = if arg_kind == PatchArgKind::Path && not_in_workspace_root @@ -141,7 +140,7 @@ pub fn do_patch_commit( let mut iterator = tree::Iterator::<{ tree::IteratorPathStyle::NodeModules }>::init(&lockfile); let mut resolution_buf = [0u8; 1024]; - // PORT NOTE: reshaped for borrowck — `compute_cache_dir_and_subpath` borrows + // reshaped for borrowck — `compute_cache_dir_and_subpath` borrows // `manager` mutably while the package name/resolution borrow `lockfile` // (which itself sometimes aliases `manager.lockfile`). Clone the slice/ // resolution out first, then compute, then assemble the result tuple. @@ -437,7 +436,7 @@ pub fn do_patch_commit( } break 'has_bun_patch_tag Some(patch_tag); }; - // PORT NOTE: deferred restore — one-off rename-back logic on every exit + // deferred restore — one-off rename-back logic on every exit // path of `'brk`. Captures borrow into stack buffers. scopeguard::defer! { if has_nested_node_modules || bun_patch_tag.is_some() { @@ -629,7 +628,7 @@ pub fn do_patch_commit( } let mut patch_key = Vec::new(); - // PORT NOTE: re-slice instead of reusing `resolution_label` so its borrow ends + // re-slice instead of reusing `resolution_label` so its borrow ends // before the `.patch` suffix write above; the prefix bytes are unchanged. write!( &mut patch_key, @@ -680,7 +679,7 @@ fn escape_patch_filename(name: &[u8]) -> Option> { } } - // PORT NOTE: Zig built this table via @typeInfo reflection over single-char enum field names. + // Zig built this table via @typeInfo reflection over single-char enum field names. // Rust has no equivalent; the table is filled by hand with the same entries. const ESCAPE_TABLE: [EscapeVal; 256] = { let mut table = [EscapeVal::Other; 256]; @@ -736,7 +735,7 @@ pub fn prepare_patch(manager: &mut PackageManager) -> Result<(), bun_core::Error .root_package_id .get(&manager.lockfile, workspace_name_hash); let not_in_workspace_root = workspace_package_id != 0; - // PORT NOTE: reshaped for borrowck — owned buffer kept so `argument` can borrow it. + // reshaped for borrowck — owned buffer kept so `argument` can borrow it. let argument_owned: Option>; let argument: &[u8] = if arg_kind == PatchArgKind::Path && not_in_workspace_root @@ -808,7 +807,7 @@ pub fn prepare_patch(manager: &mut PackageManager) -> Result<(), bun_core::Error let mut resolver: () = (); let mut package = Package::default(); let log = manager.log_mut(); - // PORT NOTE: borrowck — `parse_with_json` needs `&mut Lockfile` and + // borrowck — `parse_with_json` needs `&mut Lockfile` and // `&mut PackageManager` simultaneously, but the lockfile here is // `manager.lockfile`. Temporarily move the Box out so the two // borrows are disjoint; `parse_with_json` never reads `pm.lockfile` @@ -1206,7 +1205,7 @@ fn overwrite_package_in_node_modules_folder( type NodeModulesIterator<'a> = tree::Iterator<'a, { tree::IteratorPathStyle::NodeModules }>; -// PORT NOTE: reshaped for borrowck — `tree::Iterator::next` returns an +// reshaped for borrowck — `tree::Iterator::next` returns an // `IteratorNext<'_>` borrowing the iterator's internal `path_buf`, so we // cannot return it from inside a `while let` (borrowck rejects the next // iteration's reborrow even though it's unreachable). Callers only need @@ -1419,7 +1418,7 @@ fn pkg_info_for_name_and_version( Global::crash(); } -// PORT NOTE: takes `workspace_package_id` directly instead of `&mut PackageManager` — +// takes `workspace_package_id` directly instead of `&mut PackageManager` — // both callers already compute it via `root_package_id.get()` immediately before, and // passing `manager` here would alias `&manager.lockfile` in `prepare_patch`. fn path_argument_relative_to_root_workspace_package( @@ -1449,7 +1448,7 @@ impl PatchArgKind { if strings::contains(argument, b"node_modules/") { return PatchArgKind::Path; } - // PORT NOTE: spec asymmetry — Zig (patchPackage.zig:1028) uses `hasPrefix` + // spec asymmetry — Zig (patchPackage.zig:1028) uses `hasPrefix` // for the Windows-backslash arm but `contains` for the posix arm above. // Match the spec exactly; if this is a Zig bug, fix both sides separately. if cfg!(windows) && strings::has_prefix(argument, b"node_modules\\") { diff --git a/src/install/PackageManager/processDependencyList.rs b/src/install/PackageManager/processDependencyList.rs index 86b2be564d32..f40342a348cc 100644 --- a/src/install/PackageManager/processDependencyList.rs +++ b/src/install/PackageManager/processDependencyList.rs @@ -324,15 +324,13 @@ impl PackageManager { Global::crash(); } }; - // PORT NOTE (spec parity): Zig writes - // var scripts = manager.lockfile.packages.items(.scripts)[package_id.*]; - // which COPIES the `Scripts` struct into a local; the - // subsequent `parseAlloc` / `.filled = true` mutate the - // local and are never stored back, so - // `lockfile.packages[id].scripts` is not updated. This is - // a latent dead-store bug in processDependencyList.zig, - // but we match it exactly so .rs/.zig observable behavior - // agree. The `builder` appends still land in + // Intentional dead store: `scripts` is a local copy of + // `lockfile.packages[id].scripts`, and the `parse_alloc` / + // `.filled = true` mutations are never stored back, so + // `lockfile.packages[id].scripts` is not updated. The + // original implementation had this same latent dead-store + // bug, and we match it exactly so observable behavior + // agrees. The `builder` appends still land in // `lockfile.buffers.string_bytes`, preserving that side // effect. (Hoisted above `string_builder()` for borrowck — // `parse_count`/`allocate` don't touch `packages`.) @@ -360,8 +358,8 @@ impl PackageManager { ) -> Result<(), bun_core::Error> { match *item { TaskCallbackContext::Dependency(dependency_id) => { - // PORT NOTE: reshaped for borrowck — clone the dependency row - // out of the buffer before re-borrowing `self` for enqueue. + // Clone the dependency row out of the buffer before + // re-borrowing `self` for enqueue. let dependency = Clone::clone( &self.lockfile.buffers.dependencies.as_slice()[dependency_id as usize], ); @@ -408,8 +406,8 @@ impl PackageManager { pub fn process_peer_dependency_list(&mut self) -> Result<(), bun_core::Error> { while let Some(peer_dependency_id) = self.peer_dependencies.read_item() { - // PORT NOTE: reshaped for borrowck — clone the dependency row out - // of the buffer before re-borrowing `self` for enqueue. + // Clone the dependency row out of the buffer before re-borrowing + // `self` for enqueue. let dependency = Clone::clone( &self.lockfile.buffers.dependencies.as_slice()[peer_dependency_id as usize], ); diff --git a/src/install/PackageManager/runTasks.rs b/src/install/PackageManager/runTasks.rs index b7952a257221..11a287756b9b 100644 --- a/src/install/PackageManager/runTasks.rs +++ b/src/install/PackageManager/runTasks.rs @@ -47,11 +47,12 @@ use crate::isolated_install::store as Store; // and branches on `@TypeOf(callbacks.onExtract) != void`, `Ctx == *PackageInstaller`, // etc. Rust models this as a single trait with associated consts gating the // optional hooks (default-unreachable bodies), plus associated-const tags for -// the `Ctx` identity checks. Phase B should revisit whether the call sites can -// be split into 2–3 concrete impls instead of const-gated branches. -// -// TODO(port): callbacks trait — comptime duck-typing reshape; verify against -// the three call sites (`PackageInstaller`, `Store.Installer`, void) in Phase B. +// the `Ctx` identity checks. The trait branches on three `Ctx` identities +// (`PackageInstaller`, `Store.Installer`, neither), but there are six impls +// across four `Ctx` shapes: `PackageInstaller` (`HoistedRunTasksCallbacks`), +// `Store::Installer` (`StoreRunTasksCallbacks`), unit +// (`VoidRunTasksCallbacks`, `ManifestsOnlyCallbacks`, `InstallWaitCallbacks`), +// and `Queue` (`QueueRunTasksCallbacks` in `src/jsc/AsyncModule.rs`). pub trait RunTasksCallbacks { /// Mirrors `Ctx` (the `extract_ctx` value type). type Ctx; @@ -78,7 +79,7 @@ pub trait RunTasksCallbacks { unreachable!() } - // PORT NOTE: Zig calls `onPackageDownloadError` with two distinct shapes + // Zig calls `onPackageDownloadError` with two distinct shapes // depending on the comptime `Ctx`: `task.task_id: Task.Id` for // `*Store.Installer`, `package_id: PackageID` otherwise. Model the // comptime branch as static dispatch via two trait methods so impls @@ -153,7 +154,7 @@ pub fn run_tasks( let mut timestamp_this_tick: Option = None; // Zig: `defer { manager.drainDependencyList(); ... progress update ... }` - // PORT NOTE: scopeguard captures `manager` via raw pointer because the loop + // scopeguard captures `manager` via raw pointer because the loop // body holds `&mut` to it for the function's duration; `has_updated_this_run` // is a `Cell` so the guard captures it by shared ref. The guard runs // on every exit (incl. `?` early-returns), matching Zig `defer` semantics. @@ -252,7 +253,7 @@ pub fn run_tasks( } if C::IS_STORE_INSTALLER { - // PORT NOTE: reshaped for borrowck — Zig writes `const installer: + // reshaped for borrowck — Zig writes `const installer: // *Store.Installer = extract_ctx;` and freely aliases // `installer.manager` with the outer `manager`. Here we obtain the // installer via the trait downcast and access PackageManager only @@ -296,11 +297,11 @@ pub fn run_tasks( // its sole consumer (see Installer.rs Yield::RunScripts). // Zig: `list.*` — by-value copy of the List. let list_val = unsafe { (*list).clone() }; - // PORT NOTE: reshaped for borrowck — `Command::Context<'a>` + // reshaped for borrowck — `Command::Context<'a>` // is `&'a mut ContextData`; reborrow instead of moving the // field out of `*installer`. let command_ctx: Command::Context<'_> = &mut *installer.command_ctx; - // PORT NOTE: `installer.manager == manager` (same allocation, + // `installer.manager == manager` (same allocation, // see fn-signature note); call via the body shadow which is a // reborrow of `manager_ptr` — no extra unsafe alias needed. let spawn_res = manager.spawn_package_lifecycle_scripts( @@ -360,7 +361,7 @@ pub fn run_tasks( name, is_extended_manifest, } => { - // PORT NOTE: reshaped for borrowck — capture the name's slice + // reshaped for borrowck — capture the name's slice // pointer (`StringOrTinyString` is self-referential and not // `Clone`) so the loop body can read `name` after the // `&mut task.callback` borrow ends. @@ -555,7 +556,7 @@ pub fn run_tasks( manifest.pkg.public_max_age = timestamp_this_tick.unwrap(); - // PORT NOTE: reshaped for borrowck — Zig writes through + // reshaped for borrowck — Zig writes through // the `getOrPut` slot then re-reads it for `saveAsync`. // `bun_collections::HashMap` lacks `get_or_put` for // non-`Default` values, so insert by-value (overwriting @@ -567,7 +568,7 @@ pub fn run_tasks( .insert(name_hash, ManifestEntry::Manifest(manifest)); if manager.options.enable.contains(Enable::MANIFEST_CACHE) { - // PORT NOTE: reshaped for borrowck — compute the + // reshaped for borrowck — compute the // `&mut`-taking directory accessors first so the // shared `scope_for_package_name` / `manifests` // borrows below do not overlap them. `save_async` @@ -611,7 +612,7 @@ pub fn run_tasks( } } - // PORT NOTE: reshaped — `enqueue_parse_npm_package` takes + // reshaped — `enqueue_parse_npm_package` takes // `StringOrTinyString` by value; reconstruct from the slice we // captured (the original lives in `task.callback`, which the // enqueued resolve Task takes ownership of via `network`). @@ -620,7 +621,7 @@ pub fn run_tasks( &mut crate::network_task::filename_store_appender(), ) .expect("unreachable"); - // PORT NOTE: reshaped for borrowck — split the nested `&mut + // reshaped for borrowck — split the nested `&mut // manager` borrows (`task_batch.push` vs. `enqueue_*`). // SAFETY: `task_ptr` is non-null (checked by the iterator loop guard) // and exclusively owned by this batch; `manager` is the live PackageManager. @@ -630,7 +631,7 @@ pub fn run_tasks( manager.task_batch.push(ThreadPoolBatch::from(queued)); } NetworkTaskCallback::Extract(extract) => { - // PORT NOTE: reshaped for borrowck — `extract` borrows + // reshaped for borrowck — `extract` borrows // `task.callback`; the body also calls `&mut self` methods on // `task` (`reset_streaming_for_retry`, // `discard_unused_streaming_state`) which only touch disjoint @@ -911,7 +912,7 @@ pub fn run_tasks( } } - // PORT NOTE: reshaped for borrowck — split nested `&mut manager`. + // reshaped for borrowck — split nested `&mut manager`. let queued = enqueue::enqueue_extract_npm_package(manager, &*extract, task_ptr); manager.task_batch.push(ThreadPoolBatch::from(queued)); } @@ -930,7 +931,7 @@ pub fn run_tasks( debug_assert!(manager.pending_task_count() > 0); } // Zig: `defer manager.preallocated_resolve_tasks.put(task);` - // PORT NOTE: raw-ptr capture — borrowck would reject overlapping `&mut` + // raw-ptr capture — borrowck would reject overlapping `&mut` // with the loop body. Guard runs on every `continue`/`?`/fallthrough. // Phase B: have the iterator yield a pool guard that puts back on Drop. // SAFETY: `task_ptr` non-null per loop guard; node exclusively owned by this batch. @@ -965,7 +966,7 @@ pub fn run_tasks( match task.tag { Task::Tag::PackageManifest => { // Zig: `defer manager.preallocated_network_tasks.put(task.request.package_manifest.network);` - // PORT NOTE: capture the `*mut NetworkTask` up front — the + // capture the `*mut NetworkTask` up front — the // `&'a mut NetworkTask` field can't be moved out through // `ManuallyDrop`'s immutable `Deref` inside the defer body. let net_ptr: *mut NetworkTask = { @@ -1050,7 +1051,7 @@ pub fn run_tasks( } Task::Tag::Extract | Task::Tag::LocalTarball => { // Zig: `defer { switch (task.tag) { .extract => preallocated_network_tasks.put(...), else => {} } }` - // PORT NOTE: capture the `*mut NetworkTask` up front (only for the + // capture the `*mut NetworkTask` up front (only for the // Extract arm) so the defer body need not move the `&mut` out // through `ManuallyDrop`'s immutable `Deref`. let net_ptr: *mut NetworkTask = if task.tag == Task::Tag::Extract { @@ -1367,7 +1368,7 @@ pub fn run_tasks( // this dependency might be something other than a git dependency! only need the name and // behavior, use the resolution from the task. let dep_id = clone.dep_id; - // PORT NOTE: reshaped for borrowck — Zig copies `dep` by + // reshaped for borrowck — Zig copies `dep` by // value. Copy the small `String` handles + behavior bit so // the `&manager.lockfile` borrow doesn't extend across the // `&mut manager` calls (`has_created_network_task`, @@ -1407,7 +1408,7 @@ pub fn run_tasks( continue; } - // PORT NOTE: reshaped for borrowck — split nested `&mut manager`. + // reshaped for borrowck — split nested `&mut manager`. let queued = enqueue::enqueue_git_checkout( manager, checkout_id, @@ -1791,7 +1792,7 @@ pub fn generate_network_task_for_tarball<'a>( return Ok(None); } - // PORT NOTE: reshaped for borrowck — Zig writes the whole struct via `.* = .{}`. + // reshaped for borrowck — Zig writes the whole struct via `.* = .{}`. // All `&mut this` uses (patch-task alloc, cache/temp dir, pool slot) happen // first; the immutable `pkg_name`/`scope` borrows are taken afterwards and // live only through `for_tarball`, leaving `this` free for the streaming @@ -1831,7 +1832,7 @@ pub fn generate_network_task_for_tarball<'a>( let temp_dir = directories::get_temporary_directory(this).handle.fd(); // Backref address only — stored, not dereffed in this function. The tag is // immediately popped by the next `this` use; that's fine for a stored - // back-pointer (TODO(port): lifetime — BACKREF). + // back-pointer. let this_backref: *mut PackageManager = this; // Take the pool slot as a raw pointer so borrowck releases `this` for the diff --git a/src/install/PackageManager/security_scanner.rs b/src/install/PackageManager/security_scanner.rs index 37a4684a6e24..a898c4fc8c3c 100644 --- a/src/install/PackageManager/security_scanner.rs +++ b/src/install/PackageManager/security_scanner.rs @@ -5,7 +5,7 @@ use std::io::Write as _; use bstr::BStr; -// PORT NOTE: `BufferedReaderParent::loop_` is typed `*mut bun_uws::Loop` (the +// `BufferedReaderParent::loop_` is typed `*mut bun_uws::Loop` (the // uws wrapper — `WindowsLoop` on Windows, `PosixLoop` on POSIX), not // `bun_io::Loop` is the trait's nominal: `us_loop_t` on POSIX, `uv_loop_t` // on Windows. The inherent `loop_()` projects `.uv_loop` from the uws wrapper @@ -72,8 +72,8 @@ pub struct SecurityScanResults { pub warn_count: usize, pub packages_scanned: usize, pub duration_ms: i64, - // TODO(port): Zig borrows this from manager.options.security_scanner; using Box<[u8]> to avoid - // a struct lifetime. Revisit if the copy matters. + // Owned copy of `manager.options.security_scanner` (Zig borrows it); + // owning avoids a struct lifetime and the copy is tiny. pub security_scanner: Box<[u8]>, } @@ -100,7 +100,6 @@ pub(crate) fn do_partial_install_of_security_scanner( security_scanner_pkg_id: PackageID, original_cwd: &[u8], ) -> Result<(), Error> { - // TODO(port): narrow error set let (workspace_filters, install_root_dependencies) = InstallWithManager::get_workspace_filters(manager, original_cwd)?; // `defer manager.allocator.free(workspace_filters)` — workspace_filters is now owned, drops at scope exit. @@ -714,7 +713,7 @@ impl<'a> JSONBuilder<'a> { json_buf.extend_from_slice(b"[\n"); let mut first = true; - // PORT NOTE: `ArrayHashMap::iterator()` takes `&mut self`, but we only + // `ArrayHashMap::iterator()` takes `&mut self`, but we only // need shared access. Iterate by index over the parallel key/value // slices instead (insertion-ordered, matches Zig's `iterator()`). let path_keys = self.collector.package_paths.keys(); @@ -849,7 +848,7 @@ fn attempt_security_scan_with_retry( let json_data = json_builder.build_package_json()?; // `defer manager.allocator.free(json_data)` — Box<[u8]> drops at scope exit. - // PORT NOTE: destructure `collector` here to release its `&PackageManager` + // destructure `collector` here to release its `&PackageManager` // borrow before constructing `SecurityScanSubprocess` (which needs `&mut`). // Only `package_paths` is read past this point. let PackageCollector { package_paths, .. } = collector; @@ -878,7 +877,7 @@ fn attempt_security_scan_with_retry( b"false" }); new_code.extend_from_slice(&temp_source[index + suppress_placeholder.len()..]); - // PORT NOTE: reshaped for borrowck — drop borrow of `code` (via `temp_source`) before reassigning. + // reshaped for borrowck — drop borrow of `code` (via `temp_source`) before reassigning. code = new_code; } @@ -902,7 +901,7 @@ fn attempt_security_scan_with_retry( scanner.spawn()?; - // PORT NOTE: Zig used a local `struct { scanner, isDone }` closure for sleepUntil. + // Zig used a local `struct { scanner, isDone }` closure for sleepUntil. // `sleep_until` now takes `*mut PackageManager` + `fn(&mut C) -> bool`; pass the // boxed scanner as the closure context and a fn pointer that probes `is_done`. fn scanner_is_done(scanner: &mut Box) -> bool { @@ -1265,7 +1264,7 @@ impl<'a> SecurityScanSubprocess<'a> { /// start the fd 4 JSON writer, and begin watching for exit. fn finish_spawn( &mut self, - // PORT NOTE: Zig `spawned: anytype` — concrete type is the platform-dependent + // Zig `spawned: anytype` — concrete type is the platform-dependent // SpawnResult; Rust uses the unified `spawn::SpawnResult`. spawned: &mut spawn::SpawnResult, ipc_read_fd: Fd, @@ -1295,7 +1294,7 @@ impl<'a> SecurityScanSubprocess<'a> { .start(ipc_read_fd, true) .map_err(|e| e.to_zig_err())?; - // PORT NOTE: `to_process` consumes `SpawnResult` by value on POSIX (and + // `to_process` consumes `SpawnResult` by value on POSIX (and // `&mut self` on Windows); take ownership of the result and let the // moved-from `*spawned` drop empty (`extra_pipes` already read). let event_loop = EventLoopHandle::from_any(&mut self.manager.event_loop); @@ -1330,7 +1329,7 @@ impl<'a> SecurityScanSubprocess<'a> { unsafe { (*parent).json_writer = Some(writer) }; // errdefer if (this.json_writer) |w| { w.source.detach(); w.deref(); this.json_writer = null; } - // PORT NOTE: guard mirrors the Zig errdefer over the FIELD (not a local), + // guard mirrors the Zig errdefer over the FIELD (not a local), // including its `if (this.json_writer)` check — `start()` may already // have re-entered and nulled it. State is the `parent` backref; disarmed // via `into_inner` on the success path. @@ -1415,7 +1414,7 @@ impl<'a> SecurityScanSubprocess<'a> { } pub fn get_read_buffer(&mut self) -> &mut [core::mem::MaybeUninit] { - // PORT NOTE: Zig returns `unusedCapacitySlice()` (uninitialized spare + // Zig returns `unusedCapacitySlice()` (uninitialized spare // capacity as `[]u8`); Rust forbids `&mut [u8]` over uninit bytes, so // expose `&mut [MaybeUninit]`. Caller (BufferedReader) only writes // into this region, never reads uninit bytes. @@ -1433,7 +1432,7 @@ impl<'a> SecurityScanSubprocess<'a> { self.exit_status = Some(status); if !self.has_received_ipc { - // PORT NOTE (intentional divergence from Zig spec): the spec tears + // Intentional divergence from Zig spec: the spec tears // down `ipc_reader` here unconditionally. That races process-exit // against fd-3-readable: `ipc_reader.start()` only registers a // poll on POSIX (no sync read), and `MiniEventLoop::tick_once` diff --git a/src/install/PackageManager/updatePackageJSONAndInstall.rs b/src/install/PackageManager/updatePackageJSONAndInstall.rs index 4ba026d30e37..10a91a3897a9 100644 --- a/src/install/PackageManager/updatePackageJSONAndInstall.rs +++ b/src/install/PackageManager/updatePackageJSONAndInstall.rs @@ -53,7 +53,7 @@ pub fn update_package_json_and_install_with_manager( } } - // PORT NOTE: `manager.options.positionals` is `&'static [&'static [u8]]` so the + // `manager.options.positionals` is `&'static [&'static [u8]]` so the // sub-slice does not borrow `*manager` and can flow alongside `&mut manager`. let positionals: &'static [&'static [u8]] = &manager.options.positionals[1..]; update_package_json_and_install_with_manager_with_updates_and_update_requests( @@ -72,10 +72,9 @@ fn update_package_json_and_install_with_manager_with_updates_and_update_requests positionals: &[&[u8]], update_requests: &mut UpdateRequestArray, ) -> Result<(), Error> { - // TODO(port): narrow error set let subcommand = manager.subcommand; if subcommand != Subcommand::PatchCommit && subcommand != Subcommand::Patch { - // PORT NOTE: reshaped for borrowck — `parse` returns a `&mut [UpdateRequest]` + // reshaped for borrowck — `parse` returns a `&mut [UpdateRequest]` // sub-slice of `update_requests`; we take its length and truncate the Vec so // the next call can take the Vec by value (Zig threaded `*[]UpdateRequest`). let len = UpdateRequest::parse( @@ -106,7 +105,7 @@ fn update_package_json_and_install_with_manager_with_updates_and_update_requests fn update_package_json_and_install_with_manager_with_updates( manager: &mut PackageManager, ctx: Command::Context, - // PORT NOTE: reshaped for borrowck — Zig was `*[]UpdateRequest`. Taking by + // reshaped for borrowck — Zig was `*[]UpdateRequest`. Taking by // value lets us hand ownership to `manager.update_requests` (which the Rust // port types as `Box<[UpdateRequest]>`) and re-borrow afterwards without // aliasing `&mut manager`. @@ -124,7 +123,7 @@ fn update_package_json_and_install_with_manager_with_updates( Global::crash(); } - // PORT NOTE: reshaped for borrowck — `get_with_path` returns `&mut MapEntry` + // reshaped for borrowck — `get_with_path` returns `&mut MapEntry` // borrowed from `manager.workspace_package_json_cache`, but we then need // `&mut *manager` for `PackageJSONEditor::edit` / `do_patch_commit` while still // holding the entry. Zig held `*MapEntry` and `*PackageManager` simultaneously @@ -165,7 +164,7 @@ fn update_package_json_and_install_with_manager_with_updates( } GetResult::Entry(entry) => core::ptr::from_mut(entry), }; - // SAFETY: see PORT NOTE above — pointer into `manager.workspace_package_json_cache`, + // SAFETY: see note above — pointer into `manager.workspace_package_json_cache`, // valid until the next `get_with_path`. No `&mut manager.workspace_package_json_cache` // is taken across this borrow; `PackageJSONEditor` and `do_patch_commit` touch only // disjoint manager fields. @@ -246,7 +245,7 @@ fn update_package_json_and_install_with_manager_with_updates( for list in LISTS { if let Some(query) = current_package_json_root.as_property(list) { if query.expr.data.is_e_object() { - // PORT NOTE: reshaped for borrowck — Zig held `data.e_object` (a + // reshaped for borrowck — Zig held `data.e_object` (a // `*E.Object`) across writes to both the inner list and the parent // object. `StoreRef` is `Copy` and derefs to a raw arena // pointer, so taking it once mirrors that exactly. @@ -254,7 +253,7 @@ fn update_package_json_and_install_with_manager_with_updates( let dependencies = e_object.properties.slice_mut(); let mut i: usize = 0; let mut new_len = dependencies.len(); - // PORT NOTE: Zig copies `dependencies[i] = dependencies[new_len - 1]` + // Zig copies `dependencies[i] = dependencies[new_len - 1]` // and iterates to the original length. `G::Property` is not `Copy` in // Rust, so we `swap` instead — but the swapped-out matched element // lands in the truncated tail and MUST NOT be revisited (it would @@ -363,7 +362,7 @@ fn update_package_json_and_install_with_manager_with_updates( manager.to_update = subcommand == Subcommand::Update; - // PORT NOTE: reshaped for borrowck — Zig stored a slice header (`manager.update_requests + // reshaped for borrowck — Zig stored a slice header (`manager.update_requests // = updates.*`) so both names alias the same backing array; the Rust field is owning // (`Box<[UpdateRequest]>`), so we transfer ownership here and re-borrow from // `manager.update_requests` after `install_with_manager` (which is the only writer). @@ -414,7 +413,7 @@ fn update_package_json_and_install_with_manager_with_updates( // must store an *owning* copy to avoid a dangling borrow. PERF(port): one extra // alloc+copy vs Zig's single dupe — profile if hot. current_package_json.source.contents = Cow::Owned(new_package_json_source.clone()); - // PORT NOTE: Zig edited `current_package_json.root` in place above; the Rust + // Zig edited `current_package_json.root` in place above; the Rust // port edited a promoted copy (`current_package_json_root`), so re-parse the // printed source so the cached AST (consumed by `FolderResolver` for workspace // members during `install_with_manager`) reflects the new dependency list. @@ -441,7 +440,7 @@ fn update_package_json_and_install_with_manager_with_updates( // The lifetime of this pointer is only valid until the next call to `getWithPath`, which can happen after this scope. // https://github.com/oven-sh/bun/issues/12288 - // PORT NOTE: reshaped for borrowck — see `current_package_json_ptr` above. + // reshaped for borrowck — see `current_package_json_ptr` above. let root_package_json_ptr: *mut MapEntry = match manager.workspace_package_json_cache.get_with_path( manager.log_mut(), @@ -530,7 +529,7 @@ fn update_package_json_and_install_with_manager_with_updates( install_with_manager::install_with_manager(manager, ctx, root_package_json_path, original_cwd)?; - // PORT NOTE: reshaped for borrowck — see assignment above. `install_with_manager` + // reshaped for borrowck — see assignment above. `install_with_manager` // is the only writer to `manager.update_requests` between the assignment and // here, so taking it back yields exactly the slice Zig observed via `updates.*`. let mut updates: Box<[UpdateRequest]> = core::mem::take(&mut manager.update_requests); @@ -755,7 +754,6 @@ pub fn update_package_json_and_install_and_cli( subcommand: Subcommand, cli: CommandLineArguments, ) -> Result<(), Error> { - // TODO(port): narrow error set let (manager_ptr, original_cwd) = 'brk: { match super::init(ctx, cli.clone(), subcommand) { Ok(v) => v, diff --git a/src/install/PackageManagerTask.rs b/src/install/PackageManagerTask.rs index 150d0b8b29e9..8451be392240 100644 --- a/src/install/PackageManagerTask.rs +++ b/src/install/PackageManagerTask.rs @@ -269,7 +269,7 @@ impl<'a> Task<'a> { // SAFETY: tag == PackageManifest discriminates the union let manifest = unsafe { &mut *this.request.package_manifest }; - // PORT NOTE: split-borrow `manifest.network` so the mutable + // split-borrow `manifest.network` so the mutable // `response_buffer` borrow doesn't overlap the immutable // `response`/`callback` reads below. let network = &mut *manifest.network; @@ -301,7 +301,7 @@ impl<'a> Task<'a> { break 'body; }; - // PORT NOTE: Zig accessed the bare-union field + // Zig accessed the bare-union field // `network.callback.package_manifest.*` directly; in Rust // `Callback` is a tagged enum, so destructure the variant. // SAFETY: tag == PackageManifest ⇒ the network task was @@ -568,7 +568,7 @@ impl<'a> Task<'a> { if let Some(mut pt) = this.apply_patch_task.take() { // `defer pt.deinit()` → Box drops at end of this block pt.apply().expect("OOM"); // bun.handleOom → panic on OOM - // PORT NOTE: Zig accessed bare-union field `pt.callback.apply`; + // Zig accessed bare-union field `pt.callback.apply`; // `apply_patch_task` is only ever populated with the Apply // variant (see `new_apply_patch_hash`), so destructure it. let crate::patch_install::Callback::Apply(apply) = &mut pt.callback else { @@ -607,7 +607,6 @@ fn read_and_extract( normalize: bool, log: &mut Log, ) -> Result { - // TODO(port): narrow error set let bytes = if normalize { // Zig `File.readFromUserInput(std.fs.cwd(), tarball_path, allocator)` resolves // a user-provided relative path against `bun.fs.FileSystem.instance.top_level_dir` @@ -647,8 +646,6 @@ pub enum Status { } /// Bare Zig `union` (untagged). Discriminated externally by `Task.tag`. -/// // TODO(port): Phase B — consider folding `Tag` + `Request` + `Data` into a -/// single Rust `enum` (one discriminant instead of tag + 2 untagged unions). pub union Data { pub package_manifest: ManuallyDrop, pub extract: ManuallyDrop, @@ -684,7 +681,7 @@ pub struct ExtractRequest<'a> { pub struct GitCloneRequest { pub name: StringOrTinyString, pub url: StringOrTinyString, - // PORT NOTE: Zig stores `DotEnv.Map` by value (handle copy of the global + // Zig stores `DotEnv.Map` by value (handle copy of the global // `Repository.shared_env`). Rust's `Map` owns its storage; store a // `&'static` into the global instead — see `SharedEnv::get`. pub env: &'static dot_env::Map, @@ -699,7 +696,7 @@ pub struct GitCheckoutRequest { pub url: StringOrTinyString, pub resolved: StringOrTinyString, pub resolution: Resolution, - // See PORT NOTE on `GitCloneRequest.env`. + // See the note on `GitCloneRequest.env`. pub env: &'static dot_env::Map, } diff --git a/src/install/PackageManifestMap.rs b/src/install/PackageManifestMap.rs index f30e0499385f..61a2bf5f09e3 100644 --- a/src/install/PackageManifestMap.rs +++ b/src/install/PackageManifestMap.rs @@ -150,11 +150,11 @@ impl PackageManifestMap { /// Zig: `byNameHashAllowExpired(this, pm: *PackageManager, ...)`. /// - /// PORT NOTE: reshaped for borrowck — Zig passes `pm` and reads - /// `pm.options.enable.*`, `pm.getCacheDirectory()`, and - /// `pm.timestamp_for_manifest_cache_control` on the disk-fallback arm. - /// Those scalars are hoisted into [`DiskCacheCtx`] so callers never hold - /// `&mut pm.manifests` and a `PackageManager` borrow simultaneously. + /// The `PackageManager` scalars read on the disk-fallback arm + /// (`options.enable.*`, the cache directory, and + /// `timestamp_for_manifest_cache_control`) are hoisted into + /// [`DiskCacheCtx`] so callers never hold `&mut pm.manifests` and a + /// `PackageManager` borrow simultaneously. pub fn by_name_hash_allow_expired( &mut self, ctx: DiskCacheCtx, @@ -180,14 +180,11 @@ impl PackageManifestMap { }; } - // PORT NOTE: reshaped for borrowck — Zig's `getOrPut` returns `{ found_existing, value_ptr }`; - // Rust splits into Occupied/Vacant arms. match self.hash_map.entry(name_hash) { Entry::Occupied(occ) => { let value_ptr = occ.into_mut(); - // PORT NOTE: reshaped for borrowck — Zig mutated `value_ptr.*` in - // place from `.manifest` to `.expired`. Compute the demote decision - // first without holding a borrow that escapes the fn. + // Compute the demote decision first without holding a borrow + // that escapes the fn. let demote = matches!( value_ptr, Value::Manifest(m) diff --git a/src/install/TarballStream.rs b/src/install/TarballStream.rs index 60528ed98ff0..f2c873d87fb6 100644 --- a/src/install/TarballStream.rs +++ b/src/install/TarballStream.rs @@ -61,7 +61,7 @@ enum Phase { Done, } -// PORT NOTE: `extract_task` / `package_manager` are raw pointers, not +// `extract_task` / `package_manager` are raw pointers, not // `&'a mut` / `&'a`. The Zig original stores `*Task` / `*PackageManager` // (freely-aliasing). This struct is heap-allocated (`heap::alloc`), // crosses threads via `drain_task`, and self-destroys in `finish()`, so a @@ -966,7 +966,7 @@ impl TarballStream { // are live raw pointers; this fn is the sole owner. After // `heap::take(this)` nothing touches `this`. unsafe { - // Fields are already raw pointers (see struct PORT NOTE), so copying + // Fields are already raw pointers (see the struct-level note), so copying // them out before `heap::take(this)` is just a pointer copy — no // reborrow of `&mut Task` is ever materialised from a stored `&mut`. let task: *mut Task = (*this).extract_task.as_mut_ptr(); @@ -1511,7 +1511,6 @@ fn apply_windows_npm_path_escapes(path: OSPathZMut) { // current index (vendor/zig/lib/std/mem.zig) before returning // `buffer[index..]`, so for `"package/index.js"` the result is `"index.js"` // (no leading `/`). -// TODO(port): Phase B — hoist into bun_str or bun_paths if reused elsewhere. fn tokenize_rest_after_first(s: &[OSPathChar]) -> &[OSPathChar] { let mut i = 0; while i < s.len() && s[i] == ('/' as OSPathChar) { diff --git a/src/install/auto_installer.rs b/src/install/auto_installer.rs index c18688447a19..d877c8f67764 100644 --- a/src/install/auto_installer.rs +++ b/src/install/auto_installer.rs @@ -202,7 +202,7 @@ impl hooks::AutoInstaller for PackageManager { // `PackageJsonView` interface so this impl does not need to name // `bun_resolver::PackageJSON` directly. - // PORT NOTE: reshaped for borrowck — `string_builder!` borrows + // Reshaped from the Zig for borrowck — `string_builder!` borrows // `self.lockfile` mutably while `dep.clone_in` needs `&mut self`. // Use a raw pointer for the disjoint reborrow (same approach as // `Package::from_package_json`). diff --git a/src/install/bin.rs b/src/install/bin.rs index 8013ec5afb16..ceadbb87ed3e 100644 --- a/src/install/bin.rs +++ b/src/install/bin.rs @@ -145,7 +145,7 @@ impl Bin { /// Zig: `Bin.clone(buf, prev_external_strings, all_extern_strings, /// extern_strings_slice, Builder, builder)`. /// - /// PORT NOTE: the Zig API takes both the full `all_extern_strings` buffer + /// the Zig API takes both the full `all_extern_strings` buffer /// and a writable tail subslice into the **same** buffer — the full slice /// is only used by `ExternalStringList::init` to compute the tail's /// offset. In Rust those two views alias (`&[T]` overlapping `&mut [T]` is @@ -247,7 +247,7 @@ impl Bin { (current_len + num_props).saturating_sub(extern_strings.len()), ) .map_err(|_| AllocError)?; - // PORT NOTE: reshaped for borrowck — Zig bumped `items.len += num_props` + // reshaped for borrowck — Zig bumped `items.len += num_props` // up-front and wrote into the spare-capacity region by raw pointer // (leaving partially-init slots on mid-loop bailout); here we push // incrementally so a bailout leaves only the slots actually written. @@ -692,7 +692,7 @@ impl<'a> NamesIterator<'a> { } } -// PORT NOTE: BACKREF — Zig stores `*const ArrayList(Dependency)` / +// BACKREF — Zig stores `*const ArrayList(Dependency)` / // `*const ArrayList(u8)` (non-exclusive). `PackageInstaller` holds a // `&mut Lockfile` alongside a `Box<[TreeContext]>` whose `binaries` queues // alias into `lockfile.buffers`; a `&'a Vec<_>` borrow here would force the @@ -731,7 +731,7 @@ impl bun_collections::PriorityCompare for PriorityQueueContext { // Min-heap keyed by `PriorityQueueContext::less_than` (string-order of dep names). pub(crate) type PriorityQueue = bun_collections::PriorityQueue; -// PORT NOTE: Zig's `Bin.PriorityQueue.Context` is an inherent associated type; +// Zig's `Bin.PriorityQueue.Context` is an inherent associated type; // `inherent_associated_types` is unstable, so callers use `Bin::PriorityQueueContext`. pub type Context = PriorityQueueContext; @@ -805,7 +805,7 @@ pub struct Linker<'a> { /// Usually will be the same as `node_modules_path`. /// Used to support native bin linking. /// - /// PORT NOTE: Zig uses `*bun.AbsPath(.{})` and intentionally aliases this + /// Zig uses `*bun.AbsPath(.{})` and intentionally aliases this /// with `node_modules_path` (the common case). A `&'a AbsPath` would /// conflict with the `&'a mut AbsPath` borrow on `node_modules_path`, so /// keep it as a raw pointer; the only read site dereferences it under a @@ -922,7 +922,7 @@ impl<'a> Linker<'a> { } if let Some(seen) = self.seen.as_deref_mut() { - // PORT NOTE: StringHashMap::get_or_put boxes the key on insert; the + // StringHashMap::get_or_put boxes the key on insert; the // Zig wrote `entry.key_ptr.* = dupe(abs_dest)` which is implicit here. let _ = seen.get_or_put(abs_dest.as_bytes()); } @@ -1021,7 +1021,7 @@ impl<'a> Linker<'a> { return; } - // PORT NOTE: reshaped for borrowck — bind the owned buffer first, then + // reshaped for borrowck — bind the owned buffer first, then // borrow `content` from it (or fall back to the stack `chunk`). let content_to_free: Box<[u8]>; let content: &[u8] = if chunk.len() >= shebang_buf.len() { @@ -1116,7 +1116,7 @@ impl<'a> Linker<'a> { abs_dest: &ZStr, global: bool, ) { - // PORT NOTE: Zig declares `var shim_buf: [65536]u8` and later + // Zig declares `var shim_buf: [65536]u8` and later // `@ptrCast(@alignCast(...))`s it to `[*]u16` inside encode_into. In Zig // `@alignCast` is a *runtime safety check* (panics in safe builds on // misalignment), but in Rust constructing a `&mut [u16]` from a pointer @@ -1158,7 +1158,7 @@ impl<'a> Linker<'a> { return; } - // PORT NOTE: borrowck — Zig's `save()`/`defer restore()` returns a + // borrowck — Zig's `save()`/`defer restore()` returns a // `ResetScope` holding `&mut Path`, which would keep // `node_modules_path` exclusively borrowed across `append()`. // Snapshot the length and restore via `set_length` after. @@ -1197,8 +1197,9 @@ impl<'a> Linker<'a> { let shebang = 'shebang: { let first_content_chunk: Option<&[u8]> = 'contents: { - // TODO(port): target.stdFile().readerStreaming(&.{}) + readVec - let read = match target.read(&mut read_in_buf) { + // Loop-fill via `read_all` so a short read can't truncate the + // shebang sniff (Zig used a streaming reader's readVec here). + let read = match target.read_all(&mut read_in_buf) { sys::Result::Ok(n) => n, sys::Result::Err(_) => break 'contents None, }; @@ -1266,7 +1267,7 @@ impl<'a> Linker<'a> { #[cfg(not(windows))] fn create_symlink(&mut self, abs_target: &ZStr, abs_dest: &ZStr, global: bool) { - // PORT NOTE: hoisted from `defer { if (this.err == null) chmod }` — scopeguard + // hoisted from `defer { if (this.err == null) chmod }` — scopeguard // cannot capture `&mut self.err` without conflicting with the body's writes, // so each return path calls `Self::chmod_on_ok` explicitly instead. @@ -1292,7 +1293,7 @@ impl<'a> Linker<'a> { return; } - // PORT NOTE: reshaped for borrowck — Zig's `var s = path.save(); + // reshaped for borrowck — Zig's `var s = path.save(); // defer s.restore();` returns a `ResetScope` holding `&mut Path`; // capture `len()` and restore via `set_length()` so the path // can be re-borrowed for `append`/`slice` in between. @@ -1336,7 +1337,7 @@ impl<'a> Linker<'a> { #[cfg(not(windows))] fn chmod_on_ok(err: Option, abs_target: &ZStr) { - // PORT NOTE: hoisted from `defer` block in create_symlink + // hoisted from `defer` block in create_symlink if err.is_none() { let mode = 0o777 & !(UMASK.load(Ordering::Acquire) as Mode); let _ = sys::lchmod(abs_target, mode); @@ -1467,7 +1468,7 @@ impl<'a> Linker<'a> { /// /// Falls through to (1) when nothing exists so the existing /// `skipped_due_to_missing_bin` retry-without-redirect path still fires. - // PORT NOTE: reshaped for borrowck — Zig took `*const Linker` but only read + // reshaped for borrowck — Zig took `*const Linker` but only read // `is_native_binlink_redirect()`. Hoist that bool to a parameter so the // caller can drop its `&self` borrow before mutably calling // `link_bin_or_create_shim`. Result borrows the threadlocal join buffer @@ -1510,7 +1511,7 @@ impl<'a> Linker<'a> { let dest_dir_without_trailing_slash = strings::without_trailing_slash(unsafe { (*self.target_node_modules_path).slice() }); - // PORT NOTE: reshaped for borrowck — track offset instead of remain.ptr arithmetic + // reshaped for borrowck — track offset instead of remain.ptr arithmetic let mut off: usize = 0; let buf = &mut *self.abs_target_buf; @@ -1531,7 +1532,7 @@ impl<'a> Linker<'a> { /// Returns the offset into `self.abs_dest_buf` where the destination dir ends /// (i.e. where the bin name should be written). - // PORT NOTE: reshaped — Zig returned a `[]u8` view (remain) into abs_dest_buf; + // reshaped — Zig returned a `[]u8` view (remain) into abs_dest_buf; // returning an offset avoids overlapping &mut borrows of self. pub fn build_destination_dir(&mut self, global: bool) -> usize { let dest_dir_without_trailing_slash = @@ -1570,7 +1571,7 @@ impl<'a> Linker<'a> { debug_assert!(self.bin.tag != Tag::None); - // PORT NOTE: reshaped for borrowck — `link_bin_or_create_shim(&mut self, ..)` + // reshaped for borrowck — `link_bin_or_create_shim(&mut self, ..)` // is called while `abs_target` / `abs_dest` borrow `self.abs_target_buf` // / `self.abs_dest_buf`. The Zig holds raw `[]u8` views (no exclusivity // implied) and `link_bin_or_create_shim` never reads or writes those two @@ -1625,7 +1626,7 @@ impl<'a> Linker<'a> { dest_off += unscoped_package_name.len(); self.abs_dest_buf[dest_off] = 0; let abs_dest_len = dest_off; - // SAFETY: abs_dest_buf[abs_dest_len] == 0 written above; see PORT NOTE. + // SAFETY: abs_dest_buf[abs_dest_len] == 0 written above; see note above. let abs_dest = ZStr::from_raw(abs_dest_buf_ptr, abs_dest_len); self.link_bin_or_create_shim( @@ -1670,7 +1671,7 @@ impl<'a> Linker<'a> { dest_off += normalized_name.len(); self.abs_dest_buf[dest_off] = 0; let abs_dest_len = dest_off; - // SAFETY: abs_dest_buf[abs_dest_len] == 0 written above; see PORT NOTE. + // SAFETY: abs_dest_buf[abs_dest_len] == 0 written above; see note above. let abs_dest = ZStr::from_raw(abs_dest_buf_ptr, abs_dest_len); self.link_bin_or_create_shim( @@ -1725,7 +1726,7 @@ impl<'a> Linker<'a> { dest_off += normalized_bin_dest.len(); self.abs_dest_buf[dest_off] = 0; let abs_dest_len = dest_off; - // SAFETY: abs_dest_buf[abs_dest_len] == 0 written above; see PORT NOTE. + // SAFETY: abs_dest_buf[abs_dest_len] == 0 written above; see note above. let abs_dest = ZStr::from_raw(abs_dest_buf_ptr, abs_dest_len); self.link_bin_or_create_shim( @@ -1808,7 +1809,7 @@ impl<'a> Linker<'a> { dest_off += entry_name.len(); self.abs_dest_buf[dest_off] = 0; let abs_dest_len = dest_off; - // SAFETY: abs_dest_buf[abs_dest_len] == 0 written above; see PORT NOTE. + // SAFETY: abs_dest_buf[abs_dest_len] == 0 written above; see note above. let abs_dest = ZStr::from_raw(abs_dest_buf_ptr, abs_dest_len); self.link_bin_or_create_shim( @@ -1832,7 +1833,7 @@ impl<'a> Linker<'a> { debug_assert!(self.bin.tag != Tag::None); - // PORT NOTE: see `link()` — detach abs_target_buf borrow via raw ptr. + // see `link()` — detach abs_target_buf borrow via raw ptr. let abs_target_buf_ptr: *const u8 = self.abs_target_buf.as_ptr(); // SAFETY: abs_target_buf is not written between here and use. let package_dir = unsafe { bun_core::ffi::slice(abs_target_buf_ptr, package_dir_len) }; diff --git a/src/install/dependency.rs b/src/install/dependency.rs index aaaf57fca8aa..7844b8d58a4e 100644 --- a/src/install/dependency.rs +++ b/src/install/dependency.rs @@ -213,7 +213,6 @@ impl DependencyExt for Dependency { buf: &[u8], builder: &mut SB, ) -> Result { - // TODO(port): narrow error set self.clone_with_different_buffers(package_manager, buf, buf, builder) } @@ -224,8 +223,7 @@ impl DependencyExt for Dependency { version_buf: &[u8], builder: &mut SB, ) -> Result { - // TODO(port): narrow error set - // PORT NOTE: reshaped for borrowck — Zig captured `out_slice` first, but + // reshaped for borrowck — Zig captured `out_slice` first, but // `append_string` may reallocate `string_bytes`, invalidating the slice. // Append first, then borrow the (now-stable) buffer. let new_literal = builder.append_string(self.version.literal.slice(version_buf)); @@ -317,7 +315,7 @@ pub trait StringBuilderLike: bun_semver::StringBuilder { fn string_bytes(&self) -> &[u8]; } -// PORT NOTE: single-impl monomorphization is intentional. Every Zig call site +// single-impl monomorphization is intentional. Every Zig call site // of `Dependency.count`/`clone`/`*WithDifferentBuffers` passes // `*Lockfile.StringBuilder` (Package.zig, OverrideMap.zig, CatalogMap.zig, // install_with_manager.zig) — `semver_string::Builder` is never used here, and @@ -649,7 +647,6 @@ impl VersionExt for Version { buf: &[u8], builder: &mut SB, ) -> Result { - // TODO(port): narrow error set Ok(Version { tag: self.tag, literal: builder.append_string(self.literal.slice(buf)), @@ -771,7 +768,7 @@ impl VersionExt for Version { // Version::Tag // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: Zig `Tag.map = bun.ComptimeStringMap(Tag, ...)`. Was a `phf::Map` +// Zig `Tag.map = bun.ComptimeStringMap(Tag, ...)`. Was a `phf::Map` // in an earlier draft; rewritten as a length-gated match (cf. 12577e958d71 // clap::find_param) — 9 entries with near-unique lengths, so a single `usize` // compare rejects almost every miss before touching bytes, and hits resolve in @@ -825,7 +822,7 @@ impl TagExt for Tag { } if strings::starts_with_windows_drive_letter_t(dependency) - // PORT NOTE: Zig `std.fs.path.isSep` — platform-native separator only + // Zig `std.fs.path.isSep` — platform-native separator only // (`/` on POSIX, `/` or `\` on Windows). NOT `isSepAny`. && { #[cfg(windows)] @@ -1049,7 +1046,7 @@ impl TagExt for Tag { url = &url[b"git@".len()..]; } - let _ = url; // PORT NOTE: Zig mutates `url` but doesn't use it after this point + let _ = url; // Zig mutates `url` but doesn't use it after this point if let Ok(Some(info)) = hosted_git_info::HostedGitInfo::from_url(dependency) { diff --git a/src/install/extract_tarball.rs b/src/install/extract_tarball.rs index 30d8e7ac138b..81bfe513a67d 100644 --- a/src/install/extract_tarball.rs +++ b/src/install/extract_tarball.rs @@ -21,8 +21,6 @@ use bun_libarchive::{ArchiveAppender, ExtractOptions}; use bun_resolver::fs::FileSystem; #[cfg(windows)] use bun_sys::FdDirExt; - -// TODO(port): narrow error set type Error = bun_core::Error; const MAX_DECOMPRESSED_TARBALL_SIZE: usize = 2 * 1024 * 1024 * 1024; @@ -509,7 +507,7 @@ impl ExtractTarball { let tmpdir = Dir::borrow(&self.temp_dir); TL_BUFS.with_borrow_mut(|bufs| { - // PORT NOTE: reshaped for borrowck — Zig grabbed a raw `*TlBufs` from TLS; + // reshaped for borrowck — Zig grabbed a raw `*TlBufs` from TLS; // here the entire body lives inside the thread_local borrow closure. let folder_name: &[u8] = match self.resolution.tag { ResolutionTag::Npm => { diff --git a/src/install/hoisted_install.rs b/src/install/hoisted_install.rs index 30e6ef9d3e8e..9fe8740d2a1d 100644 --- a/src/install/hoisted_install.rs +++ b/src/install/hoisted_install.rs @@ -56,7 +56,6 @@ impl<'a> run_tasks::RunTasksCallbacks for HoistedRunTasksCallbacks<'a> { } } -// TODO(port): narrow error set pub(crate) fn install_hoisted_packages( this: &mut PackageManager, ctx: Command::Context, @@ -67,7 +66,7 @@ pub(crate) fn install_hoisted_packages( ) -> Result { analytics::features::hoisted_bun_install.fetch_add(1, Ordering::Relaxed); - // PORT NOTE: `defer { restore buffers }` (Zig:16) — side-effecting rollback, + // `defer { restore buffers }` (Zig:16) — side-effecting rollback, // not a free. Captures `*mut PackageManager` so the guard can write back // through the same provenance root the body uses (see `mgr_ptr` below). let mgr_ptr: *mut PackageManager = this; @@ -89,7 +88,7 @@ pub(crate) fn install_hoisted_packages( .clone_from(&original_tree_dep_ids); { - // PORT NOTE: reshaped for borrowck — Zig passes `this.log, this` (two + // Reshaped for borrowck — Zig passes `this.log, this` (two // borrows of `this`). `lockfile` is `Box` so the heap object // is disjoint from the `PackageManager` struct; snapshot raw `*mut // Lockfile` and `*mut Log` first so `filter` can hold `&mut Lockfile` @@ -149,13 +148,15 @@ pub(crate) fn install_hoisted_packages( download_node = root_node.start(ProgressStrings::download(), 0); install_node = root_node.start(ProgressStrings::install(), hoisted_len); scripts_node = root_node.start(ProgressStrings::script(), 0); + // The stored pointers target stack locals in this frame; they are + // cleared by `_end_progress` below before the frame returns, so + // `scripts_node_mut()` / `downloads_node_mut()` never observe a + // dangling pointer outside this call. this.downloads_node = Some(core::ptr::addr_of_mut!(download_node)); this.scripts_node = NonNull::new(&raw mut scripts_node); - // TODO(port): storing pointers to stack locals into `this` — reshape so the - // nodes live in PackageManager or thread lifetimes through. } - // PORT NOTE: `defer { progress.root.end(); progress = .{} }` + // `defer { progress.root.end(); progress = .{} }` let _end_progress = scopeguard::guard(log_level, move |log_level| { if log_level.show_progress() { // SAFETY: `mgr_ptr` provenance — see `_restore_buffers` note. @@ -229,7 +230,7 @@ pub(crate) fn install_hoisted_packages( let mut summary = package_install::Summary::default(); { - // PORT NOTE: BACKREF — `Tree::Iterator` borrows the four buffer slices, + // BACKREF — `Tree::Iterator` borrows the four buffer slices, // and `PackageInstaller` simultaneously holds `&mut Lockfile` (plus // `&[T]` column aliases into `lockfile.packages`). Zig stores // non-exclusive `*const Lockfile` in the iterator. Snapshot raw `*const @@ -343,7 +344,7 @@ pub(crate) fn install_hoisted_packages( // so we want to make sure they're not accessible to the rest of this function // to make mistakes harder // - // PORT NOTE: BACKREF — Zig's `var parts = packages.slice()` is a + // BACKREF — Zig's `var parts = packages.slice()` is a // by-value `MultiArrayList.Slice` (raw ptr+len per column), so // `parts.items(.field)` yields independent `[]T` regardless of // mutability. The `PackageInstaller` slice fields are @@ -495,7 +496,7 @@ pub(crate) fn install_hoisted_packages( struct Closure<'a, 'b> { installer: &'a mut PackageInstaller<'b>, err: Option, - // PORT NOTE: raw `*mut` (Zig `*PackageManager`) — `sleep_until` + // raw `*mut` (Zig `*PackageManager`) — `sleep_until` // also receives this pointer, so `&mut` here would alias. manager: *mut PackageManager, } @@ -562,11 +563,11 @@ pub(crate) fn install_hoisted_packages( return Err(err); } } - // PORT NOTE: Zig `while ... else { ... }` — else runs when the condition becomes false (no break in body). + // Zig `while ... else { ... }` — else runs when the condition becomes false (no break in body). this.tick_lifecycle_scripts(); this.report_slow_lifecycle_scripts(); - // PORT NOTE: reshaped for borrowck — Zig iterates `installer.trees` + // Reshaped for borrowck — Zig iterates `installer.trees` // by value while calling `installer.installAvailablePackages` (which // also touches `installer.trees`); index instead of `.iter()` so the // immutable borrow doesn't overlap `&mut self`. @@ -595,7 +596,7 @@ pub(crate) fn install_hoisted_packages( return Err(bun_core::err!("InstallFailed")); } - // PORT NOTE: Zig copies the bitset header (`summary.* = installer.*`); + // Zig copies the bitset header (`summary.* = installer.*`); // Rust moves. `replace` with a fresh empty so `installer` stays whole // for the `link_remaining_bins` / `complete_remaining_scripts` calls // below. Route through `installer.summary` because `summary` itself is diff --git a/src/install/hosted_git_info.rs b/src/install/hosted_git_info.rs index 1a535e9ae090..658edee51e36 100644 --- a/src/install/hosted_git_info.rs +++ b/src/install/hosted_git_info.rs @@ -120,11 +120,10 @@ pub enum Representation { // HostedGitInfo // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: reshaped for borrowck. The Zig stores `committish`/`project`/`user` -// as `[]const u8` slices that alias into `_memory_buffer` (a single owned -// allocation). Rust can't express that self-reference safely without lifetimes -// on the struct. We store byte ranges into `_memory_buffer` instead and expose -// slice accessors. +// `committish`/`project`/`user` conceptually alias into `_memory_buffer` (a +// single owned allocation), but Rust can't express that self-reference safely +// without lifetimes on the struct. Store byte ranges into `_memory_buffer` +// instead and expose slice accessors. pub struct HostedGitInfo { committish: Option>, project: Range, @@ -160,7 +159,7 @@ impl HostedGitInfo { /// Therefore, we use this function to first take a URL string, encode it into a *jsc.URL and /// then decode it back to a normal string. Kind of a lot of work, but it works. /// - /// PORT NOTE: returns a `Range` into the StringBuilder's allocated buffer + /// Returns a `Range` into the StringBuilder's allocated buffer /// instead of a borrowed slice (see struct-level note). fn decode_and_append( sb: &mut StringBuilder, @@ -168,8 +167,6 @@ impl HostedGitInfo { ) -> Result, HostedGitInfoError> { let start = sb.len; let writable = sb.writable(); - // PORT NOTE: Zig `PercentEncoding.decode(Writer, writer, input)` ported via the - // fixed-buffer `decode_into(out, input) -> Result` overload in bun_url. let decoded_len = PercentEncoding::decode_into(writable, input) .map_err(|_| HostedGitInfoError::InvalidURL)? as usize; sb.len += decoded_len; @@ -236,22 +233,15 @@ impl HostedGitInfo { } } - // PORT NOTE: `pub fn deinit` → `impl Drop`. Body only freed `_memory_buffer`; - // `Box<[u8]>` drops automatically, so no explicit Drop impl is needed. - - // PORT NOTE: `pub const toJS = @import("../install_jsc/...")` deleted — // `to_js` is an extension-trait method living in `bun_install_jsc`. - // PORT NOTE: `pub const StringPair` was a Zig-nested struct; hoisted to module - // scope below (Rust forbids struct defs inside `impl`). - /// Given a URL-like (including shortcuts) string, parses it into a HostedGitInfo structure. /// The HostedGitInfo is valid only for as long as `git_url` is valid. pub fn from_url(git_url: &[u8]) -> Result, HostedGitInfoError> { // git_url_mut may carry two ownership semantics: // - It aliases `git_url`, in which case it must not be freed. // - It actually points to a new allocation, in which case it must be freed. - // PORT NOTE: modeled as Cow-like local; Drop handles the owned case. + // Modeled as a Cow-like local; Drop handles the owned case. let git_url_owned: Option>; let mut git_url_mut: &[u8] = git_url; @@ -347,12 +337,9 @@ impl HostedGitInfo { } } -// PORT NOTE: Zig nested `pub const StringPair = struct {...}` inside HostedGitInfo; -// Rust can't nest struct defs inside `impl`, so it lives at module scope but is -// re-exported through the type's namespace conceptually. pub struct StringPair { - // PORT NOTE: Zig `[]const u8` aliasing-semantics. No constructor in this module - // (consumed by install_jsc), so own the buffers — callers allocate per use. + // No constructor in this module (consumed by install_jsc), so own the + // buffers — callers allocate per use. pub save_spec: Box<[u8]>, pub fetch_spec: Option>, } @@ -380,8 +367,7 @@ impl Drop for OwnedJscUrl { } } -// PORT NOTE: anonymous return struct in Zig → named struct here. -// `url` is OWNED per LIFETIMES.tsv (jsc.URL.fromString creates; caller deinits). +// `url` is owned: `jsc.URL.fromString` creates it and the holder deinits. pub struct ParsedUrl<'a> { pub url: OwnedJscUrl, pub proto: UrlProtocol<'a>, @@ -451,8 +437,8 @@ pub enum WellDefinedProtocol { /// Buffer type for holding a protocol string with colon (e.g., "git+rsync:"). /// Sized to hold the longest protocol name plus one character for the colon. -// PORT NOTE: hoisted from `impl WellDefinedProtocol` — inherent associated types -// are unstable (E0658). +// Lives at module scope (not inside `impl WellDefinedProtocol`) because +// inherent associated types are unstable (E0658). pub(crate) type StringWithColonBuffer = [u8; WellDefinedProtocol::MAX_PROTOCOL_LENGTH + 1]; impl WellDefinedProtocol { @@ -475,7 +461,6 @@ impl WellDefinedProtocol { b"ssh" => WellDefinedProtocol::Ssh, }; - // PORT NOTE: Zig `strings.getKey(self)` did reverse lookup; provide explicit map. fn protocol_str(self) -> &'static [u8] { match self { Self::Bitbucket => b"bitbucket", @@ -509,8 +494,8 @@ impl WellDefinedProtocol { } /// Maximum length of any protocol string in the strings map (computed at compile time). - // PORT NOTE: Zig computed this with a comptime loop over `strings.kvs`. The - // longest keys ("git+https", "git+rsync", "sourcehut", "bitbucket") are 9 bytes. + // The longest keys in `STRINGS` ("git+https", "git+rsync", "sourcehut", + // "bitbucket") are 9 bytes. pub(crate) const MAX_PROTOCOL_LENGTH: usize = 9; /// Get the protocol string with colon (e.g., "https:") for a given protocol enum. @@ -631,8 +616,8 @@ pub(crate) fn is_github_shorthand(npa_str: &[u8]) -> bool { } _ => { // Implement spaceOnlyAfterHash - // PORT NOTE: match Zig std.ascii.isWhitespace exactly (includes VT 0x0B and FF 0x0C; - // Rust u8::is_ascii_whitespace excludes VT). + // The whitespace set deliberately includes VT 0x0B and FF 0x0C; + // Rust's `u8::is_ascii_whitespace` excludes VT, so spell it out. if matches!(c, b' ' | b'\t' | b'\n' | b'\r' | 0x0B | 0x0C) && pound_idx.is_none() { return false; } @@ -655,8 +640,8 @@ pub(crate) fn is_github_shorthand(npa_str: &[u8]) -> bool { // UrlProtocol / UrlProtocolPair // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: Zig `union(enum) { custom: []const u8 }` borrowed the input `npa_str`. -// Carries a BORROW_PARAM lifetime; lives only for the duration of `parse_url`. +// The `Custom` variant borrows the input `npa_str`; this type lives only for +// the duration of `parse_url`. #[derive(Debug, Clone, Copy)] pub enum UrlProtocol<'a> { WellFormed(WellDefinedProtocol), @@ -679,8 +664,6 @@ impl<'a> UrlProtocol<'a> { } } -// PORT NOTE: `url: union(enum) { managed: {buf, allocator}, unmanaged: []const u8 }` -// → enum with `Managed(Box<[u8]>)` / `Unmanaged(&'a [u8])`. Allocator dropped. pub(crate) enum UrlProtocolPairUrl<'a> { Managed(Box<[u8]>), Unmanaged(&'a [u8]), @@ -699,8 +682,6 @@ impl<'a> UrlProtocolPair<'a> { } } - // PORT NOTE: `deinit` → Drop; `Managed(Box<[u8]>)` frees automatically. - /// Given a protocol pair, create a jsc.URL if possible. May allocate, but owns its memory. fn to_url(&self) -> Option { // Ehhh.. Old IE's max path length was 2K so let's just use that. I searched for a @@ -904,8 +885,8 @@ pub(crate) fn correct_url<'a>( } if col_idx == -1 && matches!(url_proto_pair.protocol, UrlProtocol::Unknown) { - // PORT NOTE: Zig copies `url_proto_pair.url` (a tagged union) by value. Here - // we know `normalize_protocol` only ever returns `Unmanaged`, so re-borrow. + // `normalize_protocol` only ever returns `Unmanaged` here, so re-borrow + // rather than move; the `Managed` arm clones for completeness. return Ok(UrlProtocolPair { url: match &url_proto_pair.url { UrlProtocolPairUrl::Unmanaged(s) => UrlProtocolPairUrl::Unmanaged(s), @@ -985,7 +966,6 @@ impl HostProvider { /// colon or not. // PERF(port): was comptime monomorphization — profile if it shows up on a hot path fn from_shortcut(shortcut_str: &[u8], with_colon: bool) -> Option { - // PORT NOTE: Zig used `inline for (std.meta.fields(Self))` (comptime reflection). for provider in Self::ALL { let shortcut_matches = if with_colon { provider.shortcut() == shortcut_str @@ -1003,7 +983,6 @@ impl HostProvider { /// Find the appropriate host provider by its domain (e.g. "github.com"). fn from_domain(domain_str: &[u8]) -> Option { - // PORT NOTE: Zig used `inline for (std.meta.fields(Self))` (comptime reflection). Self::ALL .into_iter() .find(|&provider| provider.domain() == domain_str) @@ -1055,8 +1034,8 @@ pub struct Config { pub format_extract: formatters::extract::Type, } -// PORT NOTE: `ExtractResult` corresponds to `Config.formatters.extract.Result`. -// Reshaped to use `Range` into `_owned_buffer` (see HostedGitInfo note). +// Uses `Range` into `_owned_buffer` rather than self-referential +// slices (see the HostedGitInfo struct-level note). pub struct ExtractResult { pub user: Option>, pub project: Range, @@ -1065,8 +1044,6 @@ pub struct ExtractResult { } impl ExtractResult { - // PORT NOTE: `deinit` → Drop; `Option>` frees automatically. - /// Return the buffer which owns this Result and the allocator responsible for /// freeing it. /// @@ -1399,9 +1376,8 @@ pub mod formatters { } } - // PORT NOTE: in Zig the `committish` borrow from `fragment_utf8` is freed - // before being copied into the StringBuilder. We hold the owned fragment - // here to keep the borrow valid until copied. + // Hold the owned fragment here so the `committish` borrow stays + // valid until it's copied into the StringBuilder. let fragment_utf8; let committish: Option<&[u8]> = if type_part.is_none() { let fragment_str = OwnedString::new(url.fragment_identifier()); @@ -1682,8 +1658,8 @@ pub mod formatters { return Ok(None); }; - // PORT NOTE: Zig inlines PercentEncoding.decode here instead of calling - // decodeAndAppend (returns null instead of erroring on decode failure). + // Inline percent-decode rather than `decode_and_append`: this path + // returns None instead of erroring on decode failure. let user_slice = 'blk: { let start = sb.len; let writable = sb.writable(); @@ -1911,10 +1887,9 @@ fn configs() -> &'static EnumMap { // TestingAPIs // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE (layering): `pub const X = @import("../install_jsc/...")` aliases deleted — -// `js_parse_url` / `js_from_url` live in `bun_install_jsc` (higher tier). Re-exporting -// them here would re-introduce the install ↔ jsc cycle. Module kept as a marker so -// Zig grep for `TestingAPIs` still lands here. +// Layering: `js_parse_url` / `js_from_url` live in `bun_install_jsc` (higher +// tier). Re-exporting them here would re-introduce the install ↔ jsc cycle. +// Module kept as a marker for the original `TestingAPIs` namespace. pub mod testing_apis {} // ported from: src/install/hosted_git_info.zig diff --git a/src/install/integrity.rs b/src/install/integrity.rs index 9daf4fd081cb..dfc76014ec67 100644 --- a/src/install/integrity.rs +++ b/src/install/integrity.rs @@ -48,7 +48,6 @@ pub(crate) const DIGEST_BUF_LEN: usize = { }; impl Integrity { - // TODO(port): narrow error set (Zig: `!Integrity` inferred — only error.InvalidCharacter) pub fn parse_sha_sum(buf: &[u8]) -> Result { if buf.is_empty() { return Ok(Integrity { @@ -255,7 +254,7 @@ impl fmt::Display for Integrity { } } -// PORT NOTE: Zig `enum(u8) { ..., _ }` is non-exhaustive (any u8 is a valid bit +// Zig `enum(u8) { ..., _ }` is non-exhaustive (any u8 is a valid bit // pattern, since this is read from on-disk lockfiles). A Rust `#[repr(u8)] enum` // would be UB for unknown discriminants, so we use a transparent newtype with // associated consts instead. @@ -283,7 +282,7 @@ impl Tag { } pub fn parse(buf: &[u8]) -> (Tag, usize) { - // PORT NOTE: Zig used strings.ExactSizeMatcher(8); a byte-slice match is + // Zig used strings.ExactSizeMatcher(8); a byte-slice match is // equivalent and const-propagated. let Some(i) = strings::index_of_char(&buf[0..buf.len().min(7)], b'-') else { return (Tag::UNKNOWN, 0); diff --git a/src/install/isolated_install.rs b/src/install/isolated_install.rs index 18efbceff8db..8ace0fb6cced 100644 --- a/src/install/isolated_install.rs +++ b/src/install/isolated_install.rs @@ -226,7 +226,7 @@ pub(crate) fn install_isolated_packages( ) -> Result { analytics::features::isolated_bun_install.fetch_add(1, Ordering::Relaxed); - // PORT NOTE: reshaped for borrowck — Zig holds `*Lockfile` while also + // Reshaped for borrowck — Zig holds `*Lockfile` while also // passing `*PackageManager` (which owns it); take a raw pointer so column // borrows below don't tie up `&mut manager`. let lockfile: *mut Lockfile = &raw mut *manager.lockfile; @@ -418,7 +418,7 @@ pub(crate) fn install_isolated_packages( 'check_cycle: { // check for cycles let mut nodes_slice = nodes.slice(); - // PORT NOTE: Zig grabbed multiple mutable column views from one + // Zig grabbed multiple mutable column views from one // Slice; `split_mut()` yields disjoint `&mut [_]` per column. let store::node::NodeColumnsMut { pkg_id: node_pkg_ids, @@ -493,7 +493,7 @@ pub(crate) fn install_isolated_packages( 'dont_dedupe: { let mut nodes_slice = nodes.slice(); - // PORT NOTE: disjoint-column views via `split_mut`. + // disjoint-column views via `split_mut`. let store::node::NodeColumnsMut { nodes: node_nodes, dep_id: node_dep_ids, @@ -594,7 +594,7 @@ pub(crate) fn install_isolated_packages( string_buf, pkg_names, }; - // PORT NOTE: reshaped for borrowck — clone the dedupe peers slice + // Reshaped for borrowck — clone the dedupe peers slice // before mutating node_peers. let dedupe_peers: Vec<_> = node_peers[dedupe_node_id.get() as usize].list.clone(); @@ -640,7 +640,7 @@ pub(crate) fn install_isolated_packages( })?; let mut nodes_slice = nodes.slice(); - // PORT NOTE: disjoint-column views via `split_mut`. + // disjoint-column views via `split_mut`. let store::node::NodeColumnsMut { parent_id: node_parent_ids, dependencies: node_dependencies, @@ -967,7 +967,7 @@ pub(crate) fn install_isolated_packages( // dedupe! depend on the already created entry let mut entries = store_entries.slice(); - // PORT NOTE: disjoint-column views via `split_mut`. + // disjoint-column views via `split_mut`. let store::entry::EntryColumnsMut { dependencies: entry_dependencies, parents: entry_parents, @@ -1166,7 +1166,7 @@ pub(crate) fn install_isolated_packages( let global_store_path: Option> = if manager.options.enable.global_virtual_store() { 'global_store_path: { let mut entries = store.entries.slice(); - // PORT NOTE: disjoint-column views via `split_mut`. + // disjoint-column views via `split_mut`. let store::entry::EntryColumnsMut { entry_hash: entry_hashes, node_id: entry_node_ids, @@ -1214,7 +1214,7 @@ pub(crate) fn install_isolated_packages( while !stack.is_empty() { let top_idx = stack.len() - 1; - // PORT NOTE: reshaped for borrowck — re-borrow `top` after each + // Reshaped for borrowck — re-borrow `top` after each // potential `stack.push()` realloc. let id = stack[top_idx].id; let idx = id.get() as usize; @@ -1487,7 +1487,7 @@ pub(crate) fn install_isolated_packages( } unreachable!(); }; - // PORT NOTE: reshaped for borrowck — copy members to + // Reshaped for borrowck — copy members to // avoid holding a borrow into scc_stack while mutating. let members: Vec = scc_stack[start..].to_vec(); for &m in &members { @@ -1663,7 +1663,7 @@ pub(crate) fn install_isolated_packages( if cache_dir_path.is_empty() { break 'global_store_path None; } - // PORT NOTE: Zig allocated a `[:0]u8` via `joinAbsStringBufZ`; here + // Zig allocated a `[:0]u8` via `joinAbsStringBufZ`; here // we own a Vec with a trailing NUL so it can be re-borrowed as // a `&ZStr` for `Installer.global_store_path` below. let joined = paths::resolve_path::join_abs_string::( @@ -1709,7 +1709,7 @@ pub(crate) fn install_isolated_packages( // 2. for each entry in 'node_modules' rename into 'node_modules/.old_modules-{hex}' // 3. for each workspace 'node_modules' rename into 'node_modules/.old_modules-{hex}/old_{basename}_modules' - // PORT NOTE: Zig builds a separate `RelPath(.{.unit=.u16})` + // Zig builds a separate `RelPath(.{.unit=.u16})` // for `mkdirat` because Zig's `sys.mkdirat` on Windows takes // `[:0]const u16`. The Rust `sys::mkdirat`/`renameat` take // `&ZStr` (u8) and widen internally, so a single u8 @@ -1759,7 +1759,7 @@ pub(crate) fn install_isolated_packages( continue; } - // PORT NOTE: reshaped for borrowck — Zig `save()/restore()` + // Reshaped for borrowck — Zig `save()/restore()` // holds `*Path`; capture lengths and truncate manually so // the paths stay unborrowed across the loop body. let entry_path_save = entry_path.len(); @@ -1785,14 +1785,14 @@ pub(crate) fn install_isolated_packages( AutoRelPath::from(workspace_path.slice(&lockfile.buffers.string_bytes)) .assume_ok(); - // PORT NOTE: reshaped for borrowck — clone basename before + // Reshaped for borrowck — clone basename before // mutating `workspace_node_modules` (Zig held a slice into // the buffer across an append-with-separator). let basename = workspace_node_modules.basename().to_vec(); workspace_node_modules.append(b"node_modules").assume_ok(); - // PORT NOTE: reshaped for borrowck — capture length instead + // Reshaped for borrowck — capture length instead // of `save()` so `rename_path` stays unborrowed. let rename_path_save = rename_path.len(); rename_path @@ -1896,14 +1896,14 @@ pub(crate) fn install_isolated_packages( AutoRelPath::from(workspace_path.slice(&lockfile.buffers.string_bytes)) .assume_ok(); - // PORT NOTE: reshaped for borrowck — clone basename before + // Reshaped for borrowck — clone basename before // mutating `workspace_node_modules` (Zig held a slice into // the buffer across an append-with-separator). let basename = workspace_node_modules.basename().to_vec(); workspace_node_modules.append(b"node_modules").assume_ok(); - // PORT NOTE: reshaped for borrowck — Zig `save()/restore()` + // Reshaped for borrowck — Zig `save()/restore()` // holds a `*Path`; capture the length and truncate manually // so `rename_path` stays unborrowed between save/restore. let rename_path_save = rename_path.len(); @@ -1940,8 +1940,8 @@ pub(crate) fn install_isolated_packages( }; { - // TODO(port): Progress.Node locals are conditionally initialized in Zig; - // model with Option. + // Conditionally initialized (only when progress is shown); definite- + // initialization analysis guarantees no use before assignment. let mut download_node: ProgressNode; let mut install_node: ProgressNode = ProgressNode::default(); let mut scripts_node: ProgressNode; @@ -1977,7 +1977,7 @@ pub(crate) fn install_isolated_packages( let entry_dependencies = entries.items_dependencies(); let entry_hoisted = entries.items_hoisted(); - // PORT NOTE: reshaped for borrowck — Zig holds `*Lockfile` (mut) while + // Reshaped for borrowck — Zig holds `*Lockfile` (mut) while // also keeping immutable column slices into it. Reborrow through a // `BackRef` so `string_buf` / `pkgs` don't tie up `&mut lockfile` for // the `Installer { lockfile, .. }` move below. `BackRef` is the @@ -2002,7 +2002,7 @@ pub(crate) fn install_isolated_packages( // TODO: delete let mut seen_workspace_ids: HashMap = HashMap::default(); - // PORT NOTE: reshaped — Zig does `allocator.alloc(Task, n)` then + // Reshaped — Zig does `allocator.alloc(Task, n)` then // `task.* = .{..}` in-place, which is safe because Zig has no drop // glue and no validity invariants on uninit memory. In Rust, // `installer::Task` carries `result: Result` (Drop via `TaskError` @@ -2086,7 +2086,7 @@ pub(crate) fn install_isolated_packages( // per-statement instead. // (Drop handles installer.deinit()) - // PORT NOTE: reshaped for borrowck — Zig writes `installer: &installer` + // Reshaped for borrowck — Zig writes `installer: &installer` // into `installer.tasks[i]`; in Rust the back-pointer is taken before // the `tasks` borrow. `Task.installer` is typed // `BackRef>` (raw back-ref, no real `'static` data), @@ -2100,7 +2100,7 @@ pub(crate) fn install_isolated_packages( task.installer = installer_backref; } - // PORT NOTE: hoisted — Zig lazily calls `globalLinkDirPath()` inside + // hoisted — Zig lazily calls `globalLinkDirPath()` inside // `appendStorePath` (worker threads, via `*const Installer`). Rust // can't take `&mut PackageManager` from `&self` there, so ensure the // global link dir once on the main thread before any `.symlink` @@ -2212,7 +2212,7 @@ pub(crate) fn install_isolated_packages( | ResolutionTag::Github | ResolutionTag::LocalTarball | ResolutionTag::RemoteTarball => { - // PORT NOTE: Zig used `inline ... => |pkg_res_tag|` to monomorphize the + // Zig used `inline ... => |pkg_res_tag|` to monomorphize the // body per-tag. Rust collapses to a single arm with a runtime // `pkg_res.tag` re-match where the body branches. // PERF(port): was // comptime monomorphization — profile if hot. @@ -2282,7 +2282,7 @@ pub(crate) fn install_isolated_packages( .unwrap_or(false); } installer.append_real_store_path(&mut store_path, entry_id, installer::Which::Final); - // PORT NOTE: reshaped for borrowck — Zig `save()` returns a + // Reshaped for borrowck — Zig `save()` returns a // `ResetScope` holding `*Path`; capture the length instead so // `store_path` stays unborrowed. let scope_for_patch_tag_path = store_path.len(); @@ -2384,7 +2384,7 @@ pub(crate) fn install_isolated_packages( if matches!(patch_info, installer::PatchInfo::None) { let exists = match pkg_res_tag { ResolutionTag::Npm => { - // PORT NOTE: reshaped for borrowck — capture length + // Reshaped for borrowck — capture length // instead of `save()` so the path stays unborrowed. let cache_dir_path_save = pkg_cache_dir_subpath.len(); pkg_cache_dir_subpath.append(b"package.json").assume_ok(); diff --git a/src/install/isolated_install/FileCloner.rs b/src/install/isolated_install/FileCloner.rs index d2bfcbcbd2f7..5b02ba2dc01d 100644 --- a/src/install/isolated_install/FileCloner.rs +++ b/src/install/isolated_install/FileCloner.rs @@ -4,10 +4,10 @@ use bun_sys::{self as sys, Errno, Fd, FdDirExt, FdExt}; // macOS clonefileat only -// PORT NOTE: reshaped — Zig owns `cache_dir_subpath: bun.AutoRelPath` by struct -// copy; Rust borrows mutably so the caller's path survives a clonefile→hardlink -// fallback (`continue 'backend` in `Installer::Task::run`). The borrow must be -// `&mut` because `Path::slice_z` writes the NUL terminator into the pooled buf. +// `cache_dir_subpath` is borrowed mutably (rather than owned) so the caller's +// path survives a clonefile→hardlink fallback (`continue 'backend` in +// `Installer::Task::run`). The borrow must be `&mut` because `Path::slice_z` +// writes the NUL terminator into the pooled buf. #[allow(dead_code)] pub(crate) struct FileCloner<'a> { pub cache_dir: Fd, diff --git a/src/install/isolated_install/FileCopier.rs b/src/install/isolated_install/FileCopier.rs index e5d4a6cdca7b..bbdd7f3d43f1 100644 --- a/src/install/isolated_install/FileCopier.rs +++ b/src/install/isolated_install/FileCopier.rs @@ -153,12 +153,10 @@ impl FileCopier { _ => continue, } - // PORT NOTE: reshaped for borrowck — Zig's `var s = path.save(); - // defer s.restore();` returns a `ResetScope` that holds - // `&mut Path`, which would keep `self.src_path` / - // `self.dest_subpath` exclusively borrowed for the rest of the - // iteration. Capture the saved length and restore via - // `set_length` after the body. + // A `path.save()` ResetScope would hold `&mut Path` and keep + // `self.src_path` / `self.dest_subpath` exclusively borrowed + // for the rest of the iteration. Capture the saved length and + // restore via `set_length` after the body instead. let src_saved_len = self.src_path.len(); let _ = self.src_path.append(entry.path.as_slice()); diff --git a/src/install/isolated_install/Hardlinker.rs b/src/install/isolated_install/Hardlinker.rs index 5decd6898f6a..a30c11a748a4 100644 --- a/src/install/isolated_install/Hardlinker.rs +++ b/src/install/isolated_install/Hardlinker.rs @@ -63,10 +63,7 @@ impl Hardlinker { #[cfg(windows)] { let mut cwd_buf = bun_paths::w_path_buffer_pool::get(); - // PORT NOTE: Zig spelt `FD.cwd().getFdPathW(buf)`; the Rust `Fd` - // newtype lives in `bun_core` and has no sys-layer methods, so call - // the free fn. - // PORT NOTE: `get_fd_path_w` writes the raw `\\?\C:\...` result into + // `get_fd_path_w` writes the raw `\\?\C:\...` result into // `cwd_buf` and returns a SUB-SLICE (offset 4, or 6 for UNC) after // stripping the long-path prefix. We can't keep that slice borrowed // across the loop (borrowck vs `cwd_buf`), so capture both its start @@ -101,12 +98,11 @@ impl Hardlinker { sys::Result::Err(err) => return Ok(sys::Result::Err(err)), }; - // PORT NOTE: reshaped for borrowck — Zig's `var s = path.save(); - // defer s.restore();` returns a `ResetScope` that holds `&mut Path`, - // which would keep `self.src`/`self.dest` exclusively borrowed for - // the rest of the iteration. Capture the saved length directly and - // restore via `set_length` after the body (and before any error - // return) so the truncation happens on every exit, matching `defer`. + // A `path.save()` ResetScope would hold `&mut Path` and keep + // `self.src`/`self.dest` exclusively borrowed for the rest of + // the iteration. Capture the saved length directly and restore + // via `set_length` after the body (and before any error return) + // so the truncation happens on every exit. let src_saved_len = self.src.len(); // `OsAbsPath`/`OsPath` use `CheckLength::ASSUME`, so `append`'s // `Err(MaxPathExceeded)` arm is statically unreachable (Zig returns @@ -130,11 +126,8 @@ impl Hardlinker { // `dest` may already be absolute (global virtual store // entries live under the cache, not cwd); only prefix the // working-directory path when it's project-relative. - // PORT NOTE: borrowck — Zig held both `dest_cwd` and - // `self.dest.slice()` simultaneously; here `dest_cwd` - // borrows `cwd_buf` and `self.dest.slice()` borrows - // `self`, which is fine, but stash the dest slice once - // so the borrow doesn't span the buffer-mut below. + // Stash the dest slice once so the `&self` borrow + // doesn't span the buffer-mut below. let dest_slice: &[u16] = self.dest.slice(); let dest_parts: &[&[u16]] = if !dest_slice.is_empty() && bun_paths::Platform::Windows.is_absolute_t::(dest_slice) @@ -254,11 +247,10 @@ impl Hardlinker { sys::Result::Err(err) => return Ok(sys::Result::Err(err)), }; - // PORT NOTE: reshaped for borrowck — Zig's `var s = dest.save(); - // defer s.restore();` returns a `ResetScope` holding `&mut Path`, - // which would keep `self.dest` exclusively borrowed across the - // body. Capture `len()` and restore via `set_length()` after the - // body so the truncation runs on every exit, matching `defer`. + // A `dest.save()` ResetScope would hold `&mut Path` and keep + // `self.dest` exclusively borrowed across the body. Capture + // `len()` and restore via `set_length()` after the body so the + // truncation runs on every exit. let dest_saved_len = self.dest.len(); let _ = self.dest.append(entry.path.as_bytes()); // OOM/capacity: Zig aborts; port keeps fire-and-forget diff --git a/src/install/isolated_install/Installer.rs b/src/install/isolated_install/Installer.rs index 6f30c3c8d04e..976e6f7581ff 100644 --- a/src/install/isolated_install/Installer.rs +++ b/src/install/isolated_install/Installer.rs @@ -649,7 +649,11 @@ pub enum Result { None, Err(TaskError), Blocked, - RunScripts(*mut package::scripts::List), // TODO(port): LIFETIMES.tsv=BORROW_FIELD &'a mut package::scripts::List — kept raw for borrowck (owned by store.entries.items(.scripts)[entry_id]) + // Kept raw (semantically `&mut package::scripts::List`): the pointee is + // owned by `store.entries.items(.scripts)[entry_id]`, which outlives every + // task; threading `'a` here is blocked by the `Installer<'static>` BackRef + // the queued `Task` already carries. + RunScripts(*mut package::scripts::List), Done, } @@ -675,12 +679,16 @@ impl TaskError { TaskError::Binaries(err) => TaskError::Binaries(*err), TaskError::RunScripts(err) => TaskError::RunScripts(*err), TaskError::Patching(_log) => { - // TODO(port): `bun_ast::Log` is non-Clone; the only caller of - // `TaskError::clone()` is `Yield::failure` which never receives a - // `Patching` payload (Patching is only constructed on the main - // thread via `on_package_extracted`, never passed through the - // task-thread `Yield::Fail` path). Preserve a fresh Log so we - // don't UAF a borrowed one. + // `bun_ast::Log` is non-Clone; the only caller of + // `TaskError::clone()` is the `Result::Err(err) => err.clone()` + // task-batch drain in PackageManager/runTasks.rs, and + // `Task::result` is only set to `Result::Err` from the + // `Yield::Fail` arm in `Task::run`. No `Yield::Fail` / + // `Yield::failure` site ever constructs `TaskError::Patching` — + // it is built only for direct `on_task_fail` calls (Installer.rs + // and isolated_install.rs patch paths), so this arm is + // unreachable in practice. Preserve a fresh Log so we don't UAF + // a borrowed one. TaskError::Patching(Log::init()) } TaskError::Download(dl) => TaskError::Download(DownloadError { @@ -756,7 +764,9 @@ impl Step { pub enum Yield { Yield, - RunScripts(*mut package::scripts::List), // TODO(port): LIFETIMES.tsv=BORROW_PARAM &'a mut package::scripts::List — kept raw for borrowck (borrow of entry_scripts) + // Kept raw (semantically `&mut package::scripts::List`): borrow of + // `entry_scripts`, owned by the store entry — see `Result::RunScripts`. + RunScripts(*mut package::scripts::List), Done, Blocked, Fail(TaskError), @@ -1203,7 +1213,7 @@ impl Task { let _ = Fd::cwd().delete_tree(staging.slice()); } - // PORT NOTE: reshaped for borrowck — `defer if (cached_package_dir) |d| d.close()` + // reshaped for borrowck — `defer if (cached_package_dir) |d| d.close()` // becomes a guard that *owns* the `Option` so the loop body can reassign // through `*cached_package_dir` without an outstanding closure borrow. let mut cached_package_dir = scopeguard::guard(None::, |dir| { @@ -1218,7 +1228,7 @@ impl Task { let mut backend = InstallMethod::from_u8(installer.supported_backend.load(Ordering::Relaxed)); 'backend: loop { - // PORT NOTE: reshaped for borrowck — Zig builds `dest_subpath` once + // reshaped for borrowck — Zig builds `dest_subpath` once // before the labeled-switch and passes it by-value (struct copy) // into each backend's helper. Rust moves it, so rebuild per // iteration; this only re-runs once on an EXDEV/OPNOTSUPP retry. @@ -1491,7 +1501,7 @@ impl Task { installer.append_store_path(&mut dep_store_path, dep.entry_id); } - // PORT NOTE: reshaped for borrowck — Zig's + // reshaped for borrowck — Zig's // `const dest_save = dest.save(); defer dest_save.restore();` // can't coexist with `dest.undo()/dest.relative()` because // the `ResetScope` guard holds `&mut dest`. Capture the @@ -1826,7 +1836,7 @@ impl Task { ); } - // PORT NOTE: `target_node_modules_path` intentionally aliases + // `target_node_modules_path` intentionally aliases // `node_modules_path` in the common (no-replacement) case — // mirrors the Zig `*AbsPath` aliasing. The Linker field is a // raw `*const AbsPath` for exactly this reason. @@ -2052,7 +2062,7 @@ pub struct PatchInfoRemove { pub struct PatchInfoPatch { pub name_and_version_hash: u64, - pub patch_path: Box<[u8]>, // TODO(port): lifetime — slices into lockfile string_buf + pub patch_path: Box<[u8]>, // owned copy of the lockfile string_buf slice (Zig borrowed) pub contents_hash: u64, } @@ -2255,7 +2265,6 @@ impl<'a> Installer<'a> { &self, parent_entry_id: StoreEntryId, ) -> core::result::Result<(), bun_core::Error> { - // TODO(port): narrow error set let lockfile = self.lockfile(); let store = self.store; @@ -2330,7 +2339,7 @@ impl<'a> Installer<'a> { ); } - // PORT NOTE: see the matching note in `Step::LinkBinaries` — Zig + // see the matching note in `Step::LinkBinaries` — Zig // aliases `target_node_modules_path` with `node_modules_path` and // the Linker field is a raw `*const AbsPath` to permit that. let target_nm_ptr: *const DefaultAbsPath = match target_node_modules_path.as_ref() { @@ -2713,7 +2722,7 @@ impl<'a> Installer<'a> { buf.append(pkg_res.workspace().slice(string_buf)); } ResolutionTag::Symlink => { - // PORT NOTE: reshaped — Zig `globalLinkDirPath()` lazily ensures + // reshaped — Zig `globalLinkDirPath()` lazily ensures // the dir and mutates `*PackageManager`. `append_store_path` is // `&self` (matching Zig `*const Installer`) and may run on worker // threads, so the lazy init is hoisted to the main-thread caller diff --git a/src/install/isolated_install/Store.rs b/src/install/isolated_install/Store.rs index 3ab61e923d77..a917066af091 100644 --- a/src/install/isolated_install/Store.rs +++ b/src/install/isolated_install/Store.rs @@ -136,7 +136,7 @@ impl Store { len = parent_dedupe.len(); while i < len { - // PORT NOTE: reshaped for borrowck — capture key before mutating `parent_dedupe`. + // Capture key before mutating `parent_dedupe`. let key = parent_dedupe.keys()[i]; for &parent_id in entry_parents[key.get() as usize].as_slice() { if parent_id == entry::Id::INVALID { @@ -157,11 +157,10 @@ impl Store { // ────────────────────────────────────────────────────────────────────────── // OrderedArraySet — Zig: `fn OrderedArraySet(comptime T, comptime Ctx) type`. -// PORT NOTE: the `Ctx` type param is dropped from the struct; ctx is passed per-call as -// `&impl OrderedArraySetCtx`. In Zig the Ctx param only contributed comptime method -// lookup; in Rust the two instantiations (`Dependencies`, `Peers`) are already distinct -// via `T`. Ctx structs carry borrowed slices, so binding their lifetime into the -// container type would infect stored fields. +// The ctx is passed per-call as `&impl OrderedArraySetCtx` rather than as a +// type param on the struct; the two instantiations (`Dependencies`, `Peers`) +// are already distinct via `T`. Ctx structs carry borrowed slices, so binding +// their lifetime into the container type would infect stored fields. // ────────────────────────────────────────────────────────────────────────── pub(crate) trait OrderedArraySetCtx { fn eql(&self, l: T, r: T) -> bool; @@ -281,9 +280,9 @@ pub mod entry { // Zig default: `.empty` pub parents: Vec, // Zig default: `.init(.link_package)` - // PORT NOTE: `std.atomic.Value(Installer.Task.Step)` → `AtomicU32` storing - // the `#[repr(u8)]` discriminant. Loads/stores go through `Step as u32` / - // `Step::from_u32` (see Installer.rs); no atomic-enum wrapper exists. + // `AtomicU32` storing the `#[repr(u8)]` `Step` discriminant. Loads and + // stores go through `Step as u32` / `Step::from_u32` (see Installer.rs); + // no atomic-enum wrapper exists. pub step: core::sync::atomic::AtomicU32, // if true this entry gets symlinked to `node_modules/.bun/node_modules` @@ -302,7 +301,7 @@ pub mod entry { pub entry_hash: u64, // Zig default: `null` - // PORT NOTE: `Cell` because `Installer::Task::run` writes this slot + // `Cell` because `Installer::Task::run` writes this slot // from a task thread through `&Store` (each Task is the sole writer for // its own `entry_id`; see Installer.zig:541/1161). Without interior // mutability the only access path is `&Store → &[Option<_>]` and the @@ -474,8 +473,6 @@ pub mod entry { } pub(crate) fn debug_gather_all_parents(entry_id: Id, store: &Store) -> Vec { - // PORT NOTE: reshaped — Zig leaked the local map and returned its keys slice; - // Rust returns an owned Vec instead. let mut i: usize = 0; let mut len: usize; @@ -493,7 +490,7 @@ pub mod entry { len = parents.len(); while i < len { - // PORT NOTE: reshaped for borrowck — capture key before mutating `parents`. + // Capture key before mutating `parents`. let key = parents.keys()[i]; for &parent_id in entry_parents[key.get() as usize].as_slice() { if parent_id == Id::INVALID { diff --git a/src/install/isolated_install/Symlinker.rs b/src/install/isolated_install/Symlinker.rs index 6d7cae628975..9e64fab964da 100644 --- a/src/install/isolated_install/Symlinker.rs +++ b/src/install/isolated_install/Symlinker.rs @@ -3,20 +3,18 @@ use bun_paths; use bun_sys::{self, Errno, Fd, FdDirExt, FdExt}; pub struct Symlinker { - // TODO(port): bun.Path/RelPath/AbsPath are comptime-config generic (`.{ .sep = .auto }`); - // mapped to non-generic bun_paths types here — may need a `` param. pub dest: bun_paths::Path, pub target: bun_paths::RelPath, pub fallback_junction_target: bun_paths::AbsPath, } impl Symlinker { - // PORT NOTE: `&mut self` (vs Zig `*const`) because `Path::slice_z()` writes + // `&mut self` (vs Zig `*const`) because `Path::slice_z()` writes // the trailing NUL into its pooled buffer and so requires `&mut`. pub fn symlink(&mut self) -> bun_sys::Result<()> { #[cfg(windows)] { - // PORT NOTE: borrowck — `slice_z()` mut-borrows each path to write + // borrowck — `slice_z()` mut-borrows each path to write // the trailing NUL; bind the fallback first so all three borrows // are live disjointly when passed to `symlink_or_junction`. let fallback = self.fallback_junction_target.slice_z(); diff --git a/src/install/lib.rs b/src/install/lib.rs index 29f47ff7b4c2..bf66a31c6a01 100644 --- a/src/install/lib.rs +++ b/src/install/lib.rs @@ -229,7 +229,7 @@ pub(crate) mod install { /// `windows-shim/BinLinkingShim.zig` — `.bunx` shim encoder consumed by /// `bin::Linker` (Windows only at runtime, but the encoder types are /// referenced unconditionally so the module must exist on all targets). -// PORT NOTE: `#[path]` inside an inline `mod {}` resolves relative to the +// `#[path]` inside an inline `mod {}` resolves relative to the // synthetic `windows_shim/` directory, which doesn't exist on disk. Hoist the // file-backed module to crate level with an absolute-ish path and re-export // through the inline mod so `windows_shim::bin_linking_shim` keeps resolving. @@ -388,7 +388,10 @@ pub struct PackageJSON { #[derive(Default)] pub struct PackageJSONDependencyMap { pub map: bun_collections::ArrayHashMap, - // TODO(port): lifetime — borrows the package.json source contents + // Erased borrow of the package.json source contents (mirrors + // `bun_resolver::package_json::DependencyMap::source_buf`, which is + // likewise `'static`-erased); kept alive by the originating + // `PackageJSON::source_contents` for the lifetime of the map. pub source_buf: &'static [u8], } @@ -480,7 +483,7 @@ impl RunCommand { /// therefore re-points a stale link on EEXIST instead of trusting it. #[cfg(not(windows))] pub const BUN_NODE_DIR: &'static str = { - // PORT NOTE: Zig used comptime `++`; `const_format::concatcp!` cannot host + // Zig used comptime `++`; `const_format::concatcp!` cannot host // `if` expressions inline, so split into helper consts. use const_format::concatcp; const TMP: &str = if cfg!(target_os = "macos") { @@ -747,7 +750,7 @@ impl RunCommand { // ALREADY_EXISTS short-circuit below never reuses a stale // hardlink at a previous debug binary. // - // PORT NOTE: Zig's `@panic("huh?")` assumes the wipe always + // Zig's `@panic("huh?")` assumes the wipe always // leaves the path absent. `bun-run.test.ts` now uses // `describe.concurrent`, so multiple debug processes race on // this shared dir and `make_dir` can legitimately observe @@ -764,7 +767,7 @@ impl RunCommand { let image_path = win::exe_path_w(); for name in [strings::w!("\\node.exe\0"), strings::w!("\\bun.exe\0")] { target_path_buffer[dir_slice_len..][..name.len()].copy_from_slice(name); - // PORT NOTE: Zig held a `[]const u16` into `target_path_buffer` + // Zig held a `[]const u16` into `target_path_buffer` // across in-place mutation (the dir-NUL/backslash toggle below). // Under Stacked Borrows a `*const` derived via `Deref::deref` // is invalidated by the intervening `&mut` from `IndexMut`, so @@ -975,7 +978,7 @@ impl<'a> StorePathFormatter<'a> { impl<'a> fmt::Display for StorePathFormatter<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // PORT NOTE: `core::fmt` cannot emit non-UTF-8 bytes. The Zig spec writes raw + // `core::fmt` cannot emit non-UTF-8 bytes. The Zig spec writes raw // bytes via `writer.writeByte(c)`; routing through `to_str_lossy()` here was wrong // (it silently expanded each invalid byte to U+FFFD = 3 bytes, changing on-disk // store directory names). We now build the raw byte sequence via `write_to` and @@ -1034,7 +1037,7 @@ pub(crate) fn initialize_mini_store() { // to the allocation for its entire scope — no aliasing. Mirrors Zig's // `threadlocal var instance: ?*MiniStore` single-owner deref. let mini_store = unsafe { &mut *instance.get().unwrap() }; - // PORT NOTE: Zig checked `stack_allocator.fixed_buffer_allocator.end_index >= + // Zig checked `stack_allocator.fixed_buffer_allocator.end_index >= // buffer.len() - 1` to decide whether to recycle the heap arena. The Rust // `ASTMemoryAllocator` collapses SFA+fallback into a single bumpalo arena, // so there is no stack-buffer watermark to inspect — `reset()` already diff --git a/src/install/lifecycle_script_runner.rs b/src/install/lifecycle_script_runner.rs index 3d44a4562d09..407895ad70c0 100644 --- a/src/install/lifecycle_script_runner.rs +++ b/src/install/lifecycle_script_runner.rs @@ -21,7 +21,7 @@ use bun_spawn::SpawnResultExt as _; use bun_spawn::{Process, ProcessExit, ProcessExitKind, Rusage, SpawnOptions, Status}; #[cfg(unix)] use bun_sys::Fd; -// PORT NOTE: `BufferedReaderParent::loop_` is typed `*mut bun_uws::Loop` (the +// `BufferedReaderParent::loop_` is typed `*mut bun_uws::Loop` (the // `bun_io::Loop` is the trait's nominal: `us_loop_t` on POSIX, `uv_loop_t` // on Windows. `AnyEventLoop::native_loop()` projects through the uws wrapper // (`WindowsLoop::uv_loop`) on Windows so both paths hand back the same shape @@ -316,7 +316,7 @@ impl<'a> InstallCtx<'a> { } } -// PORT NOTE: Zig's `Intrusive(T, Context, less)` takes the comparator as a comptime +// Zig's `Intrusive(T, Context, less)` takes the comparator as a comptime // fn-pointer. The Rust `io_heap::Intrusive` folds it into `HeapContext::less` on the // `Context` type instead, so `sort_by_started_at` is provided via a trait impl on a // ZST `StartedAtCtx` (the Zig context arg `*PackageManager` is unused by `less`). @@ -452,8 +452,8 @@ impl<'a> LifecycleScriptSubprocess<'a> { let flags = bun_sys::get_fcntl_flags(fd).expect("Failed to get fcntl flags"); debug_assert!(flags & bun_sys::O::NONBLOCK as isize != 0); - let _stat = bun_sys::fstat(fd).expect("Failed to fstat"); - // TODO(port): `bun.S.ISSOCK(stat.mode)` once bun_sys exposes `S::ISSOCK`. + let stat = bun_sys::fstat(fd).expect("Failed to fstat"); + debug_assert!(bun_sys::S::ISSOCK(stat.st_mode as _)); } let _ = fd; } @@ -498,7 +498,6 @@ impl<'a> LifecycleScriptSubprocess<'a> { this: *mut Self, next_script_index: u8, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set bun_core::analytics::Features::LIFECYCLE_SCRIPTS.fetch_add(1, Ordering::Relaxed); // SAFETY: `this` is non-null and uniquely accessed (caller contract). @@ -512,7 +511,7 @@ impl<'a> LifecycleScriptSubprocess<'a> { } // errdefer { decrement alive_count; ensure_not_in_heap } - // PORT NOTE: Zig's `errdefer` is modeled by splitting the fallible body into + // Zig's `errdefer` is modeled by splitting the fallible body into // `spawn_next_script_inner` and running the cleanup on the error branch. Both // functions take the allocation-rooted `*mut Self` (mirroring Zig's // `*LifecycleScriptSubprocess` receiver) so that backrefs stored into the @@ -619,7 +618,7 @@ impl<'a> LifecycleScriptSubprocess<'a> { core::mem::size_of::<[*const c_char; 4]>() == 4 * core::mem::size_of::() ); - // PORT NOTE / OWNERSHIP: Zig allocates the libuv pipes + // OWNERSHIP: Zig allocates the libuv pipes // (`bun.new(uv.Pipe, zeroes)`), stashes the *non-owning* `*uv.Pipe` // in `this.stdout.source.?.pipe`, and reuses the same heap pointer for // `SpawnOptions.{stdout,stderr} = .{ .buffer = pipe }`. In Rust, @@ -718,7 +717,7 @@ impl<'a> LifecycleScriptSubprocess<'a> { // linked in the libuv loop's handle queue forever). Zig avoided // this by stashing the pipes in `this.{stdout,stderr}.source` // BEFORE building `SpawnOptions` (lifecycle_script_runner.zig:190); - // the Rust ordering moved allocation inline (see PORT NOTE above) + // the Rust ordering moved allocation inline (see OWNERSHIP note above) // and must therefore handle the error path explicitly. let mut spawn_options = spawn_options; spawn_options.stdout.deinit(); @@ -818,7 +817,7 @@ impl<'a> LifecycleScriptSubprocess<'a> { pub fn print_output(&mut self) { if !self.manager().options.log_level.is_verbose() { // Reuse the memory - // PORT NOTE: reshaped for borrowck — Zig evaluated all three clauses + // Reshaped for borrowck — Zig evaluated all three clauses // (`stdout.len==0 && stdout.cap>0 && stderr.buffer().cap==0`) before // the swap, holding two `*ArrayList(u8)` simultaneously. Evaluate // the stderr-capacity check first (immutable), then take the @@ -922,17 +921,15 @@ impl<'a> LifecycleScriptSubprocess<'a> { } else { // .monotonic because this is what `completeOne` does. This is the same // as `completeOne` but doesn't update the parent. - // TODO(port): Zig used `@atomicRmw(usize, &node.unprotected_completed_items, .Add, 1, .monotonic)`; - // the stub `bun_progress::Node` is non-atomic & has no parent, so the - // detached-parent path collapses to `complete_one()` until the real - // `std.Progress` port lands. - scripts_node.complete_one(); + scripts_node + .unprotected_completed_items + .fetch_add(1, Ordering::Relaxed); } } if let Some(nanos) = maybe_duration { if nanos > MIN_MILLISECONDS_TO_LOG * bun_core::time::NS_PER_MS { - // PORT NOTE: Zig passed `manager.lockfile.allocator`; allocator param + // Zig passed `manager.lockfile.allocator`; allocator param // dropped per §Allocators (non-AST crate). Zig borrowed the lockfile // string buffer for `package_name`; we own a `Box<[u8]>` that drops on // `destroy`, so the log entry takes its own owned copy. @@ -1137,7 +1134,7 @@ impl<'a> LifecycleScriptSubprocess<'a> { break 'try_delete_dir; }; let basename = bun_paths::basename(self.scripts.cwd.as_bytes()); - // PORT NOTE: Zig (lifecycle_script_runner.zig:533-534) leaks this fd + // Zig (lifecycle_script_runner.zig:533-534) leaks this fd // too — fixed here since this path returns to the install loop without // exiting, so the HANDLE/fd would otherwise persist for the rest of // the install on every failed optional-dependency lifecycle script. @@ -1161,7 +1158,6 @@ impl<'a> LifecycleScriptSubprocess<'a> { foreground: bool, ctx: Option>, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let package_name = list.package_name.clone(); let lifecycle_subprocess = Self::new(LifecycleScriptSubprocess { manager: bun_ptr::BackRef::new_mut(manager), diff --git a/src/install/lockfile.rs b/src/install/lockfile.rs index 8bf1175ef33b..cd5326100910 100644 --- a/src/install/lockfile.rs +++ b/src/install/lockfile.rs @@ -136,7 +136,9 @@ pub enum Cleaned<'a> { New(Box), } -// TODO(port): std.io.FixedBufferStream([]u8) — replace with cursor over &mut [u8] +// Zig: `std.io.FixedBufferStream([]u8)`. The Rust stream owns its backing +// `Vec` — every load path hands the file contents to the stream anyway, +// which avoids threading a `&mut [u8]` lifetime through the load call graph. pub type Stream = bun_io::FixedBufferStream>; /// Duck-typed surface that `Buffers::write_array`/`save` and @@ -497,7 +499,7 @@ impl Lockfile { pub fn load_from_dir<'a, const ATTEMPT_LOADING_FROM_OTHER_LOCKFILE: bool>( &'a mut self, dir: Fd, - manager: Option<&mut PackageManager>, + mut manager: Option<&mut PackageManager>, log: &mut bun_ast::Log, ) -> LoadResult<'a> { // Zig: `bun.assert(FileSystem.instance_loaded);` @@ -610,16 +612,70 @@ impl Lockfile { }); } - // TODO(port): borrowck — `self` is reborrowed inside `result` via &'a mut Lockfile. - // PORT NOTE: reshaped for borrowck — the debug round-trip block below mutates `self` - // through `result.ok.lockfile` which already holds the &mut. The `BUN_DEBUG_TEST_ - // TEXT_LOCKFILE` round-trip path (lockfile.zig:364-406) needs simultaneous access - // to `manager` (already moved into the call above for `Option<&mut PackageManager>`) - // and the `&mut Lockfile` inside `result`. Restoring it requires the - // `manager.as_deref_mut()` reborrow which today's `Option<&mut PackageManager>` - // surface forbids. Until reconciler-6, the debug round-trip is omitted. - // TODO(port): re-enable BUN_DEBUG_TEST_TEXT_LOCKFILE round-trip once borrowck reshape lands. - self.load_from_bytes(manager, buf, log) + let mut result = self.load_from_bytes(manager.as_deref_mut(), buf, log); + + // Zig lockfile.zig: when BUN_DEBUG_TEST_TEXT_LOCKFILE is set, convert + // the freshly loaded binary lockfile into a text lockfile in memory, + // then parse it back into a binary lockfile, so the two codepaths + // cross-check each other in tests. + if bun_core::env_var::BUN_DEBUG_TEST_TEXT_LOCKFILE + .get() + .unwrap_or(false) + { + if let (LoadResult::Ok(ok), Some(manager)) = (&mut result, manager) { + let mut writer_buf: Vec = Vec::new(); + // `save_from_binary` reads only `loaded_from_binary_lockfile()` + // from its `load_result` parameter. `result` itself cannot be + // passed while `ok.lockfile` is mutably borrowed (Zig aliases + // both freely), so hand it a stand-in that answers + // `format == Binary` the same way the real `Ok` result does. + let binary_origin = LoadResult::Err(LoadResultErr { + step: LoadStep::ParseFile, + value: err!("DebugTextLockfileRoundTrip"), + lockfile_path: zstr!("bun.lockb"), + format: LockfileFormat::Binary, + }); + if let Err(e) = TextLockfile::Stringifier::save_from_binary( + &mut *ok.lockfile, + &binary_origin, + &manager.options, + &mut writer_buf, + ) { + Output::panic(format_args!( + "failed to convert binary lockfile to text lockfile: {}", + e.name() + )); + } + + let source = bun_ast::Source::init_path_string(b"bun.lock", writer_buf.as_slice()); + initialize_store(); + let bump = bun_alloc::Arena::new(); + let json = match JSON::parse_package_json_utf8(&source, log, &bump) { + Ok(j) => j, + Err(e) => Output::panic(format_args!( + "failed to print valid json from binary lockfile: {}", + e.name() + )), + }; + + if let Err(e) = TextLockfile::parse_into_binary_lockfile( + &mut *ok.lockfile, + json, + &source, + log, + Some(manager), + ) { + Output::panic(format_args!( + "failed to parse text lockfile converted from binary lockfile: {}", + <&'static str>::from(e) + )); + } + + bun_core::analytics::Features::text_lockfile_inc(); + } + } + + result } pub fn load_from_bytes<'a>( @@ -696,7 +752,6 @@ impl Lockfile { exact_versions: bool, log_level: LogLevel, ) -> Result, BunError> { - // TODO(port): narrow error set let old_packages = old.packages.slice(); let old_dependencies_lists = old_packages.items_dependencies(); let old_resolutions_lists = old_packages.items_resolutions(); @@ -747,7 +802,6 @@ impl Lockfile { updates: &mut [UpdateRequest], exact_versions: bool, ) -> Result<(), BunError> { - // TODO(port): narrow error set let workspace_package_id = manager .root_package_id .get(old, manager.workspace_name_hash); @@ -755,7 +809,7 @@ impl Lockfile { old.packages.items_dependencies()[workspace_package_id as usize]; if (root_deps_list.off as usize) < old.buffers.dependencies.len() { - // PORT NOTE: split-borrow — `string_builder!` only takes + // Split-borrow: `string_builder!` only takes // `old.buffers.string_bytes` + `old.string_pool`, leaving // `old.packages` / `old.buffers.{dependencies,resolutions}` free. let mut string_builder = string_builder!(old); @@ -787,7 +841,7 @@ impl Lockfile { // TODO(dylan-conway): this will need to handle updating dependencies (exact, ^, or ~) and aliases - // PORT NOTE: Zig's `switch (exact_versions) { else => |exact| ... }` is just a + // Zig's `switch (exact_versions) { else => |exact| ... }` is just a // way to capture a comptime-ish bool; in Rust we use it directly. let npm_ver = res.npm().version; let len = bun_core::fmt::count(format_args!( @@ -896,7 +950,6 @@ impl Lockfile { exact_versions: bool, log_level: LogLevel, ) -> Result, BunError> { - // TODO(port): narrow error set // This is wasteful, but we rarely log anything so it's fine. let mut log = bun_ast::Log::init(); // defer { for (...) item.deinit(); log.deinit(); } — handled by Drop @@ -1001,7 +1054,6 @@ impl Lockfile { exact_versions: bool, log_level: LogLevel, ) -> Result, BunError> { - // TODO(port): narrow error set // Zig names the receiver `old`; alias `self` so the body reads // identically to the spec (lockfile.zig:637). let old: &mut Lockfile = self; @@ -1053,7 +1105,7 @@ impl Lockfile { old.scratch.dependency_list_queue.discard(queued); { - // PORT NOTE: reshaped for borrowck. Zig holds `&old.overrides` / + // Reshaped for borrowck. Zig holds `&old.overrides` / // `&old.catalogs` while also passing `*Lockfile old` and // `*Lockfile new` (the latter aliased again inside `builder`). // The Rust signatures take `&Lockfile` for `old` and read `new` @@ -1074,7 +1126,7 @@ impl Lockfile { let mut package_id_mapping = vec![invalid_package_id; old.packages.len()]; let clone_queue_ = PendingResolutions::new(); - // PORT NOTE: explicit `&mut *` reborrows so `old`/`manager`/`new` are + // Explicit `&mut *` reborrows so `old`/`manager`/`new` are // released back to this scope once `cloner` is dropped. let mut cloner = Cloner { old: &mut *old, @@ -1091,7 +1143,7 @@ impl Lockfile { // try clone_queue.ensureUnusedCapacity(root.dependencies.len); let _ = root.clone(&mut cloner)?; - // PORT NOTE: between here and `cloner.flush()`, `old`/`new`/`manager` + // Between here and `cloner.flush()`, `old`/`new`/`manager` // are live inside `cloner`. Reach them via `cloner.old` / // `cloner.lockfile` so borrowck sees disjoint field paths. { @@ -1109,7 +1161,7 @@ impl Lockfile { let mut workspace_paths_builder = string_builder!(new); // Sort by name for determinism - // PORT NOTE: Zig defines a local `WorkspacePathSorter` struct; in Rust we use a closure. + // Zig defines a local `WorkspacePathSorter` struct; in Rust we use a closure. { let string_buf = old.buffers.string_bytes.as_slice(); // `ArrayHashMap::sort` mirrors Zig's `entries.sort(ctx)` — @@ -1328,8 +1380,9 @@ impl<'a> fmt::Display for MetaHashFormatter<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let remain: &[u8] = &self.meta_hash[..]; - // {X}-{x}-{X}-{x} — Zig's `{X}` on a slice prints uppercase hex, `{x}` lowercase. - // TODO(port): verify byte-slice hex formatting matches Zig std.fmt exactly. + // {X}-{x}-{X}-{x} — Zig's `{X}` on a byte slice prints each byte as two + // uppercase hex digits with no separators, `{x}` lowercase; `HexBytes` + // emits the identical encoding (two digits per byte, contiguous). write!( f, "{}-{}-{}-{}", @@ -1434,7 +1487,7 @@ impl Lockfile { pub fn hoist( &mut self, log: &mut bun_ast::Log, - // PORT NOTE: Zig used `comptime method` to make these params `void` for + // Zig used `comptime method` to make these params `void` for // non-`.filter` builds. `tree::Builder` stores them unconditionally // (Option/slice), so accept the concrete shapes for all `METHOD`s. manager: Option<&PackageManager>, @@ -1444,7 +1497,7 @@ impl Lockfile { ) -> Result<(), tree::SubtreeError> { let slice = self.packages.slice(); - // PORT NOTE: `tree::Builder` stores `lockfile: ParentRef` so + // `tree::Builder` stores `lockfile: ParentRef` so // the `&mut buffers.resolutions` split-borrow below can coexist with // the read-only lockfile view inside the builder (see Tree.rs note). // `ParentRef::new` captures `SharedReadOnly` provenance from `&*self`, @@ -1466,17 +1519,18 @@ impl Lockfile { list: Default::default(), sort_buf: Default::default(), }; - // TODO(port): Tree::Builder field set may differ; verify against the Zig. Tree::default().process_subtree(tree::ROOT_DEP_ID, tree::INVALID_ID, &mut builder)?; // This goes breadth-first while let Some(item) = builder.queue.read_item() { use tree::BuilderEntryColumns as _; - // PORT NOTE: reshaped for borrowck — Zig indexes builder.list while passing &mut builder. + // Zig indexes `builder.list` in-place while passing `&mut builder`; + // copy the element instead. `process_subtree` only reads `this.id` + // (before appending to `builder.list`, which may reallocate), so the + // copy is equivalent — and avoids the dangling-element hazard. let tree = builder.list.items_tree()[item.tree_id as usize]; tree.process_subtree(item.dependency_id, item.hoist_root_id, &mut builder)?; - // TODO(port): `tree` may need to be a reference into builder.list, not a copy. } let cleaned = builder.clean()?; @@ -1516,7 +1570,7 @@ impl Lockfile { } let cache_ctx = manager.manifest_disk_cache_ctx(); - // PORT NOTE: heavy borrowck overlap — Zig calls + // Heavy borrowck overlap — Zig calls // `manager.manifests.byNameHash(manager, …)` (manifests is a field of // manager) and then opens a `string_builder` on `manager.lockfile` // while still holding `&manifest`. Route the `manifests` projection @@ -1532,7 +1586,7 @@ impl Lockfile { let mut pkgs = self.packages.slice(); let len = pkgs.len(); - // PORT NOTE: Zig takes `pkgs.items(.bin)` / `pkgs.items(.meta)` as + // Zig takes `pkgs.items(.bin)` / `pkgs.items(.meta)` as // simultaneous mutable column views; `split_mut()` yields disjoint // `&mut [_]` per column from one `&mut Slice` borrow. let self::package::PackageColumnsMut { @@ -1543,7 +1597,7 @@ impl Lockfile { meta, .. } = pkgs.split_mut(); - // PORT NOTE: Zig has two near-identical loops gated by `update_os_cpu`; + // Zig has two near-identical loops gated by `update_os_cpu`; // collapse to one loop and bind `pkg_metas` as an empty slice when the // const generic is false (Zig left it `undefined`). let pkg_metas: &mut [self::package::meta::Meta] = @@ -1581,7 +1635,7 @@ impl Lockfile { continue; }; - // PORT NOTE: Zig opens the builder on `manager.lockfile`, + // Zig opens the builder on `manager.lockfile`, // which is the same `*Lockfile` as `this`/`self`. Re-deriving a // whole `&mut Lockfile` from `manager_ptr` here would create a // second mutable reference aliasing `self` (UB) — go through @@ -1613,7 +1667,7 @@ impl Lockfile { ); let new_len = extern_strings_list.len(); - // PORT NOTE: Zig passes both `extern_strings_list.items` (full slice) + // Zig passes both `extern_strings_list.items` (full slice) // and a tail subslice to `bin.clone()`; the full slice is only used to // compute the tail's offset for `ExternalStringList::init`. In Rust the // two views would alias, so `Bin::clone_with_buffers` takes the offset @@ -1677,7 +1731,6 @@ impl<'a> Printer<'a> { input_lockfile_path: &[u8], format: PrinterFormat, ) -> Result<(), BunError> { - // TODO(port): narrow error set // We truncate longer than allowed paths. We should probably throw an error instead. let path = &input_lockfile_path[..input_lockfile_path.len().min(MAX_PATH_BYTES)]; @@ -1701,7 +1754,7 @@ impl<'a> Printer<'a> { // returns the length written into the caller-owned buffer. let cwd_len = bun_sys::getcwd(&mut lockfile_path_buf1[..])?; let parts = [path]; - // PORT NOTE: reshaped for borrowck — copy `cwd` out of `buf1` so the + // Copy `cwd` out of `buf1` so the // join can write into `buf2` while `cwd` borrows `buf1` only. let cwd = &lockfile_path_buf1[..cwd_len]; let lockfile_path__len = resolve_path::join_abs_string_buf::( @@ -1803,7 +1856,6 @@ impl<'a> Printer<'a> { format: PrinterFormat, writer: W, ) -> Result<(), BunError> { - // TODO(port): narrow error set // `FileSystem::init` ran in the caller (`Printer::print`); this is the // process-static singleton (Zig `&FileSystem.instance`). Single-threaded // CLI path, no concurrent access. @@ -1813,7 +1865,7 @@ impl<'a> Printer<'a> { ..Default::default() }; - // PORT NOTE: reshaped for borrowck — capture the `'static` cwd slice + // Capture the `'static` cwd slice // before borrowing `fs.fs` mutably. let top_level_dir = fs.top_level_dir; let entries_option = fs.fs.read_directory(top_level_dir, None, 0, true)?; @@ -1869,7 +1921,6 @@ impl<'a> Printer<'a> { impl Lockfile { pub fn verify_data(&self) -> Result<(), BunError> { - // TODO(port): narrow error set debug_assert!(self.format == FormatVersion::current()); let mut i: usize = 0; while i < self.packages.len() { @@ -1991,7 +2042,6 @@ impl Lockfile { let written = start_len - cursor.len(); ZStr::from_buf(&tmpname_buf, written - 1) }; - // TODO(port): Zig `{x}` on `&[8]u8` formats as lowercase hex of bytes; verify HexBytes matches. let file = match File::openat(Fd::cwd(), tmpname, sys::O::CREAT | sys::O::WRONLY, 0o777) { sys::Result::Err(e) => { @@ -2058,7 +2108,7 @@ impl Lockfile { #[inline] pub fn str<'a, T: bun_semver::Slicable>(&'a self, slicable: &'a T) -> &'a [u8] { - // PORT NOTE: Zig had compile-time guards rejecting by-value String/ExternalString. + // Zig had compile-time guards rejecting by-value String/ExternalString. // In Rust we just take &T; the temporary-pointer hazard does not exist. slicable.slice(self.buffers.string_bytes.as_slice()) } @@ -2256,7 +2306,7 @@ impl Lockfile { /// Appends `pkg` to `this.packages`, and adds to `this.package_index`. /// - /// PORT NOTE: Zig takes `string_buf: []const u8` as a separate parameter + /// Zig takes `string_buf: []const u8` as a separate parameter /// (always `lockfile.buffers.string_bytes.items`). In Rust that aliases the /// `&mut self` borrow, so read it from `self` and split borrows at the /// field level (`package_index` / `packages` / `buffers.string_bytes` are @@ -2488,7 +2538,6 @@ impl Lockfile { bytes: &mut self.buffers.string_bytes, pool: &mut self.string_pool, } - // TODO(port): String.Buf API in bun_semver — Zig also passed `allocator`. } } @@ -2551,7 +2600,7 @@ pub struct LockfileFields<'a> { // StringBuilder // ──────────────────────────────────────────────────────────────────────────── -/// PORT NOTE: Zig stored `lockfile: *Lockfile` and reached `.buffers.string_bytes` +/// Zig stored `lockfile: *Lockfile` and reached `.buffers.string_bytes` /// / `.string_pool` through it. In Rust that coarse `&mut Lockfile` borrow /// blocks every caller from touching disjoint fields (`packages`, `buffers /// .dependencies`, …) while a builder is alive. Hold the two fields the @@ -2767,8 +2816,9 @@ impl<'a> bun_semver::StringBuilder for StringBuilder<'a> { pub mod package_index { use super::*; + // Zig: `std.HashMap(.., .., IdentityContext, 80)` — `bun_collections::HashMap` + // hard-codes the same 80% max load factor. pub type Map = BunHashMap>; - // TODO(port): Zig uses load factor 80; bun_collections::HashMap should match. #[repr(u8)] pub enum Tag { @@ -3032,7 +3082,6 @@ impl Lockfile { print_name_version_string: bool, packages_len: usize, ) -> Result { - // TODO(port): narrow error set let previous_meta_hash = self.meta_hash; self.meta_hash = self.generate_meta_hash(print_name_version_string, packages_len)?; Ok(!strings::eql_long( @@ -3047,7 +3096,6 @@ impl Lockfile { print_name_version_string: bool, packages_len: usize, ) -> Result { - // TODO(port): narrow error set if packages_len <= 1 { return Ok(ZERO_HASH); } @@ -3066,7 +3114,7 @@ impl Lockfile { let mut i: usize = 1; while i + 16 < packages_len { - // PORT NOTE: Zig used `inline while` to unroll 16 iterations. Plain loop here. + // Zig used `inline while` to unroll 16 iterations. Plain loop here. // PERF(port): was comptime-unrolled inner loop — profile if it shows up on a hot path. for j in 0..16usize { alphabetized_names[(i + j) - 1] = (i + j) as PackageID; // @truncate diff --git a/src/install/lockfile/Buffers.rs b/src/install/lockfile/Buffers.rs index fc6460e8e7a6..20d98b82ca70 100644 --- a/src/install/lockfile/Buffers.rs +++ b/src/install/lockfile/Buffers.rs @@ -4,9 +4,9 @@ use bun_collections::DynamicBitSet as Bitset; #[cfg(debug_assertions)] use bun_core::strings; -// PORT NOTE: `use super::{self as lockfile, ...}` and bare `use super as lockfile;` -// are both rejected by rustc (E0432: "no `super` in the root" — rust-lang/rust#48067), -// so the parent-module alias is spelled via its crate path instead. +// `use super::{self as lockfile, ...}` and bare `use super as lockfile;` are both +// rejected by rustc (E0432: "no `super` in the root" — rust-lang/rust#48067), so the +// parent-module alias is spelled via its crate path instead. use super::{ DependencyIDList, DependencyList, ExternalStringBuffer, Lockfile, PackageIDList, Stream, StringBuffer, Tree, assert_no_uninitialized_padding, tree, @@ -30,12 +30,11 @@ pub struct Buffers { pub string_bytes: StringBuffer, } -// PORT NOTE: Zig `deinit` only freed owned ArrayListUnmanaged fields; in Rust the -// Vec-backed field types drop automatically, so no explicit `Drop` impl is needed. +// Zig `deinit` only freed owned ArrayListUnmanaged fields; in Rust the Vec-backed +// field types drop automatically, so no explicit `Drop` impl is needed. impl Buffers { pub fn preallocate(&mut self, that: &Buffers) -> Result<(), bun_alloc::AllocError> { - // TODO(port): narrow error set self.trees .reserve(that.trees.len().saturating_sub(self.trees.len())); self.resolutions.reserve( @@ -65,7 +64,7 @@ impl Buffers { // ────────────────────────────────────────────────────────────────────────── // `sizes` — comptime field-order table // -// PORT NOTE: the Zig computed this with `std.meta.fields` + an insertion sort +// The Zig computed this with `std.meta.fields` + an insertion sort // by descending `@alignOf(field.type)`. Every field is an `ArrayListUnmanaged`, // whose alignment is `@alignOf(usize)`, so the stable sort is a no-op and the // result is declaration order. We hard-code that order here. `types[0]` (used @@ -87,12 +86,10 @@ mod sizes { const _: () = assert!(ALIGN_TYPE_0 == align_of::<&[Tree]>()); // `sizes.bytes` was never read in the Zig; omitted. - // TODO(port): if another file reads `Buffers.sizes.bytes`, add it back. } pub fn read_array(stream: &mut Stream) -> Result, bun_core::Error> { - // TODO(port): narrow error set (CorruptLockfile | OOM) - // PORT NOTE: Zig went through `stream.reader()`; `FixedBufferStream` exposes + // Zig went through `stream.reader()`; `FixedBufferStream` exposes // `read_int_le` directly, so the intermediate reader handle is elided. let start_pos = stream.read_int_le::()?; @@ -172,20 +169,23 @@ pub fn write_array( prefix: &'static str, ) -> Result<(), bun_core::Error> where - // PORT NOTE: Zig threaded a separate `stream` (anytype) and `writer` over the + // Zig threaded a separate `stream` (anytype) and `writer` over the // same buffer. Two `&mut` to one object is UB in Rust regardless of access // order, so the port collapses both roles onto one type — `StreamType` impls // both `PositionalStream` (get_pos/pwrite) and `bun_io::Write` (append). S: lockfile::PositionalStream + bun_io::Write, - // TODO(port): narrow error set { - // TODO(port): comptime `assertNoUninitializedPadding(@TypeOf(array))` — needs - // a const-eval padding check on `T`; could add a `const _: () = assert!(...)` - // per call site or a `NoPadding` marker trait. + // Zig's comptime `assertNoUninitializedPadding(@TypeOf(array))`. In Rust this + // call is a zero-cost intent marker only — it carries no trait bound (see the + // doc comment on `assert_no_uninitialized_padding`). The actual compile-time + // enforcement is the per-type `const` field-offset asserts and the + // `layout_asserts` size/align pins in src/install/padding_checker.rs; any new + // `T` serialized through here must be added to that audit. assert_no_uninitialized_padding(array); - // SAFETY: `T` has no uninitialized padding (asserted above in Zig); reading - // its bytes is sound. Matches `std.mem.sliceAsBytes`. + // SAFETY: `T` has no uninitialized padding (audited via the per-type layout + // asserts in padding_checker.rs); reading its bytes is sound. Matches + // `std.mem.sliceAsBytes`. let bytes: &[u8] = unsafe { bun_core::ffi::slice(array.as_ptr().cast::(), core::mem::size_of_val(array)) }; @@ -193,7 +193,7 @@ where stream.write_int_le::(0xDEAD_BEEF)?; stream.write_int_le::(0xDEAD_BEEF)?; - // PORT NOTE: Zig built this with `std.fmt.comptimePrint` over + // Zig built this with `std.fmt.comptimePrint` over // `@typeName/@sizeOf/@alignOf(std.meta.Child(ArrayList))`. The reader skips // this prefix by absolute offset so it is semantically inert, but we emit the // exact bytes Zig produces so that re-saving an unchanged lockfile is a byte @@ -236,13 +236,13 @@ pub fn save( stream: &mut S, ) -> Result<(), bun_core::Error> where - // PORT NOTE: see `write_array` — Zig's separate stream/writer aliased one - // buffer; collapsed to a single bound to avoid two `&mut` to the same object. + // See `write_array` — Zig's separate stream/writer aliased one buffer; + // collapsed to a single bound to avoid two `&mut` to the same object. S: lockfile::PositionalStream + bun_io::Write, { let buffers = &lockfile.buffers; - // PORT NOTE: Zig used `inline for (sizes.names) |name|` + `@field(buffers, name)`. + // Zig used `inline for (sizes.names) |name|` + `@field(buffers, name)`. // Rust has no field-name reflection, so the loop is unrolled in declaration // order (see `sizes` module note — the comptime sort was a no-op). @@ -251,7 +251,7 @@ where if options.log_level.is_verbose() { bun_core::pretty_errorln!("Saving {} {}", buffers.$field.len(), $name); } - // PORT NOTE: the Zig had `if (comptime Type == Tree)` here, but `Type` + // The Zig had `if (comptime Type == Tree)` here, but `Type` // was `@TypeOf(list.items)` i.e. `[]Elem`, never `Tree`, so that arm // was dead. We port only the live `else` arm. // We duplicate it here so that alignment bytes are zeroed out @@ -271,7 +271,7 @@ where if options.log_level.is_verbose() { bun_core::pretty_errorln!("Saving {} {}", buffers.trees.len(), "trees"); } - // PORT NOTE: Zig's `if (comptime Type == Tree)` arm (Buffers.zig:248) + // Zig's `if (comptime Type == Tree)` arm (Buffers.zig:248) // never fires because `Type` is `[]Tree`, so Zig writes raw `Tree` // bytes — which works only because Zig's auto-layout for `Tree` happens // to match the `[id|dep_id|parent|off|len]` encoding that `load` @@ -287,7 +287,7 @@ where stream, clone.as_slice(), // Verbatim Zig `@typeName(Tree)` output. Zig writes raw `Tree` (the - // `Type == Tree` branch is dead — see PORT NOTE above), so it reports + // `Type == Tree` branch is dead — see comment above), so it reports // `4 alignof` even though we serialize `tree::External` (`[u8;20]`, // align 1). The reader ignores this string; we match Zig's bytes. "\n 20 sizeof, 4 alignof\n", @@ -415,8 +415,8 @@ impl Buffers { 0 => return Ok(tree::ROOT_DEP_ID), id if id == invalid_package_id => return Ok(invalid_package_id), _ => { - // PORT NOTE: reshaped for borrowck — `dependency_visited` is - // captured once outside the loop instead of re-matched per iter. + // `dependency_visited` is captured once outside the loop + // instead of re-matched per iteration (borrowck). let mut visited = dependency_visited; for (dep_id, &pkg_id) in self.resolutions.iter().enumerate() { if pkg_id == package_id { @@ -443,7 +443,7 @@ pub(crate) fn load( let mut this = Buffers::default(); let external_dependency_list_: Vec; - // PORT NOTE: Zig `inline for (sizes.names)` unrolled — see `sizes` module note. + // Zig `inline for (sizes.names)` unrolled — see `sizes` module note. macro_rules! load_generic_field { ($field:ident, $name:literal, $elem:ty) => {{ @@ -467,7 +467,7 @@ pub(crate) fn load( let _pos: usize = stream.pos; let tree_list: Vec = read_array(stream)?; - // PORT NOTE: Zig did `initCapacity` + `items.len = N` + write each slot. + // Zig did `initCapacity` + `items.len = N` + write each slot. // In Rust, `set_len` then `iter_mut()` would form `&mut Tree` to // uninitialized memory (UB), so we push into the reserved capacity // instead — same allocation pattern, no uninit reads. @@ -518,10 +518,7 @@ pub(crate) fn load( buffer: string_buf, package_manager: pm_, }; - // TODO(port): `Dependency::Context` borrows `log`, `string_buf`, and `pm_` - // simultaneously with `&mut this`; may need to restructure borrows. - - // PORT NOTE: Zig did `expandToCapacity` + `items.len = N` then wrote each + // Zig did `expandToCapacity` + `items.len = N` then wrote each // slot via `*dep = ...`. In Rust, `set_len` then `as_mut_slice()` would form // `&mut Dependency` to uninitialized memory (UB even when write-only), so we // push into the reserved capacity instead — same single allocation, same @@ -535,7 +532,7 @@ pub(crate) fn load( // Legacy tree structure stores package IDs instead of dependency IDs if !this.trees.is_empty() && this.trees[0].dependency_id != tree::ROOT_DEP_ID { let mut visited = Bitset::init_empty(this.dependencies.len())?; - // PORT NOTE: reshaped for borrowck — iterate by index so + // Iterate by index so // `legacy_package_to_dependency_id` can borrow `&self` while we hold // `&mut this.trees[i]`. for i in 0..this.trees.len() { diff --git a/src/install/lockfile/CatalogMap.rs b/src/install/lockfile/CatalogMap.rs index c364a746e3a1..318a4f0435ad 100644 --- a/src/install/lockfile/CatalogMap.rs +++ b/src/install/lockfile/CatalogMap.rs @@ -7,11 +7,11 @@ use bun_collections::array_hash_map::ArrayHashAdapter; use bun_install::dependency::DependencyExt as _; use bun_install::lockfile::{Buffers, StringBuilder}; use bun_install::{Dependency, Lockfile, PackageManager}; -// PORT NOTE (layering): Zig `bun.ast.Expr` resolves to the full T4 parser AST, -// but every install-side caller (Package.rs / pnpm.rs) parses JSON/YAML into -// the lower-tier `bun_ast::js_ast` shape (re-exported via `crate::bun_json`). -// Importing `bun_js_parser` here forced a higher-tier dep and produced -// distinct-`Expr`-type errors at every call site, so use the T2 type directly. +// Layering: every install-side caller (Package.rs / pnpm.rs) parses JSON/YAML +// into the lower-tier `bun_ast::js_ast` shape (re-exported via +// `crate::bun_json`). Importing `bun_js_parser` here would force a higher-tier +// dep and produce distinct-`Expr`-type errors at every call site, so use the +// T2 type directly. use crate::bun_json::{E, Expr, ExprData}; use bun_ast::{Log, Source}; use bun_semver::String; @@ -68,8 +68,8 @@ impl CatalogMap { group.get_adapted(&dep_name, &ctx(buf)).cloned() } - /// PORT NOTE: Zig took `lockfile: *Lockfile` but only reads its string - /// buffer for the hash context. Narrow to `buf: &[u8]` so callers can hold + /// Takes `buf: &[u8]` (the lockfile's string buffer, used for the hash + /// context) rather than the whole `Lockfile` so callers can hold /// `&mut lockfile.catalogs` while only borrowing `buffers.string_bytes` /// (disjoint field), instead of forcing a whole-`Lockfile` borrow that /// conflicts with the `&mut self` receiver. @@ -110,8 +110,7 @@ impl CatalogMap { ) } - // PORT NOTE: Zig took `lockfile: *Lockfile` only for `.allocator` (dropped - // per global-mimalloc rule). Removing it lets `lockfile.catalogs.parse_count` + // Deliberately takes no `Lockfile` param so `lockfile.catalogs.parse_count` // call sites avoid the `&mut self` vs `&mut Lockfile` self-alias. pub fn parse_count(&mut self, expr: Expr, builder: &mut StringBuilder) { if let Some(default_catalog) = expr.get(b"catalog") { @@ -165,11 +164,9 @@ impl CatalogMap { } } - /// PORT NOTE: Zig threaded `lockfile: *Lockfile` for `.allocator` and - /// `.buffers.string_bytes`. The allocator is dropped (global mimalloc) and - /// `builder` already holds `&mut string_bytes`, so read the buffer through - /// `builder` and drop the `lockfile` param — otherwise call sites would - /// alias `&mut lockfile.catalogs` against `&mut lockfile`. + /// `builder` already holds `&mut string_bytes`, so the string buffer is + /// read through it rather than taking a `lockfile` param — otherwise call + /// sites would alias `&mut lockfile.catalogs` against `&mut lockfile`. pub fn parse_append( &mut self, pm: &mut PackageManager, @@ -300,12 +297,12 @@ impl CatalogMap { Ok(found_any) } - // PORT NOTE: reshaped for borrowck — Zig threads `*Lockfile` through, but - // the only field this body touches is `lockfile.catalogs`, and the call - // site in `pnpm.rs` must simultaneously hold `&mut StringBuf` (which - // already borrows `lockfile.buffers.string_bytes` + `lockfile.string_pool`). - // Taking `&mut Lockfile` here would alias those borrows, so narrow to - // `&mut CatalogMap` and let the caller split the disjoint fields. + // The only lockfile field this body touches is `lockfile.catalogs`, and + // the call site in `pnpm.rs` must simultaneously hold `&mut StringBuf` + // (which already borrows `lockfile.buffers.string_bytes` + + // `lockfile.string_pool`). Taking `&mut Lockfile` here would alias those + // borrows, so narrow to `&mut CatalogMap` and let the caller split the + // disjoint fields. pub fn from_pnpm_lockfile( catalogs: &mut CatalogMap, log: &mut Log, @@ -339,11 +336,11 @@ impl CatalogMap { Ok(()) } - // PORT NOTE: Zig took `lockfile: *const Lockfile` but only reads its - // string buffer. Narrow to `buffers: &Buffers` so the call site can hold - // `&mut lockfile.catalogs` while only borrowing `lockfile.buffers` - // immutably (disjoint fields), instead of forcing a whole-`Lockfile` - // shared borrow that conflicts with the `&mut self` receiver. + // Takes `buffers: &Buffers` rather than the whole `Lockfile` so the call + // site can hold `&mut lockfile.catalogs` while only borrowing + // `lockfile.buffers` immutably (disjoint fields), instead of forcing a + // whole-`Lockfile` shared borrow that conflicts with the `&mut self` + // receiver. pub fn sort(&mut self, buffers: &Buffers) { let buf = buffers.string_bytes.as_slice(); let dep_less_than = |_: &[String], deps: &[Dependency], l: usize, r: usize| -> bool { @@ -365,12 +362,12 @@ impl CatalogMap { // Zig `deinit(allocator)` deleted: `Map` and `ArrayHashMap` are owned // collections whose `Drop` recursively frees the nested maps. - /// PORT NOTE: Zig took `*const Lockfile` but only ever read - /// `lockfile.buffers.string_bytes` — accept the slice directly so callers - /// can split-borrow the lockfile alongside a live `StringBuilder`. + /// Accepts `lockfile.buffers.string_bytes` directly (rather than the whole + /// `Lockfile`) so callers can split-borrow the lockfile alongside a live + /// `StringBuilder`. pub fn count(&self, string_bytes: &[u8], builder: &mut StringBuilder) { let buf = string_bytes; - // PORT NOTE: `ArrayHashMap::iterator()` requires `&mut`; iterate the + // `ArrayHashMap::iterator()` requires `&mut`; iterate the // `keys()`/`values()` slices instead so `count` can stay `&self`. for (dep_name, dep) in self.default.keys().iter().zip(self.default.values()) { builder.count(dep_name.slice(buf)); @@ -387,9 +384,9 @@ impl CatalogMap { } } - /// PORT NOTE: Zig also passed `*Lockfile new`, but `builder` already - /// borrows `new.buffers.string_bytes` — read the new-side buffer through - /// it instead so the call site doesn't alias `&mut new` twice. + /// `builder` already borrows the new lockfile's `buffers.string_bytes`, so + /// the new-side buffer is read through it instead of taking a separate + /// `new: &mut Lockfile` param that would alias `&mut new` twice. /// `pm` is generic over `NpmAliasRegistry` (was `&mut PackageManager`) so a /// caller already holding `&mut manager.lockfile` can pass /// `&mut manager.known_npm_aliases` instead of the whole manager. diff --git a/src/install/lockfile/OverrideMap.rs b/src/install/lockfile/OverrideMap.rs index 15a1f2a9d3ec..71a54681d9ce 100644 --- a/src/install/lockfile/OverrideMap.rs +++ b/src/install/lockfile/OverrideMap.rs @@ -42,27 +42,27 @@ impl OverrideMap { self.map.get(&name_hash).map(|dep| dep.version.clone()) } - // PORT NOTE: reshaped for borrowck — Zig took `*const Lockfile` but every - // caller already holds `&mut self` on `lockfile.overrides`, so accept just - // the string buffer (the only field `sort` reads). + // Every caller already holds `&mut self` on `lockfile.overrides`, so + // accept just the string buffer (the only lockfile field `sort` reads) + // rather than the whole `Lockfile`. pub(crate) fn sort(&mut self, string_bytes: &[u8]) { self.map.sort(|_, deps: &[Dependency], l, r| { deps[l].name.order(deps[r].name, string_bytes, string_bytes) == Ordering::Less }); } - /// PORT NOTE: Zig took `*const Lockfile` but only ever read - /// `lockfile.buffers.string_bytes` — accept the slice directly so callers - /// can split-borrow the lockfile alongside a live `StringBuilder`. + /// Accepts `lockfile.buffers.string_bytes` directly (rather than the whole + /// `Lockfile`) so callers can split-borrow the lockfile alongside a live + /// `StringBuilder`. pub(crate) fn count(&self, string_bytes: &[u8], builder: &mut StringBuilder) { for dep in self.map.values() { dep.count(string_bytes, builder); } } - /// PORT NOTE: Zig also passed `*Lockfile new`, but it was unused — - /// the new-side buffer lives inside `new_builder`. Dropped to avoid the alias. - /// `pm` is generic over `NpmAliasRegistry` (was `&mut PackageManager`) so a + /// The new-side buffer lives inside `new_builder`, so no separate + /// `new: &mut Lockfile` param is taken — that would alias the borrow. + /// `pm` is generic over `NpmAliasRegistry` (not `&mut PackageManager`) so a /// caller already holding `&mut manager.lockfile` can pass /// `&mut manager.known_npm_aliases` instead of the whole manager. pub(crate) fn clone( @@ -85,10 +85,9 @@ impl OverrideMap { // the rest of this struct is expression parsing code: - // PORT NOTE: Zig passed `lockfile: *Lockfile` solely for `lockfile.allocator` - // (string transcode); JSON strings are already UTF-8 here, so the parameter - // is dropped — also avoids the `&mut lockfile.overrides` / `&mut lockfile` - // alias at the only call site. + // No `lockfile` param: JSON strings are already UTF-8 here, and omitting + // it avoids the `&mut lockfile.overrides` / `&mut lockfile` alias at the + // only call site. pub(crate) fn parse_count(&mut self, expr: Expr, builder: &mut StringBuilder) { if let Some(overrides) = expr.as_property(b"overrides") { let ExprData::EObject(obj) = &overrides.expr.data else { @@ -423,10 +422,9 @@ impl OverrideMap { // Only used in warning-message formatting, so runtime &'static str is fine. pub fn parse_override_value( field: &'static str, - // PORT NOTE: Zig took `*Lockfile` but only read `buffers.dependencies` and - // `buffers.string_bytes`. Callers hold a live `StringBuilder` (which owns - // `&mut string_bytes`), so accept the dependency slice directly and read - // string-bytes through `builder.string_bytes`. + // Callers hold a live `StringBuilder` (which owns `&mut string_bytes`), so + // accept the dependency slice directly and read string-bytes through + // `builder.string_bytes` instead of taking the whole `Lockfile`. lockfile_dependencies: &[Dependency], package_manager: &mut PackageManager, root_package: &Package, diff --git a/src/install/lockfile/Package.rs b/src/install/lockfile/Package.rs index 75a1928c4ec6..717e8f3653b4 100644 --- a/src/install/lockfile/Package.rs +++ b/src/install/lockfile/Package.rs @@ -22,7 +22,7 @@ use crate::{ // `Package.rs` is mounted as `crate::lockfile_real::package`; the parent module // (`super`) is the real `lockfile.rs`, distinct from the `crate::lockfile` // stub that lib.rs exposes for downstream crates during the staged port. -// PORT NOTE: bare `use super as lockfile;` fails when this file is reached via +// bare `use super as lockfile;` fails when this file is reached via // `#[path]` from a non-module context (rust-lang/rust#48067). Name the parent // module by its absolute crate path instead. use crate::lockfile_real as lockfile; @@ -67,7 +67,7 @@ impl ExprStr for Expr { // Defaulted to `u64` so bare `Package` matches Zig's primary `Package(u64)` // instantiation (the only one the lockfile/PM call sites name unqualified). // -// PORT NOTE: `` cannot be used here — the derive +// `#[derive(MultiArrayElement)]` cannot be used here — the derive // emits a `PackageField` enum with snake_case variants and an inherent // `__MAL_SIZES` const that fail to const-eval through the defaulted // `SemverIntType` param. The trait impl, field enum, and `PackageColumns` / @@ -405,7 +405,7 @@ impl Package { } } -// PORT NOTE: `clone` / `from_package_json` / `from_npm` / `parse*` all interact +// `clone` / `from_package_json` / `from_npm` / `parse*` all interact // with `Lockfile`, whose package list is concretely `MultiArrayList>`. // Zig's `Package(SemverIntType)` is only ever instantiated at `u64` for these // paths (the `u32` instantiation is migration-only and routed through @@ -414,8 +414,7 @@ impl Package { // site. impl Package { pub fn clone(&self, cloner: &mut Cloner) -> Result { - // TODO(port): narrow error set - // PORT NOTE: Zig passes (`pm`, `old`, `new`, `package_id_mapping`, + // Zig passes (`pm`, `old`, `new`, `package_id_mapping`, // `cloner`) separately, but `cloner` already owns `&mut` to all four. // Rust borrowck rejects the redundant aliasing at the call site, so // route everything through `cloner`'s disjoint fields here instead. @@ -428,7 +427,7 @@ impl Package { let package_id_mapping = &mut *cloner.mapping; let old_string_buf = old.buffers.string_bytes.as_slice(); let old_extern_string_buf = old.buffers.extern_strings.as_slice(); - // PORT NOTE: `string_builder!` split-borrows only `new.buffers + // `string_builder!` split-borrows only `new.buffers // .string_bytes` + `new.string_pool`, leaving sibling buffer fields // (`dependencies`, `resolutions`, `extern_strings`, `packages`) free // for the disjoint borrows below. @@ -493,7 +492,7 @@ impl Package { // Default-fill the tail so it is valid before `bin.clone` overwrites // it (replaces `reserve` + raw `set_len`). bun_core::vec::grow_default(&mut new.buffers.extern_strings, new_extern_string_count); - // PORT NOTE: Zig passes both `new.buffers.extern_strings.items` (full slice) and a + // Zig passes both `new.buffers.extern_strings.items` (full slice) and a // tail subslice into `bin.clone`; the full slice is only used to compute the tail's // offset for `ExternalStringList::init`. In Rust those two views would alias, so // `Bin::clone_with_buffers` takes the precomputed offset directly. @@ -501,7 +500,7 @@ impl Package { let id = new.packages.len() as PackageID; - // PORT NOTE: Zig calls `appendPackageWithID` mid-body while still + // Zig calls `appendPackageWithID` mid-body while still // holding live slices into `new.buffers` and the `builder`. Rust can't // express that (the method borrows `&mut Lockfile` whole), so build the // `Package` value and clone the dependency strings *first* (only needs @@ -592,12 +591,11 @@ impl Package { ) -> Result { #[allow(non_snake_case)] let FEATURES = features; - // TODO(port): narrow error set let mut package = Self::default(); // var string_buf = package_json; - // PORT NOTE: split-borrow `string_bytes`/`string_pool` so the disjoint + // split-borrow `string_bytes`/`string_pool` so the disjoint // `lockfile.buffers.dependencies/resolutions` borrows below pass. let mut string_builder = crate::string_builder!(lockfile); @@ -709,7 +707,6 @@ impl Package { ) -> Result { #[allow(non_snake_case)] let FEATURES = features; - // TODO(port): narrow error set let mut package = Self::default(); let package_version = *package_version_ptr; @@ -732,7 +729,7 @@ impl Package { out }; - // PORT NOTE: split-borrow so `lockfile.buffers.dependencies/resolutions + // split-borrow so `lockfile.buffers.dependencies/resolutions // /extern_strings` below are disjoint from the builder's `string_bytes`. let mut string_builder = crate::string_builder!(lockfile); @@ -1013,7 +1010,7 @@ impl DiffSummary { } impl Diff { - // PORT NOTE: Zig's `Package` here is the canonical `Package(u64)` (the only + // Zig's `Package` here is the canonical `Package(u64)` (the only // instantiation `Lockfile` ever holds). Dropping the generic avoids a // spurious `Package` ≠ `Package` mismatch on the recursive call // through `from_lockfile.packages.get(...)`. @@ -1027,10 +1024,9 @@ impl Diff { update_requests: Option<&[UpdateRequest]>, mut id_mapping: Option<&mut [PackageID]>, ) -> Result { - // TODO(port): narrow error set let mut summary = DiffSummary::default(); let is_root = id_mapping.is_some(); - // PORT NOTE: Zig held `to_deps` as a mutable slice binding and reassigned + // Zig held `to_deps` as a mutable slice binding and reassigned // it after `parseWithJSON` (which may grow `to_lockfile.buffers // .dependencies` and invalidate the old slice). Mirror that with raw fat // pointers so the `&mut to_lockfile`/`&mut from_lockfile` reborrows below @@ -1055,7 +1051,7 @@ impl Diff { .resolutions .get(from_lockfile.buffers.resolutions.as_slice()) .into(); - // See PORT NOTE above — `from_lockfile.buffers` is not reallocated for + // See note above — `from_lockfile.buffers` is not reallocated for // the lifetime of these references. let (from_deps, from_resolutions) = (from_deps.slice(), from_resolutions.slice()); let mut to_i: usize = 0; @@ -1067,7 +1063,7 @@ impl Diff { bun_core::pretty_errorln!("Overrides changed since last install"); } } else { - // PORT NOTE: reshaped for borrowck — Zig passed `from_lockfile` + // Reshaped for borrowck — Zig passed `from_lockfile` // twice (once as `&mut self` via `.overrides`, once as `lockfile`). // `OverrideMap::sort` only reads `lockfile.buffers.string_bytes`, // so split the borrow at the field. @@ -1122,7 +1118,7 @@ impl Diff { break 'catalogs; } - // PORT NOTE: reshaped for borrowck — see `overrides.sort` note above. + // Reshaped for borrowck — see `overrides.sort` note above. lockfile::CatalogMap::sort(&mut from_lockfile.catalogs, &from_lockfile.buffers); lockfile::CatalogMap::sort(&mut to_lockfile.catalogs, &to_lockfile.buffers); @@ -1472,7 +1468,7 @@ impl Diff { ); let _ = package_json_path.append(b"package.json"); // OOM/capacity: Zig aborts; port keeps fire-and-forget - // PORT NOTE: `bun.sys.File.toSource` was removed from + // `bun.sys.File.toSource` was removed from // T1 (`bun_sys`) because `bun_ast::Source` lives in T2. // Route through the workspace cache's path-based getter // instead, which both reads and parses. @@ -1653,7 +1649,6 @@ impl Package { resolver: &mut R, features: Features, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set initialize_store(); // Zig threaded `lockfile.allocator` for the JSON arena. The returned // `Expr` tree only needs to live until `parse_with_json` finishes, so @@ -1709,7 +1704,7 @@ impl Package { // Zig: `comptime group: DependencyGroup`, `comptime features: Features`, `comptime tag: ?Dependency.Version.Tag` // PERF(port): was comptime monomorphization on `group`/`tag` — profile if hot. // - // PORT NOTE: Zig took `lockfile: *Lockfile`, but the live `StringBuilder` + // Zig took `lockfile: *Lockfile`, but the live `StringBuilder` // (also passed) already holds `&mut lockfile.buffers.string_bytes`. The // body only otherwise touches `workspace_paths` / `workspace_versions`, // so accept those two maps directly and read `string_bytes` via the @@ -1733,7 +1728,6 @@ impl Package { key_loc: bun_ast::Loc, value_loc: bun_ast::Loc, ) -> Result, bun_core::Error> { - // TODO(port): narrow error set #[cfg(windows)] let external_version = 'brk: { match tag.unwrap_or_else(|| dependency::version::Tag::infer(version)) { @@ -2151,12 +2145,11 @@ impl Package { ) -> Result<(), bun_core::Error> { #[allow(non_snake_case)] let FEATURES = features; - // TODO(port): narrow error set // Zig threads `allocator` for `asString` transcoding; the Rust signature // dropped it, so use a function-local arena (transcoded strings are only // borrowed until `string_builder.append` copies them). let bump = bun_alloc::Arena::new(); - // PORT NOTE: split-borrow `string_bytes`/`string_pool` so the dozens of + // split-borrow `string_bytes`/`string_pool` so the dozens of // disjoint `lockfile.{buffers.*, overrides, catalogs, workspace_*, …}` // accesses below pass borrowck. Reads of `lockfile.buffers.string_bytes` // must go through `string_builder.string_bytes` while it's live. @@ -2557,7 +2550,7 @@ impl Package { ); } - // PORT NOTE: Zig slices `lockfile.buffers.dependencies.items.ptr[off..total_len]` + // Zig slices `lockfile.buffers.dependencies.items.ptr[off..total_len]` // — i.e. into reserved-but-uncommitted capacity *without* bumping `items.len`. // Mirroring that here matters: `parse_dependency` can return early with an error // (e.g. `InstallFailed` for a non-matching `workspace:` range), and the caller @@ -2698,7 +2691,7 @@ impl Package { if cfg!(debug_assertions) { debug_assert!(i == extern_strings.len()); } - // PORT NOTE: Zig passed the full extern_strings + // Zig passed the full extern_strings // buffer + tail subslice; `init` only needs the // tail's offset, so construct directly to avoid // the aliasing borrow. @@ -3176,16 +3169,17 @@ pub mod serializer { stream: &mut S, ) -> Result<(), bun_core::Error> where - // PORT NOTE: Zig threaded a separate `stream` (anytype) and `writer` over + // Zig threaded a separate `stream` (anytype) and `writer` over // the same buffer. Two `&mut` to one object is UB in Rust regardless of // access order, so the port collapses both roles onto one type — // `Serializer::StreamType` impls both `PositionalStream` and // `bun_io::Write`. S: PositionalStream + bun_io::Write, { - // TODO(port): narrow error set stream.write_int_le::(list.len() as u64)?; - // TODO(port): @alignOf(@TypeOf(list.bytes)) — needs concrete type from MultiArrayList. + // Zig wrote `@alignOf(@TypeOf(list.bytes))` — the alignment of the + // MultiArrayList bytes *pointer* itself (not the pointee), which is + // exactly `align_of::<*mut u8>()` on every supported target. stream.write_int_le::(mem::align_of::<*mut u8>() as u64)?; stream.write_int_le::(FIELD_COUNT as u64)?; let begin_at = stream.get_pos()?; @@ -3193,7 +3187,8 @@ pub mod serializer { let end_at = stream.get_pos()?; stream.write_int_le::(0)?; - // TODO(port): Aligner.write needs the bytes-pointer alignment type. + // `*mut u8` carries the pointer alignment, matching the + // `@alignOf(@TypeOf(list.bytes))` value serialized above. let pos = stream.get_pos()? as u64; let _ = Aligner::write::<*mut u8, _>(&mut *stream, pos)?; @@ -3225,9 +3220,10 @@ pub mod serializer { bytes.len(), ); } - // TODO(port): assert_no_uninitialized_padding once a typed accessor - // is exposed; for now `Package`'s field types are all `#[repr(C)]` - // with explicit padding zeroed by their `Default`/`init` paths. + // Zig's `assert_no_uninitialized_padding` has no per-field typed + // accessor here; the invariant holds because `Package`'s field + // types are all `#[repr(C)]` with explicit padding zeroed by + // their `Default`/`init` paths. if matches!(field, PackageField::Resolution) { // copy each resolution to make sure the union is zero initialized let resolutions: &[Resolution] = @@ -3265,7 +3261,7 @@ pub mod serializer { pub needs_update: bool, } - // PORT NOTE: Zig parameterised on `SemverIntType`, but the v2-migration arm + // Zig parameterised on `SemverIntType`, but the v2-migration arm // below hard-codes `u32 → u64` (`VersionedURL.migrate()` returns ``). // The only caller (`bun.lockb.rs`) instantiates at `u64`, so bind concretely // instead of carrying a phantom generic that can't typecheck the migrate arm. @@ -3275,7 +3271,6 @@ pub mod serializer { migrate_from_v2: bool, ) -> Result, bun_core::Error> { type SemverIntType = u64; - // TODO(port): narrow error set let reader = stream.reader(); let list_len = reader.read_int_le::()?; @@ -3289,7 +3284,8 @@ pub mod serializer { let mut list = List::::default(); - // TODO(port): @alignOf(@TypeOf(list.bytes)) — needs MultiArrayList bytes ptr type. + // Zig compared against `@alignOf(@TypeOf(list.bytes))` — the alignment + // of the MultiArrayList bytes *pointer* itself, i.e. pointer alignment. let expected_alignment = mem::align_of::<*mut u8>() as u64; if expected_alignment != input_alignment { return Err(bun_core::err!( @@ -3403,7 +3399,6 @@ pub mod serializer { list: &mut List, needs_update: &mut bool, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let _n = list.len(); let mut sliced = list.slice(); @@ -3421,7 +3416,6 @@ pub mod serializer { sliced.column_bytes_mut(field as usize) } }; - // TODO(port): assert_no_uninitialized_padding once a typed accessor lands. let end_pos = stream.pos + bytes.len(); if end_pos as u64 <= end_at { let src = &stream.buffer[stream.pos..stream.pos + bytes.len()]; diff --git a/src/install/lockfile/Package/Meta.rs b/src/install/lockfile/Package/Meta.rs index ed0b68f12cce..19a67b8de92a 100644 --- a/src/install/lockfile/Package/Meta.rs +++ b/src/install/lockfile/Package/Meta.rs @@ -85,10 +85,9 @@ impl Meta { self.has_install_script == HasInstallScript::Old } - // PORT NOTE: Zig used `comptime StringBuilderType: type` duck-typing for the - // builder param. The only concrete instantiation in install is - // `*Lockfile.StringBuilder`, so we take it directly here instead of a - // placeholder trait that nothing implements. + // The only concrete builder type used in install is the lockfile + // `StringBuilder`, so take it directly instead of a placeholder trait that + // nothing implements. pub fn count(&self, buf: &[u8], builder: &mut LockfileStringBuilder<'_>) { builder.count(self.man_dir.slice(buf)); } diff --git a/src/install/lockfile/Package/Scripts.rs b/src/install/lockfile/Package/Scripts.rs index dd02cbcfad72..bb5c1faaee51 100644 --- a/src/install/lockfile/Package/Scripts.rs +++ b/src/install/lockfile/Package/Scripts.rs @@ -11,7 +11,7 @@ use bun_semver::String as SemverString; use bun_sys::{self, Fd}; use crate::bun_json::{self, Expr}; -// PORT NOTE: Zig used `comptime Builder: type` duck-typing for the builder +// Zig used `comptime Builder: type` duck-typing for the builder // param. The only concrete instantiation in install is `*Lockfile.StringBuilder`, // so we take `crate::lockfile_real::StringBuilder` directly (matches Meta.rs). use crate::lockfile_real::{Lockfile as RealLockfile, StringBuilder as LockfileStringBuilder}; @@ -120,7 +120,7 @@ impl Scripts { } /// return: (first_index, total, entries) - /// PORT NOTE: Zig also passed `*const Lockfile` (for `allocator`); the + /// Zig also passed `*const Lockfile` (for `allocator`); the /// allocator is gone in Rust and the parameter was already unused — drop /// it so callers can split-borrow `lockfile.{packages, scripts}` while /// this only reads `lockfile_buf`. @@ -264,13 +264,13 @@ impl Scripts { // builder with `.count` / `.append`. Generic over `bun_semver::StringBuilder` // so both `lockfile_real::StringBuilder` and `bun_semver::semver_string::Builder` // are accepted (both impl the trait). - // PORT NOTE: `json` is `Copy` (matches Zig by-value `Expr`). + // `json` is `Copy` (matches Zig by-value `Expr`). pub fn parse_count(builder: &mut B, json: Expr) { if let Some(scripts_prop) = json.as_property(b"scripts") { if scripts_prop.expr.is_object() { for script_name in LockfileScripts::NAMES { if let Some(script) = scripts_prop.expr.get(script_name.as_bytes()) { - // PORT NOTE: Zig `script.asString(allocator)` — JSON parser + // Zig `script.asString(allocator)` — JSON parser // produces UTF-8 `EString`s, so the alloc-free literal accessor // is sufficient here. if let Some(input) = script.as_utf8_string_literal() { @@ -307,7 +307,6 @@ impl Scripts { folder_name: &[u8], resolution: &Resolution, ) -> Result, bun_core::Error> { - // TODO(port): narrow error set if self.has_any() { let add_node_gyp_rebuild_script = if lockfile.has_trusted_dependency(folder_name, folder_name, resolution) @@ -351,8 +350,7 @@ impl Scripts { log: &mut bun_ast::Log, folder_path: &mut bun_paths::AutoAbsPath, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set - // PORT NOTE: Zig threaded `allocator` for JSON parsing; the Rust JSON + // Zig threaded `allocator` for JSON parsing; the Rust JSON // parser uses a bump arena. Scoped here since the AST is consumed // immediately into the string builder. `json_buf` is hoisted so the // source bytes outlive the parsed `Expr` (which may borrow them). @@ -386,7 +384,6 @@ impl Scripts { folder_name: &[u8], resolution_tag: ResolutionTag, ) -> Result, bun_core::Error> { - // TODO(port): narrow error set // Zig: `var tmp: Lockfile = undefined; tmp.initEmpty(allocator)`. let mut tmp = RealLockfile::init_empty_value(); // `defer tmp.deinit()` — `tmp` stays empty (only `string_builder` borrows it), so field @@ -425,7 +422,7 @@ pub enum PrintFormat { Untrusted, } -// PORT NOTE: `Clone` — Zig had borrowed slices so `list.*` was a shallow +// `Clone` — Zig had borrowed slices so `list.*` was a shallow // pointer copy. The Rust port owns `cwd`/`package_name`/`items`, but // `runTasks.rs` (`.run_scripts` arm) and `lifecycle_script_runner` need a // by-value copy while the original allocation in `Store.entries.scripts` diff --git a/src/install/lockfile/Package/WorkspaceMap.rs b/src/install/lockfile/Package/WorkspaceMap.rs index 34e45fb3006f..ad4724b8ebdf 100644 --- a/src/install/lockfile/Package/WorkspaceMap.rs +++ b/src/install/lockfile/Package/WorkspaceMap.rs @@ -293,7 +293,7 @@ impl WorkspaceMap { let mut arena = Arena::new(); // PERF(port): was arena bulk-free per-iteration via reset(.retain_capacity) for (i, user_pattern) in workspace_globs.iter().enumerate() { - // PORT NOTE: Zig `defer arena.reset()` ran *after* iter.deinit()/walker.deinit() at + // Zig `defer arena.reset()` ran *after* iter.deinit()/walker.deinit() at // end of each iter. In Rust, walker/iter borrow `&arena` and Drop at scope exit, // so resetting here (top of next iter) ensures they drop before invalidation. // Last iter's allocs are freed when `arena` itself drops after the loop. @@ -311,7 +311,7 @@ impl WorkspaceMap { if cwd.is_empty() { cwd = bun_resolver::fs::FileSystem::instance().top_level_dir(); } - // PORT NOTE: GlobWalker::init_with_cwd is now an associated constructor + // GlobWalker::init_with_cwd is now an associated constructor // returning `Result>`; arena param dropped (heap-backed), // ignore filter supplied as final arg (was comptime fn param in Zig). let mut walker = match GlobWalker::init_with_cwd( @@ -338,8 +338,9 @@ impl WorkspaceMap { return Err(bun_core::err!("GlobError")); } }; - // walker dropped at end of loop iter (Drop impl handles deinit(false)) - // TODO(port): GlobWalker::deinit(false) — Drop cannot take params; assume default Drop matches `false` + // walker dropped at end of loop iter. Zig called `deinit(false)` (skip the + // arena free); the Rust GlobWalker is heap-backed with no arena, and its + // Drop only logs + frees owned Vec/Box fields, so default Drop matches. let mut iter = glob::walk::Iterator::new(&mut walker); if let Err(e) = iter.init()? { @@ -520,7 +521,7 @@ impl WorkspaceMap { } // Sort the names for determinism - // PORT NOTE: reshaped for borrowck — Zig captured `values()` slice in sort ctx; + // reshaped for borrowck — Zig captured `values()` slice in sort ctx; // here ArrayHashMap::sort provides values internally to the comparator. workspace_names .map @@ -543,7 +544,7 @@ fn ignored_workspace_paths(path: &[u8]) -> bool { false } -// PORT NOTE: Zig `glob.GlobWalker(ignoredWorkspacePaths, glob.walk.SyscallAccessor, false)` — +// Zig `glob.GlobWalker(ignoredWorkspacePaths, glob.walk.SyscallAccessor, false)` — // the comptime ignore-filter fn param was lowered to a runtime fn-pointer field on // `bun_glob::GlobWalker` (const-generic fn ptrs are unstable). Supplied via // `init_with_cwd(..., Some(ignored_workspace_paths))`. diff --git a/src/install/lockfile/Tree.rs b/src/install/lockfile/Tree.rs index d6f2863a1660..f62298894792 100644 --- a/src/install/lockfile/Tree.rs +++ b/src/install/lockfile/Tree.rs @@ -18,7 +18,7 @@ use crate::{ // Tree // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: `#[repr(C)]` pins field order to declaration order so the raw +// `#[repr(C)]` pins field order to declaration order so the raw // in-memory bytes match the `[u8; 20]` `External` encoding read by // `Buffers::load` (which decodes via `to_tree` assuming // id|dep_id|parent|off|len). Under `repr(Rust)` rustc may reorder fields and @@ -176,7 +176,7 @@ pub enum IteratorPathStyle { PkgPath, } -// PORT NOTE: reshaped — Zig stores `lockfile: *const Lockfile`; here we store +// reshaped — Zig stores `lockfile: *const Lockfile`; here we store // the four buffer slices the iterator actually reads so callers from both // `crate::lockfile` (stub) and `crate::lockfile_real` can drive the same // iterator without a unified `Lockfile` type (reconciler-6). @@ -247,7 +247,7 @@ impl<'a, const PATH_STYLE: IteratorPathStyle> Iterator<'a, PATH_STYLE> { self.tree_id = 0; } - // TODO(port): Zig signature varies `completed_trees` type by `path_style` (void when .pkg_path). + // Zig varied the `completed_trees` type by `path_style` (void when .pkg_path). // Here we accept `Option<&mut DynamicBitSet>` unconditionally; callers with PkgPath must pass None. pub fn next( &mut self, @@ -259,7 +259,7 @@ impl<'a, const PATH_STYLE: IteratorPathStyle> Iterator<'a, PATH_STYLE> { return None; } - // PORT NOTE: reshaped for borrowck — cannot mutably borrow completed_trees in loop while moved. + // reshaped for borrowck — cannot mutably borrow completed_trees in loop while moved. let mut completed_trees = completed_trees; while trees[self.tree_id as usize].dependencies.len == 0 { @@ -306,7 +306,7 @@ pub fn folder_name_is_safe(name: &[u8]) -> bool { } /// Returns relative path and the depth of the tree -// PORT NOTE: reshaped — Zig takes `*const Lockfile`; here we take the three +// reshaped — Zig takes `*const Lockfile`; here we take the three // buffer slices directly so callers from both `crate::lockfile` (stub) and // `crate::lockfile_real` can use this without a shared `Lockfile` type. pub(crate) fn relative_path_and_depth<'b, const PATH_STYLE: IteratorPathStyle>( @@ -421,12 +421,11 @@ pub enum BuilderMethod { Filter, } -// TODO(port): Zig conditionally typed `manager`/`workspace_filters`/`install_root_dependencies`/ -// `packages_to_install` as `void` when method != .filter. Rust const generics cannot vary field -// types; using Option<_>/empty defaults instead. TODO(refactor): split into two structs or use a -// trait-associated type if the size matters. +// Zig conditionally typed `manager`/`workspace_filters`/`install_root_dependencies`/ +// `packages_to_install` as `void` when method != .filter; Rust const generics cannot vary field +// types, so they are `Option<_>`/empty defaults and only meaningful when `METHOD == .Filter`. pub struct Builder<'a, const METHOD: BuilderMethod> { - // PORT NOTE: Zig `std.mem.Allocator` param field dropped. Sole construction site is + // Zig `std.mem.Allocator` param field dropped. Sole construction site is // `Lockfile.hoist()` (src/install/lockfile.zig) which passes `lockfile.allocator` — the // lockfile's persistent allocator (bun.default_allocator via PackageManager/CLI ctx), not an // arena. Global mimalloc is correct here; no `&'bump Bump` threading needed. @@ -436,7 +435,7 @@ pub struct Builder<'a, const METHOD: BuilderMethod> { pub resolution_lists: &'a [DependencyIDSlice], pub queue: TreeFiller, pub log: &'a mut bun_ast::Log, - /// PORT NOTE: Zig stores `*Lockfile` alongside `&mut buffers.resolutions` + /// Zig stores `*Lockfile` alongside `&mut buffers.resolutions` /// (an aliased subslice of the same struct). Stored as `ParentRef` (raw /// non-null backref) so the construction site (`Lockfile::hoist`) can /// split-borrow `resolutions` mutably without borrowck rejecting the @@ -501,10 +500,8 @@ impl<'a, const METHOD: BuilderMethod> Builder<'a, METHOD> { pub(crate) fn clean(&mut self) -> Result { let mut total: u32 = 0; - // TODO(port): Zig captured `list.bytes` raw pointer to reuse the MultiArrayList backing - // allocation for the output `trees` slice. That optimization depends on MultiArrayList - // internal layout. Porting the straightforward path (fresh Vec) instead. - // PERF(port): was MultiArrayList buffer reuse — profile if hot. + // PERF(port): was MultiArrayList buffer reuse (Zig recycled `list.bytes` for the + // output `trees` slice); ported as a fresh Vec — profile if hot. let mut slice = self.list.to_owned_slice(); let mut trees: Vec = slice.items_tree().to_vec(); let dependencies: &mut [DependencyIDList] = slice.items_dependencies_mut(); @@ -540,7 +537,7 @@ impl<'a, const METHOD: BuilderMethod> Builder<'a, METHOD> { } // queue / sort_buf / pending_optional_peers freed by Drop; explicit deinit removed. - // TODO(port): if Builder outlives clean(), explicitly clear these fields here. + // The sole caller (`Lockfile::hoist`) drops the Builder immediately after clean(). slice.deinit_owned(); @@ -552,7 +549,7 @@ impl<'a, const METHOD: BuilderMethod> Builder<'a, METHOD> { // is_filtered_dependency_or_workspace // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: reshaped — Zig reads `lockfile.buffers.resolutions[dep_id]` directly, +// reshaped — Zig reads `lockfile.buffers.resolutions[dep_id]` directly, // but `Builder` holds a live `&mut [PackageID]` over that buffer (see `Builder.lockfile` // safety contract), so callers must thread `resolutions` explicitly to avoid an // aliasing read through the shared `&Lockfile`. @@ -720,7 +717,7 @@ impl Tree { dependencies: DependencyIDList::default(), })?; - // PORT NOTE: reshaped for borrowck. + // reshaped for borrowck. let next_id = (builder.list.len() - 1) as Id; // Copy the `ParentRef` out (it's `Copy`) so the resulting `&Lockfile` @@ -730,7 +727,7 @@ impl Tree { let lockfile: &Lockfile = lockfile_ref.get(); let pkgs = lockfile.packages.slice(); let pkg_resolutions = pkgs.items_resolution(); - // PORT NOTE: reshaped for borrowck — copy the `&'a [Dependency]` out of + // reshaped for borrowck — copy the `&'a [Dependency]` out of // `builder` so `&dependencies[i]` does not keep `builder` borrowed. let dependencies: &[Dependency] = builder.dependencies; @@ -757,7 +754,7 @@ impl Tree { }); } - // PORT NOTE: reshaped for borrowck — iterate over a snapshot of sort_buf indices since + // reshaped for borrowck — iterate over a snapshot of sort_buf indices since // builder is mutably borrowed inside the loop. let sort_buf_len = builder.sort_buf.len(); 'dep: for sort_idx in 0..sort_buf_len { @@ -946,7 +943,7 @@ impl Tree { } HoistDependencyResult::Placement(dest) => { { - // PORT NOTE: reshaped for borrowck — Zig held both `items(.dependencies)` + // reshaped for borrowck — Zig held both `items(.dependencies)` // and `items(.tree)` mutably from one slice; here we go through ListExt // accessors sequentially so the &mut borrows do not overlap. // bun.handleOom -> push (aborts on OOM via global allocator) @@ -970,7 +967,7 @@ impl Tree { } } - // PORT NOTE: reshaped for borrowck — re-read `next` via index. + // reshaped for borrowck — re-read `next` via index. let next: Tree = builder.list.items_tree()[next_id as usize]; if next.dependencies.len == 0 { if cfg!(debug_assertions) { @@ -987,7 +984,7 @@ impl Tree { // 2 (return id) - move the package to the top directory // 3 (return dependency_loop) - leave the package at the same (relative) directory // - // PORT NOTE: reshaped for borrowck — Zig passed `&mut self` (an element of `trees`) plus + // reshaped for borrowck — Zig passed `&mut self` (an element of `trees`) plus // `trees: &mut [Tree]`, `dependency_lists: &mut [...]`, and `builder: &mut Builder` // simultaneously, which overlaps mutable borrows. The body never mutates `self`, `trees`, // or `dependency_lists`, so we take `self_id: Id` by value and re-derive read-only views @@ -1093,7 +1090,7 @@ impl Tree { } if AS_DEFINED && !dep.behavior.is_peer() { - // PORT NOTE: reshaped for borrowck — `maybe_report_error` takes + // reshaped for borrowck — `maybe_report_error` takes // `&mut self` but the format args borrow `&self` (via // `package_name`/`package_version`/`buf`). Inline against split // field borrows: copy the `ParentRef` out so the `&Lockfile` is diff --git a/src/install/lockfile/bun.lock.rs b/src/install/lockfile/bun.lock.rs index 9661811ab251..a78123ae51cc 100644 --- a/src/install/lockfile/bun.lock.rs +++ b/src/install/lockfile/bun.lock.rs @@ -37,7 +37,7 @@ use crate::npm::Negatable; use crate::package_manager_real::Options as PackageManagerOptions; use crate::repository::RepositoryExt as _; use bun_install_types::DependencyVersionTag; -// PORT NOTE: this file is `crate::lockfile_real::bun_lock`; `super` is the +// this file is `crate::lockfile_real::bun_lock`; `super` is the // real `Lockfile` module, distinct from the `crate::lockfile` stub. use super::PackageIDSlice; use super::package::{Meta, PackageColumns as _}; @@ -77,7 +77,7 @@ pub(crate) fn url_is_under_registry(url: &[u8], registry: &[u8]) -> bool { && (url.len() == registry.len() || url[registry.len()] == b'/') } -// PORT NOTE: reshaped for borrowck. Zig keeps a single `var string_buf = +// reshaped for borrowck. Zig keeps a single `var string_buf = // lockfile.stringBuf()` for the whole parser, but in Rust that locks out every // other `lockfile.*` access (the `string_buf()` method borrows the whole // receiver). Construct a fresh `Buf` at each append site so the disjoint @@ -93,7 +93,6 @@ macro_rules! sbuf { }; } -// TODO(port): narrow to a concrete byte-writer trait once bun_io stabilizes. // PERF(port): anytype → dyn dispatch (Zig used `writer: anytype`; PORTING.md // prefers `impl Trait`, but the trait shape is unsettled so dyn for now). type Writer = dyn bun_io::Write; @@ -299,13 +298,12 @@ impl Stringifier { options: &PackageManagerOptions, writer: &mut Writer, ) -> Result<(), WriteError> { - // TODO(port): narrow error set let buf = lockfile.buffers.string_bytes.as_slice(); let extern_strings = lockfile.buffers.extern_strings.as_slice(); let deps_buf = lockfile.buffers.dependencies.as_slice(); let resolution_buf = lockfile.buffers.resolutions.as_slice(); let pkgs = lockfile.packages.slice(); - // PORT NOTE: Zig `pkgs.items(.field)` → derive(MultiArrayElement)-generated + // Zig `pkgs.items(.field)` → derive(MultiArrayElement)-generated // `items_()` column accessors on `Slice`. let pkg_dep_lists: &[DependencySlice] = pkgs.items_dependencies(); let pkg_resolutions: &[Resolution] = pkgs.items_resolution(); @@ -328,7 +326,7 @@ impl Stringifier { let mut pkg_map: PkgMap<()> = PkgMap::init(); - // PORT NOTE: `from_slices` (vs `init(lockfile)`) is used so the iterator + // `from_slices` (vs `init(lockfile)`) is used so the iterator // borrows only `buffers.{trees,hoisted_dependencies,dependencies,string_bytes}`; // `overrides`/`catalogs` are mutated below while the iterator is still live. let mut pkgs_iter = tree::Iterator::<'_, { tree::IteratorPathStyle::PkgPath }>::from_slices( @@ -1072,8 +1070,7 @@ impl Stringifier { relative_path: &[u8], path_buf: &mut [u8], ) -> Result<(), WriteError> { - // TODO(port): narrow error set to { OutOfMemory, WriteFailed } - // PORT NOTE: Zig `defer optional_peers_buf.clearRetainingCapacity()` moved to fn tail. + // Zig `defer optional_peers_buf.clearRetainingCapacity()` moved to fn tail. // Error path (`?` on writer) aborts the whole save in the caller, so skipping the // clear on early-return cannot leak stale entries into a subsequent call. @@ -1250,8 +1247,7 @@ impl Stringifier { relative_path: &[u8], path_buf: &mut [u8], ) -> Result<(), WriteError> { - // TODO(port): narrow error set to { OutOfMemory, WriteFailed } - // PORT NOTE: Zig `defer optional_peers_buf.clearRetainingCapacity()` moved to fn tail. + // Zig `defer optional_peers_buf.clearRetainingCapacity()` moved to fn tail. // Error path (`?` on writer) aborts the whole save in the caller, so skipping the // clear on early-return cannot leak stale entries into a subsequent call. @@ -1494,7 +1490,7 @@ pub(crate) enum ResolveError { } impl PkgMap { - // PORT NOTE: Zig `pub const Entry = T;` — inherent associated types are + // Zig `pub const Entry = T;` — inherent associated types are // unstable in Rust; callers name `T` directly. pub(crate) fn init() -> Self { @@ -2181,7 +2177,7 @@ pub fn parse_into_binary_lockfile( if lockfile_version != Version::V0 { // these are the `workspaceOnly` packages - // PORT NOTE: snapshot the workspace-path handles up front so the loop + // snapshot the workspace-path handles up front so the loop // body can take `&mut *lockfile` (`parse_append_dependencies`, // `append_package_dedupe`) without conflicting with the // `workspace_paths.values()` iterator borrow. `String` is `Copy`. @@ -2797,7 +2793,7 @@ pub fn parse_into_binary_lockfile( // is chosen (dev -> optional -> prod -> peer) let mut seen_deps: bun_collections::StringArrayHashMap<()> = Default::default(); - // PORT NOTE: Zig grabs `pkgs.items(.meta)` / `.items(.resolution)` as + // Zig grabs `pkgs.items(.meta)` / `.items(.resolution)` as // mutable column slices, writes index 0, then keeps the resolution slice // for read-only lookups. In Rust the two `[0]` writes are done first via // sequential `&mut` accessors so the loops can take all column views @@ -3009,7 +3005,7 @@ pub fn parse_into_binary_lockfile( Ok(()) } -// PORT NOTE: Zig signature takes `*BinaryLockfile` plus a `*Dependency` that +// Zig signature takes `*BinaryLockfile` plus a `*Dependency` that // points into `lockfile.buffers.dependencies` — fine in Zig, illegal aliasing in // Rust. The function only touches `buffers.resolutions[dep_id]` and reads // `text_lockfile_version`, so accept those disjoint pieces directly and let the @@ -3086,7 +3082,7 @@ fn dependency_resolution_failure( Ok(()) } -// PORT NOTE: Zig threaded `string_buf: *String.Buf` separately from `lockfile`. +// Zig threaded `string_buf: *String.Buf` separately from `lockfile`. // In Rust the `Buf` borrows the same `lockfile.buffers.string_bytes` / // `string_pool` fields, so the two parameters alias. The `buf` parameter is // dropped and each append constructs a fresh `sbuf!(lockfile)` so the borrow @@ -3103,9 +3099,10 @@ fn parse_append_dependencies bundled_pkgs: Option<&PkgPathSet>, workspaces_obj: Option<&Expr>, ) -> Result<(u32, u32), ParseError> { - // PORT NOTE: defer optional_peers_buf.clearRetainingCapacity() moved to fn tail - // (and to each early-return path implicitly via clear-on-next-call semantics in caller). - // TODO(port): if exact defer semantics matter on error paths, wrap in scopeguard. + // Zig cleared `optional_peers_buf` via `defer ... clearRetainingCapacity()` on + // every exit path. Clearing on entry is observationally equivalent for all + // callers (none read the buf between calls) and also covers early-error exits. + optional_peers_buf.clear(); if let Some(optional_peers) = obj.get(b"optionalPeers") { if !optional_peers.is_array() { diff --git a/src/install/lockfile/bun.lockb.rs b/src/install/lockfile/bun.lockb.rs index 826bda09c35c..6b9c3a3a24f6 100644 --- a/src/install/lockfile/bun.lockb.rs +++ b/src/install/lockfile/bun.lockb.rs @@ -6,7 +6,7 @@ use core::mem::{align_of, size_of}; use bun_core::Error; use bun_io::Write as _; -// PORT NOTE: `Lockfile`/`Stream`/`StringPool`/`package_index` live in the parent +// `Lockfile`/`Stream`/`StringPool`/`package_index` live in the parent // `lockfile_real` module (this file is `lockfile_real::bun_lockb`). The // `bun_install::lockfile::*` path is the stub surface and lacks these items. use super::PatchedDep; @@ -24,13 +24,13 @@ use bun_core::strings; use bun_install::{PackageID, PackageManager, PackageNameAndVersionHash, PackageNameHash}; use bun_semver::{self as semver, String as SemverString}; -// TODO(port): z_allocator is a zeroing allocator (bun.z_allocator). In Rust, -// the equivalent is a wrapper that zeroes allocations. Provide -// `bun_alloc::ZAllocator` or ensure padding bytes are zeroed via -// `#[derive(zeroize)]` / explicit zeroing on the serialized structs. +// Zig used `bun.z_allocator` (a zeroing allocator) so serialized padding bytes +// were deterministic. There is no Rust equivalent here; instead the per-field +// save path zeroes padding explicitly (see the note in `save` and the +// `assert_no_uninitialized_padding` invariant in `Package::Serializer`). pub const VERSION: &[u8] = b"bun-lockfile-format-v0\n"; -// PORT NOTE: Zig: "#!/usr/bin/env bun\n" ++ version +// Zig: "#!/usr/bin/env bun\n" ++ version const HEADER_BYTES: &[u8] = b"#!/usr/bin/env bun\nbun-lockfile-format-v0\n"; // `@bitCast(@as([8]u8, "...".*))` → native-endian reinterpretation of 8 bytes as u64. @@ -46,7 +46,7 @@ const HAS_CONFIG_VERSION_TAG: u64 = u64::from_ne_bytes(*b"cNfGvRsN"); /// (`get_pos`/`pwrite`) and append semantics (`write_all`/`write_int_*`) for /// `Lockfile.Package.Serializer.save` / `Lockfile.Buffers.save`. /// -/// PORT NOTE: reshaped for borrowck — Zig held a separate `stream` and `writer` +/// reshaped for borrowck — Zig held a separate `stream` and `writer` /// over the same `bytes` simultaneously (legal in Zig, aliased `&mut` in Rust). /// Collapsed into a single type so callers pass exactly one `&mut StreamType`. /// @@ -174,17 +174,13 @@ pub fn save( total_size: &mut usize, end_pos: &mut usize, ) -> Result<(), Error> { - // we clone packages with the z_allocator to make sure bytes are zeroed. - // TODO: investigate if we still need this now that we have `padding_checker.zig` - // TODO(port): z_allocator clone — `MultiArrayList::clone` requires - // `Package: MultiArrayElement` (not yet derived). The Zig path only exists - // to zero padding bytes for byte-exact serialization; the per-field writers - // below already zero-pad via `assert_no_uninitialized_padding`, so skipping - // the clone is a no-op for correctness. Revisit once the derive lands. - // let old_packages_list = core::mem::replace(&mut this.packages, this.packages.clone_zeroed()?); - // drop(old_packages_list); - - // PORT NOTE: reshaped for borrowck — Zig holds `writer` and `stream` over + // Zig cloned `packages` with the zeroing z_allocator so padding bytes were + // deterministic for byte-exact serialization. The clone is intentionally + // not ported: the per-field writers below already zero-pad via the + // `assert_no_uninitialized_padding` invariant, so skipping it is a no-op + // for correctness. + + // reshaped for borrowck — Zig holds `writer` and `stream` over // the same `bytes` simultaneously; collapsed into a single `StreamType`. let mut stream = StreamType { bytes }; @@ -231,12 +227,12 @@ pub fn save( } } - // PORT NOTE: Zig passes `StreamType` (type) + `stream` (value) + + // Zig passes `StreamType` (type) + `stream` (value) + // `@TypeOf(writer)` + `writer` as separate comptime/runtime args. In Rust the // callees take a single `&mut S: PositionalStream + bun_io::Write` (both // roles collapsed onto `StreamType`) — two `&mut` aliases of one object would // be UB regardless of access order. - // PORT NOTE: turbofish — `this.packages` is `PackageList = List`, but + // turbofish — `this.packages` is `PackageList = List`, but // inference through `MultiArrayList>` is brittle when sibling // generic types in the crate have errors. Pin SemverIntType explicitly. package::serializer::save::(&this.packages, &mut stream)?; @@ -338,7 +334,7 @@ pub fn save( write_array::(&mut stream, catalog_deps.keys(), PREFIX_SEMVER_STRING)?; external_deps_buf.reserve(catalog_deps.count().saturating_sub(external_deps_buf.len())); - // PORT NOTE: Zig sets `items.len = count` then writes each slot via `dest.* = ...`. + // Zig sets `items.len = count` then writes each slot via `dest.* = ...`. // Reshape: push into the cleared Vec instead. for src in catalog_deps.values() { external_deps_buf.push(dependency::to_external(src)); @@ -376,9 +372,8 @@ pub(crate) fn load( log: &mut Log, mut manager: Option<&mut PackageManager>, ) -> Result { - // TODO(port): narrow error set let mut res = SerializerLoadResult::default(); - // PORT NOTE: Zig's `var reader = stream.reader();` is a thin view over the + // Zig's `var reader = stream.reader();` is a thin view over the // same buffer. `FixedBufferStream` exposes the read methods directly, so we // call them on `stream` to avoid holding a long-lived `&mut` borrow that // would conflict with the `stream.pos` / `stream.buffer` accesses below. @@ -406,7 +401,7 @@ pub(crate) fn load( } lockfile.format = FormatVersion::current(); - // PORT NOTE: `lockfile.allocator = allocator;` dropped — global mimalloc. + // `lockfile.allocator = allocator;` dropped — global mimalloc. let _ = stream.read_all(&mut lockfile.meta_hash)?; @@ -477,9 +472,8 @@ pub(crate) fn load( break 'workspace_versions_list versions_list; }; - // TODO(port): comptime type assertion that VersionHashMap key/value types - // match PackageNameHash / Semver.Version. Rust cannot express this as a - // const block without specialization; rely on type-checked + // Zig had a comptime assertion that VersionHashMap key/value types + // match PackageNameHash / Semver.Version; here we rely on the type-checked // `ensure_total_capacity` + slice copy below to enforce it. if workspace_package_name_hashes.len() != workspace_versions_list.len() { @@ -577,7 +571,7 @@ pub(crate) fn load( if next_num == HAS_OVERRIDES_TAG { let overrides_name_hashes: Vec = buffers::read_array(stream)?; - // PORT NOTE: Zig: `var map = lockfile.overrides.map; defer lockfile.overrides.map = map;` + // Zig: `var map = lockfile.overrides.map; defer lockfile.overrides.map = map;` // is a move-out/move-back pattern. In Rust we mutate in place. lockfile .overrides @@ -585,7 +579,7 @@ pub(crate) fn load( .ensure_total_capacity(overrides_name_hashes.len())?; let override_versions_external: Vec = buffers::read_array(stream)?; - // PORT NOTE: reshaped for borrowck — `Context.buffer` borrows + // reshaped for borrowck — `Context.buffer` borrows // `lockfile.buffers.string_bytes` while we also need // `&mut lockfile.overrides`. Split the disjoint fields up front so // borrowck sees sibling borrows (no raw-ptr provenance laundering). @@ -627,7 +621,7 @@ pub(crate) fn load( let patched_dependencies_name_and_version_hashes: Vec = buffers::read_array(stream)?; - // PORT NOTE: Zig: `var map = lockfile.patched_dependencies; defer lockfile.patched_dependencies = map;` + // Zig: `var map = lockfile.patched_dependencies; defer lockfile.patched_dependencies = map;` let map = &mut lockfile.patched_dependencies; map.ensure_total_capacity(patched_dependencies_name_and_version_hashes.len())?; @@ -663,7 +657,7 @@ pub(crate) fn load( let default_deps: Vec = buffers::read_array(stream)?; - // PORT NOTE: reshaped for borrowck — `dependency::Context` / + // reshaped for borrowck — `dependency::Context` / // `ArrayHashContext` borrow `lockfile.buffers.string_bytes` while // we also need `&mut lockfile.catalogs`. Split the disjoint // fields up front so borrowck sees sibling borrows (no raw-ptr @@ -709,7 +703,7 @@ pub(crate) fn load( let catalog_deps: Vec = buffers::read_array(stream)?; - // PORT NOTE: `CatalogMap::get_or_put_group` currently takes the + // `CatalogMap::get_or_put_group` currently takes the // stub `bun_install::lockfile::Lockfile`; inline its body here // against the split `catalogs` borrow to avoid the type // mismatch and the simultaneous `&mut lockfile` self-borrow. @@ -773,7 +767,7 @@ pub(crate) fn load( .package_index .ensure_total_capacity(lockfile.packages.len())?; - // PORT NOTE: reshaped for borrowck — Zig holds `slice.items(.name_hash)` / + // reshaped for borrowck — Zig holds `slice.items(.name_hash)` / // `slice.items(.resolution)` across `lockfile.getOrPutID(&mut self, …)`. // `get_or_put_id` only mutates `package_index` (and reads `packages` / // `buffers.string_bytes`), and `workspace_paths.put` only mutates diff --git a/src/install/lockfile/lockfile_json_stringify_for_debugging.rs b/src/install/lockfile/lockfile_json_stringify_for_debugging.rs index 8b4358ceabe7..67e3b1c54dde 100644 --- a/src/install/lockfile/lockfile_json_stringify_for_debugging.rs +++ b/src/install/lockfile/lockfile_json_stringify_for_debugging.rs @@ -15,12 +15,7 @@ use super::package::scripts::Scripts as PackageScripts; use super::tree::{DepthBuf, IteratorPathStyle, MAX_DEPTH}; use super::{FormatVersion, Lockfile, Package, package_index, tree}; -// TODO(refactor): `w: anytype` is a `std.json.WriteStream`-shaped writer. Introduce a -// `JsonWriter` trait in bun_core (or bun_collections) with the methods used below: -// begin_object/end_object/begin_array/end_array/object_field/write/write_null/print. -// `write` is generic over JSON-encodable scalars (bool, integers, &[u8]). -// -// PORT NOTE: Zig used `defer w.endObject() catch {}` so that closing braces are emitted +// Zig used `defer w.endObject() catch {}` so that closing braces are emitted // on both success and error paths (with the close error swallowed). In Rust the `?` // early-return drops the borrow on `w`, and a scopeguard would need a second `&mut w`. // Since this output is debug-only and an error mid-stream already yields malformed JSON, @@ -34,7 +29,6 @@ fn json_stringify_dependency( dep: &Dependency, res: PackageID, ) -> Result<(), bun_core::Error> -// TODO(port): narrow error set where W: JsonWriter, { @@ -195,7 +189,6 @@ where } pub fn json_stringify(this: &Lockfile, w: &mut W) -> Result<(), bun_core::Error> -// TODO(port): narrow error set where W: JsonWriter, { diff --git a/src/install/lockfile/printer/Yarn.rs b/src/install/lockfile/printer/Yarn.rs index a0c0c17f85cc..0e1c72c04e3d 100644 --- a/src/install/lockfile/printer/Yarn.rs +++ b/src/install/lockfile/printer/Yarn.rs @@ -11,23 +11,32 @@ use bun_install::PackageID; use bun_install::Resolution; use bun_install::dependency::{self, Behavior, VersionExt as _}; use bun_install::lockfile::package; -// PORT NOTE: `lockfile.packages.slice()` returns +// `lockfile.packages.slice()` returns // `bun_collections::multi_array_list::Slice>`; the `items_()` // column accessors are an extension trait (Zig's `slice.items(.field)` is // comptime-dispatched, Rust models it as a hand-expanded trait per Package.rs). use crate::integrity; use crate::lockfile_real::Printer; -// TODO(port): narrow error set (only writer + alloc errors are produced) pub fn print(this: &mut Printer, writer: &mut impl bun_io::Write) -> Result<(), bun_core::Error> { // internal for debugging, print the lockfile as custom json // limited to debug because we don't want people to rely on this format. + // Zig: `std.json.Stringify.write(this.lockfile)` dispatches to Lockfile's + // custom `jsonStringify`; the Rust port calls it directly. #[cfg(debug_assertions)] - { - // TODO(port): std.process.hasEnvVarConstant("JSON") + std.json.Stringify - // have no direct equivalent here; wire up bun_core::env_var + a JSON - // serializer for Lockfile if this debug path is still wanted. - let _ = &this.lockfile; + if std::env::var_os("JSON").is_some() { + use crate::lockfile_real::lockfile_json_stringify_for_debugging::{ + WriteStream, WriteStreamOptions, + }; + let mut stream = WriteStream::new(WriteStreamOptions { + indent: 2, + emit_null_optional_fields: true, + emit_nonportable_numbers_as_strings: true, + }); + crate::lockfile_real::json_stringify(this.lockfile, &mut stream)?; + writer.write_all(&stream.into_bytes())?; + writer.write_all(b"\n")?; + return Ok(()); } writer.write_all( @@ -53,7 +62,7 @@ fn packages(this: &mut Printer, writer: &mut impl bun_io::Write) -> Result<(), b let dependencies_buffer: &[Dependency] = this.lockfile.buffers.dependencies.as_slice(); // Zig: std.HashMap(PackageID, []Dependency.Version, IdentityContext(PackageID), 80) - // PORT NOTE: reshaped for borrowck — store (start, len) into + // Reshaped for borrowck — store (start, len) into // `all_requested_versions_buf` instead of overlapping &mut [Version] slices. let mut requested_versions: HashMap = HashMap::default(); diff --git a/src/install/lockfile/printer/tree_printer.rs b/src/install/lockfile/printer/tree_printer.rs index 2e42c74d635b..b3d12eb964f2 100644 --- a/src/install/lockfile/printer/tree_printer.rs +++ b/src/install/lockfile/printer/tree_printer.rs @@ -8,16 +8,14 @@ use bun_install::{ self as install, Bin, Dependency, DependencyID, INVALID_PACKAGE_ID, PackageID, PackageManager, PackageNameHash, Resolution, bin, resolution, }; -// PORT NOTE: Zig `slice.items(.field)` → trait-provided `items_()` -// accessors on `MultiArrayList` / its `Slice`. use crate::lockfile_real::package::PackageColumns as _; use crate::package_manager_real::TrackInstalledBin; use bun_sys::Fd; type Bitset = DynamicBitSet; -// PORT NOTE: `comptime print_section_header: enum(u1) { print_section_header, dont_print_section_header }` -// is a two-state comptime flag; mapped to `const PRINT_SECTION_HEADER: bool`. +// Zig's `comptime print_section_header: enum(u1)` two-state flag is mapped to +// `const PRINT_SECTION_HEADER: bool`. fn print_installed_workspace_section< W, const ENABLE_ANSI_COLORS: bool, @@ -34,13 +32,11 @@ fn print_installed_workspace_section< where W: Write, { - // TODO(port): narrow error set let lockfile = &this.lockfile; let string_buf = lockfile.buffers.string_bytes.as_slice(); let packages_slice = lockfile.packages.slice(); let resolutions = lockfile.buffers.resolutions.as_slice(); let dependencies = lockfile.buffers.dependencies.as_slice(); - // PORT NOTE: Zig `slice.items(.field)` → derive(MultiArrayElement)-generated `items_()`. let workspace_res = &packages_slice.items_resolution()[workspace_package_id as usize]; let names = packages_slice.items_name(); let pkg_metas = packages_slice.items_meta(); @@ -56,7 +52,7 @@ where // While both are technically installed, only one was chosen and should be printed. let mut dep_dedupe: HashMap = HashMap::new(); - // PORT NOTE: reshaped for borrowck — `id_map` is reborrowed per call below. + // Reshaped for borrowck — `id_map` is reborrowed per call below. let mut id_map = id_map; // find the updated packages @@ -250,7 +246,6 @@ fn print_updated_package( where W: Write, { - // TODO(port): narrow error set let string_buf = this.lockfile.buffers.string_bytes.as_slice(); let dependency = &this.lockfile.buffers.dependencies.as_slice()[update_info.dependency_id as usize]; @@ -289,7 +284,6 @@ fn print_installed_package( where W: Write, { - // TODO(port): narrow error set let string_buf = this.lockfile.buffers.string_bytes.as_slice(); let packages_slice = this.lockfile.packages.slice(); let resolution: Resolution = packages_slice.items_resolution()[package_id as usize]; @@ -353,7 +347,6 @@ pub fn print( where W: Write, { - // TODO(port): narrow error set writer.write_str("\n")?; // `allocator` param dropped — global mimalloc. let slice = this.lockfile.packages.slice(); @@ -536,7 +529,7 @@ where done: false, dir_iterator: None, package_name: name, - // PORT NOTE: Zig default `bun.invalid_fd.stdDir()` — never read on + // Zig default `bun.invalid_fd.stdDir()` — never read on // the .map/.file/.named_file paths this arm covers. destination_node_modules: Fd::INVALID, buf: bun_paths::PathBuffer::uninit(), @@ -556,7 +549,7 @@ where { if matches!(manager.track_installed_bin, TrackInstalledBin::Pending) { - // PORT NOTE: `iterator.next()` returns `Result, E>` (Zig `!?[]const u8`); + // `iterator.next()` returns `Result, E>` (Zig `!?[]const u8`); // `catch null` → `.unwrap_or(None)`. Reshaped for borrowck — `bin_name`'s // borrow of `iterator.buf` must end before the loop's `iterator.next()`. if let Some(bin_name) = iterator.next().unwrap_or(None) { diff --git a/src/install/migration.rs b/src/install/migration.rs index 06062c2c57dc..fee3e1cd34e9 100644 --- a/src/install/migration.rs +++ b/src/install/migration.rs @@ -235,7 +235,6 @@ pub(crate) fn migrate_npm_lockfile<'a>( data: &[u8], abs_path: &[u8], ) -> Result, Error> { - // TODO(port): narrow error set debug!("begin lockfile migration"); this.init_empty(); @@ -309,7 +308,7 @@ pub(crate) fn migrate_npm_lockfile<'a>( let json_array = match &wksp.data { ExprData::EArray(arr) => *arr, ExprData::EObject(obj) => { - // PORT NOTE: `StoreRef::get` shadows `E::Object::get`; deref-coerce. + // `StoreRef::get` shadows `E::Object::get`; deref-coerce. let obj: &E::Object = obj; if let Some(packages) = obj.get(b"packages") { match &packages.data { @@ -368,7 +367,7 @@ pub(crate) fn migrate_npm_lockfile<'a>( else { return Err(err!("InvalidNPMLockfile")); }; - // PORT NOTE: `StoreRef::get` shadows `E::Object::get`; deref-coerce. + // `StoreRef::get` shadows `E::Object::get`; deref-coerce. let pkg: &E::Object = pkg; if pkg.get(b"link").is_some() { @@ -503,7 +502,7 @@ pub(crate) fn migrate_npm_lockfile<'a>( // dependency on `resolved`, a dependencies version tag might change, requiring // new strings to be allocated. - // PORT NOTE: reshaped for borrowck — `string_buf()` borrows `this` mutably, + // Reshaped for borrowck — `string_buf()` borrows `this` mutably, // so we re-acquire it locally where needed instead of holding it across // other `this.*` mutations. @@ -549,7 +548,7 @@ pub(crate) fn migrate_npm_lockfile<'a>( else { unreachable!("npm lockfile: non-object Expr from JSON parser") }; - // PORT NOTE: `StoreRef::get` shadows `E::Object::get`; deref-coerce. + // `StoreRef::get` shadows `E::Object::get`; deref-coerce. let pkg: &E::Object = pkg; let pkg_path = entry @@ -839,36 +838,14 @@ pub(crate) fn migrate_npm_lockfile<'a>( debug_assert!(this.packages.len() == package_idx as usize); } - // ignoring length check because we pre-allocated it. the length may shrink later - // so it's faster if we ignore the underlying length buffer and just assign it at the very end. - // PORT NOTE: reshaped for borrowck — track cursor indices into reserved capacity instead of - // shrinking slices via pointer arithmetic. - let dependencies_base: *mut Dependency = this.buffers.dependencies.as_mut_ptr(); - let resolutions_base: *mut PackageID = this.buffers.resolutions.as_mut_ptr(); - let mut deps_cursor: usize = 0; - let mut res_cursor: usize = 0; - // TODO(port): Stacked-Borrows audit — these raw ptrs into - // `buffers.{dependencies,resolutions}` and the `packages` columns below are - // held across `&mut self` calls to `string_buf()` / `get_or_put_id()`. The - // fields actually touched are disjoint (string_bytes/string_pool resp. - // package_index + read of resolutions), so this is sound under Tree - // Borrows and matches the Zig spec, but SB retags through `Unique`. - // Fix by split-borrowing the disjoint fields (see the `bin` path above) or - // by setting Vec lengths up-front and indexing safely. - - // pre-initialize the dependencies and resolutions to `unset_package_id` - #[cfg(debug_assertions)] - { - // SAFETY: capacity reserved above for num_deps - unsafe { - for i in 0..(num_deps as usize) { - core::ptr::write(dependencies_base.add(i), Dependency::default()); - core::ptr::write(resolutions_base.add(i), UNSET_PACKAGE_ID); - } - } - } + // Zig pre-allocated both buffers, wrote through raw cursor pointers, and + // assigned the final length at the very end. The Rust port pushes into the + // pre-reserved Vecs instead: capacity for `num_deps` was reserved above so + // pushes never reallocate, every cursor bump in the Zig code immediately + // followed a write (so `len()` tracks the cursor exactly), and no raw + // pointers are held across `&mut self` calls. - // PORT NOTE: MultiArrayList column access — re-borrow the (disjoint) `resolution`, + // MultiArrayList column access — re-borrow the (disjoint) `resolution`, // `meta`, `dependencies` and `resolutions` columns of `this.packages` at each use // site via the generated `items_*` / `items_*_mut` accessors. Capacity is fixed // (no further append until end of fn), so the slices stay valid; re-acquiring per @@ -910,7 +887,7 @@ pub(crate) fn migrate_npm_lockfile<'a>( else { unreachable!("npm lockfile: non-object Expr from JSON parser") }; - // PORT NOTE: `StoreRef::get` shadows `E::Object::get`; deref-coerce. + // `StoreRef::get` shadows `E::Object::get`; deref-coerce. let pkg: &E::Object = pkg; if pkg.get(b"link").is_some() || is_skipped_pkg(pkg) { @@ -924,26 +901,28 @@ pub(crate) fn migrate_npm_lockfile<'a>( .as_string(&arena) .unwrap(); - let dependencies_start = deps_cursor; - let resolutions_start = res_cursor; + let dependencies_start = this.buffers.dependencies.len(); + let resolutions_start = this.buffers.resolutions.len(); - // PORT NOTE: Zig used `defer` here to write dependencies_list/resolution_list and + // Zig used `defer` here to write dependencies_list/resolution_list and // increment package_idx at every loop exit. Reshaped for borrowck — inlined as // `finalize_pkg!` at the one early-continue and at natural end-of-loop. macro_rules! finalize_pkg { () => {{ // package_idx < pkg_count; columns re-borrowed disjointly per statement. - if dependencies_start == deps_cursor { + if dependencies_start == this.buffers.dependencies.len() { this.packages.items_dependencies_mut()[package_idx as usize] = ExternalSlice::default(); this.packages.items_resolutions_mut()[package_idx as usize] = ExternalSlice::default(); } else { - let len: u32 = (res_cursor - resolutions_start) as u32; + let len: u32 = (this.buffers.resolutions.len() - resolutions_start) as u32; #[cfg(debug_assertions)] { debug_assert!(len > 0); - debug_assert!(len == (deps_cursor - dependencies_start) as u32); + debug_assert!( + len == (this.buffers.dependencies.len() - dependencies_start) as u32 + ); } this.packages.items_dependencies_mut()[package_idx as usize] = ExternalSlice::new(dependencies_start as u32, len); @@ -996,27 +975,19 @@ pub(crate) fn migrate_npm_lockfile<'a>( let mut sb = this.string_buf(); let wksp_name = sb.append(&value.name)?; let wksp_path = sb.append(key)?; - // SAFETY: deps_cursor < num_deps; capacity reserved above - unsafe { - core::ptr::write( - dependencies_base.add(deps_cursor), - Dependency { - name: wksp_name, - name_hash, - version: DepVersion { - tag: DepTag::Workspace, - literal: wksp_path, - value: DepValue { - workspace: wksp_path, - }, - }, - behavior: Behavior::WORKSPACE, + this.buffers.dependencies.push(Dependency { + name: wksp_name, + name_hash, + version: DepVersion { + tag: DepTag::Workspace, + literal: wksp_path, + value: DepValue { + workspace: wksp_path, }, - ); - core::ptr::write(resolutions_base.add(res_cursor), entry1.new_package_id); - } - deps_cursor += 1; - res_cursor += 1; + }, + behavior: Behavior::WORKSPACE, + }); + this.buffers.resolutions.push(entry1.new_package_id); } } } @@ -1154,7 +1125,7 @@ pub(crate) fn migrate_npm_lockfile<'a>( let behavior = dep_key.behavior; - // PORT NOTE: capture tag and git/github owner before moving + // Capture tag and git/github owner before moving // `version` into the buffer (Zig copies the struct by value; Rust // moves it). The owner is needed when `version.tag` is git/github // but the package's `resolved` URL infers as something else, in @@ -1167,21 +1138,13 @@ pub(crate) fn migrate_npm_lockfile<'a>( _ => SemverString::default(), }; - // SAFETY: cursor < num_deps; capacity reserved - unsafe { - core::ptr::write( - dependencies_base.add(deps_cursor), - Dependency { - name: dep_name, - name_hash, - version, - behavior, - }, - ); - core::ptr::write(resolutions_base.add(res_cursor), id); - } - deps_cursor += 1; - res_cursor += 1; + this.buffers.dependencies.push(Dependency { + name: dep_name, + name_hash, + version, + behavior, + }); + this.buffers.resolutions.push(id); // If the package resolution is not set, resolve the target package // using the information we have from the dependency declaration. @@ -1473,24 +1436,15 @@ pub(crate) fn migrate_npm_lockfile<'a>( }; if b.value { let behavior = Behavior::OPTIONAL | Behavior::PEER; - // SAFETY: cursor < num_deps; capacity reserved - unsafe { - core::ptr::write( - dependencies_base.add(deps_cursor), - Dependency { - name: dep_name, - name_hash, - version, - behavior, - }, - ); - core::ptr::write( - resolutions_base.add(res_cursor), - Install::INVALID_PACKAGE_ID, - ); - } - deps_cursor += 1; - res_cursor += 1; + this.buffers.dependencies.push(Dependency { + name: dep_name, + name_hash, + version, + behavior, + }); + this.buffers + .resolutions + .push(Install::INVALID_PACKAGE_ID); continue 'dep_loop; } } @@ -1516,17 +1470,10 @@ pub(crate) fn migrate_npm_lockfile<'a>( finalize_pkg!(); } - // SAFETY: res_cursor elements written above into reserved capacity - unsafe { - this.buffers.resolutions.set_len(res_cursor); - this.buffers.dependencies.set_len(res_cursor); - } - - // In allow_assert, we prefill this buffer with uninitialized values that we can detect later // It is our fault if we hit an error here, making it safe to disable in release. #[cfg(debug_assertions)] { - debug_assert!(this.buffers.dependencies.len() == deps_cursor); + debug_assert!(this.buffers.dependencies.len() == this.buffers.resolutions.len()); debug_assert!(this.buffers.dependencies.len() <= num_deps as usize); let mut crash = false; for (i, r) in this.buffers.dependencies.iter().enumerate() { @@ -1593,7 +1540,6 @@ pub(crate) fn migrate_npm_lockfile<'a>( Ok(LoadResult::Ok(LoadResultOk { lockfile: this, - // TODO(port): lifetime — LoadResult holds &mut Lockfile in Zig; verify Rust ownership migrated: Migrated::Npm, loaded_from_binary_lockfile: false, serializer_result: Default::default(), diff --git a/src/install/npm.rs b/src/install/npm.rs index 452a088a415c..264462a4dd76 100644 --- a/src/install/npm.rs +++ b/src/install/npm.rs @@ -4,7 +4,7 @@ use std::io::Write as _; use crate::bun_json as JSON; use crate::bun_schema::api; use bun_alloc::AllocError; -use bun_collections::{HashMap, StringSet}; +use bun_collections::{HashMap, IdentityContext, StringSet}; use bun_core::{Error, Global, Output, err, fmt as bun_fmt}; use bun_core::{MutableString, strings}; use bun_dotenv::Loader as DotEnv; @@ -342,7 +342,7 @@ pub mod registry { } } - // PORT NOTE: Zig's `URL.parse(registry.url)` borrows the input + // Zig's `URL.parse(registry.url)` borrows the input // `[]const u8`; here `url` borrows the owned `registry_url` buffer // for the duration of parsing. The final href is moved into // `Scope.url: OwnedURL` (owned `Box<[u8]>`). @@ -453,7 +453,7 @@ pub mod registry { } } - // PORT NOTE: reshaped for borrowck — Zig's `defer { url.pathname = pathname; url.path = pathname; }` + // reshaped for borrowck — Zig's `defer { url.pathname = pathname; url.path = pathname; }` // is applied at every `break 'outer` above and once more here at fallthrough. url.pathname = pathname; url.path = pathname; @@ -494,7 +494,7 @@ pub mod registry { let final_href: Box<[u8]> = if needs_normalize { url.href_without_auth() } else { - // PORT NOTE: reshaped for borrowck — `url` (borrowing + // reshaped for borrowck — `url` (borrowing // `registry_url`) is dead on this branch (every path that // mutated `url.pathname` also set `needs_normalize = true`). registry_url @@ -513,8 +513,8 @@ pub mod registry { } } - // TODO(port): Zig used `IdentityContext(u64)` hasher; std HashMap is fine for now. - pub type Map = HashMap; + // Keys are pre-hashed (`Scope::hash`), so don't re-hash them. + pub type Map = HashMap>; pub(crate) enum PackageVersionResponse { Cached(PackageManifest), @@ -532,7 +532,6 @@ pub mod registry { package_manager: &mut PackageManager, is_extended_manifest: bool, ) -> Result { - // TODO(port): narrow error set match response.status_code { 400 => return Err(err!("BadRequest")), 429 => return Err(err!("TooManyRequests")), @@ -914,9 +913,10 @@ pub mod package_manifest { const HEADER_BYTES: &'static str = concat!("#!/usr/bin/env bun\n", "bun-npm-manifest-cache-v0.0.7\n"); - // TODO(port): `sizes` was a comptime block iterating PackageManifest's fields by alignment. - // Rust cannot reflect struct fields. Hardcode the field order produced by the Zig sort - // (descending alignment); re-verify against the Zig output if the layout changes. + // Zig's `sizes` was a comptime block iterating PackageManifest's fields by + // alignment; Rust cannot reflect struct fields, so this hardcodes the field + // order produced by the Zig sort (descending alignment). Re-verify against + // the Zig output if the layout changes. pub const SIZES_FIELDS: &'static [&'static str] = &[ "pkg", "string_buf", @@ -1000,7 +1000,7 @@ pub mod package_manifest { pos += 128 / 8; - // TODO(port): inline-for over SIZES_FIELDS — unrolled by hand. Verify field + // Zig inline-for over SIZES_FIELDS — unrolled by hand. Verify field // order matches Zig comptime sort (descending alignment) if the layout changes. { // "pkg" @@ -1235,19 +1235,22 @@ pub mod package_manifest { Batch as PoolBatch, Node as PoolNode, Task as PoolTask, }; - pub(crate) struct SaveTask<'a> { + pub(crate) struct SaveTask { manifest: PackageManifest, - scope: &'a registry::Scope, + // Owned: the thread-pool task can outlive the caller's borrow + // of the Registry.Scope (Zig assumed the Scope was effectively + // immortal; owning a clone removes that assumption). + scope: registry::Scope, tmpdir: Fd, cache_dir: Fd, task: PoolTask, } - bun_threading::intrusive_work_task!(['a] SaveTask<'a>, task); + bun_threading::intrusive_work_task!(SaveTask, task); - impl<'a> SaveTask<'a> { - pub(crate) fn new(init: SaveTask<'a>) -> Box> { + impl SaveTask { + pub(crate) fn new(init: SaveTask) -> Box { Box::new(init) } @@ -1269,7 +1272,7 @@ pub mod package_manifest { if let Err(err) = Serializer::save( &save_task.manifest, - save_task.scope, + &save_task.scope, save_task.tmpdir, save_task.cache_dir, ) { @@ -1285,11 +1288,9 @@ pub mod package_manifest { } } - // TODO(port): lifetime — `scope` is borrowed across a thread boundary; Zig assumed - // the Registry.Scope outlives the threadpool task. Prove or change to owned. let task = bun_core::heap::into_raw(SaveTask::new(SaveTask { manifest: this.clone(), - scope, + scope: scope.clone(), tmpdir, cache_dir, task: PoolTask { @@ -1398,7 +1399,6 @@ pub mod package_manifest { return Ok(None); } - // TODO(port): manifest borrows `bytes` in Zig; here read_all copies into Box<[T]>. Ok(Some(manifest)) } @@ -1424,7 +1424,8 @@ pub mod package_manifest { return Ok(None); } - // TODO(port): inline-for over SIZES_FIELDS — unrolled by hand + // Zig inline-for over SIZES_FIELDS — unrolled by hand; keep the order + // in sync with `Serializer::write` and SIZES_FIELDS. { // std.mem.alignForward(usize, pos, alignOf(NpmPackage)) pkg_stream.pos = pkg_stream @@ -1942,8 +1943,8 @@ impl PackageManifest { } } -// TODO(port): Zig used `IdentityContext(u64)` hasher; std HashMap is fine for now. -type ExternalStringMapDeduper = HashMap; +// Keys are pre-hashed string hashes, so don't re-hash them. +type ExternalStringMapDeduper = HashMap>; use bun_install_types::DependencyGroup; /// Abbreviated registry metadata never carries `devDependencies` — keep this 3-wide intentionally. @@ -1965,19 +1966,17 @@ impl PackageManifest { public_max_age: u32, is_extended_manifest: bool, ) -> Result, Error> { - // TODO(port): narrow error set // `bun_ast::Source::init_path_string` accepts borrowed `&[u8]` via // `IntoStr`; the Source only lives for the duration of this function, // so pass the caller's buffers through directly without manufacturing // `'static` references here (PORTING.md §Forbidden lifetime extension). let source = bun_ast::Source::init_path_string(expected_name, json_buffer); initialize_store(); - // TODO(port): bun.ast.Stmt.Data.Store.memory_allocator.?.pop() — Zig - // pushed/popped the AST arena around the parse so the JSON AST is - // bulk-freed on return. `initialize_mini_store` already does the push; - // the pop is handled by resetting on the next call. Wire an explicit - // RAII guard once `ASTMemoryAllocator::pop` is exposed. - // PERF(port): was arena bulk-free — profile if hot. + // Zig deferred `Store.memory_allocator.?.pop()` so the JSON AST arena was + // bulk-freed on return. The Rust `initialize_mini_store` deliberately keeps + // the allocator pushed across calls (the AstAlloc state stays installed for + // the re-arm) and bulk-frees via `reset_retain_with_limit` on the next + // call — see `initialize_mini_store` in lib.rs for why. let bump = bun_alloc::Arena::new(); let json = match JSON::parse_utf8(&source, log, &bump) { Ok(j) => j, @@ -2001,8 +2000,10 @@ impl PackageManifest { } } + // Zig used `bun.serializable()` to zero padding for cache determinism; the + // serialized `NpmPackage` has explicit `_padding_*` fields zeroed by + // `Default`, so plain `default()` is already deterministic here. let mut result: PackageManifest = PackageManifest::default(); - // TODO(port): bun.serializable() — zero-init for serialization determinism let mut all_extern_strings_dedupe_map = ExternalStringMapDeduper::default(); let mut version_extern_strings_dedupe_map = ExternalStringMapDeduper::default(); @@ -2348,10 +2349,10 @@ impl PackageManifest { vec![PackageNameHash::default(); bundled_deps_count].into_boxed_slice(); let mut bundled_deps_offset: usize = 0; - // PORT NOTE: Zig manually @memset zeroed the buffers; Default::default() above achieves + // Zig manually @memset zeroed the buffers; Default::default() above achieves // the same determinism for these POD types. - // PORT NOTE: reshaped for borrowck — Zig used overlapping mutable subslices into the + // reshaped for borrowck — Zig used overlapping mutable subslices into the // same allocation. Rust uses index cursors instead and re-slices on demand. let mut versioned_package_releases_start: usize = 0; let all_versioned_package_releases_range = 0..release_versions_len; @@ -2376,7 +2377,7 @@ impl PackageManifest { string_builder.allocate()?; - // PORT NOTE: Zig zeroed the freshly allocated buffer for determinism; + // Zig zeroed the freshly allocated buffer for determinism; // `Builder::allocate` already produces a zeroed `Box<[u8]>`. // // Zig kept a single `string_buf` slice over the builder's backing @@ -2414,7 +2415,9 @@ impl PackageManifest { bin: Bin::init(), ..PackageVersion::default() }; - // TODO(port): bun.serializable() on empty_version + // Zig: `bun.serializable(PackageVersion{...})` — PackageVersion's + // explicit `_padding_*` fields are zeroed by `Default`, so no + // separate padding scrub is needed. for prop in versions { let Some(version_name) = prop @@ -2568,7 +2571,7 @@ impl PackageManifest { }; let mut group_i: u32 = 0; - // PORT NOTE: Zig wrote through a raw `*[len]ExternalString` + // Zig wrote through a raw `*[len]ExternalString` // sub-pointer. The boxed slice is fully initialised // (`vec![Default; n].into_boxed_slice()` in the counting // pass) and `ExternalString: Copy`, so plain absolute @@ -2775,7 +2778,7 @@ impl PackageManifest { // iteration's slice of `bundled_deps_buf`, so an // unconditional empty pass would clobber the value the // `dependencies` iteration just produced. - // PORT NOTE: hoist `versioned_deps` so the borrowed + // hoist `versioned_deps` so the borrowed // `obj.properties.slice()` outlives the labelled block. let versioned_deps = prop .value @@ -2806,7 +2809,7 @@ impl PackageManifest { } }; if items.len() > 0 || has_meta_only_peers { - // PORT NOTE: reshaped for borrowck — index into all_extern_strings / version_extern_strings + // reshaped for borrowck — index into all_extern_strings / version_extern_strings let names_base = dependency_names_cursor; let values_base = dependency_values_cursor; @@ -3101,10 +3104,84 @@ impl PackageManifest { _ => unreachable!("non-value Expr from JSON parser"), } - // TODO(port): debug-assertions block (Zig lines 2478-2522) elided — - // it re-reads `this_names`/`this_versions` via `mut()` after dedupe. - // Re-add with cursor-based slicing if needed. - let _ = (this_names, this_versions); + // Zig `allow_assert` block: the dedupe must hand back + // lists resolving to identical contents. The string + // pool dedupes equal strings to equal offsets, so + // field equality holds even when `name_list` points + // at an earlier entry's region. + #[cfg(debug_assertions)] + { + let dependencies_list = map; + debug_assert!( + (dependencies_list.name.off as usize) < all_extern_strings.len() + ); + debug_assert!( + (dependencies_list.value.off as usize) < all_extern_strings.len() + ); + debug_assert!( + dependencies_list.name.off as usize + + (dependencies_list.name.len as usize) + < all_extern_strings.len() + ); + debug_assert!( + dependencies_list.value.off as usize + + (dependencies_list.value.len as usize) + < all_extern_strings.len() + ); + + let name_dependencies = + dependencies_list.name.get(&all_extern_strings); + let value_dependencies = + dependencies_list.value.get(&version_extern_strings); + + // std.meta.eql — element-wise field equality. + debug_assert!(name_dependencies.len() == this_names.len()); + debug_assert!(value_dependencies.len() == this_versions.len()); + for (a, b) in name_dependencies.iter().zip(this_names.iter()) { + debug_assert!(a.hash == b.hash && a.value == b.value); + } + for (a, b) in value_dependencies.iter().zip(this_versions.iter()) { + debug_assert!(a.hash == b.hash && a.value == b.value); + } + + // Per-element string-content checks against the + // source JSON. Skipped (as in Zig) when meta-only + // optional peers may have been synthesised, since + // `items[j]` correspondence no longer holds then. + if !is_peer || optional_peer_dep_names.is_empty() { + let string_buf: &[u8] = string_builder.allocated_slice(); + for (j, dep_name) in name_dependencies.iter().enumerate() { + debug_assert!( + dep_name.value.slice(string_buf) + == this_names[j].value.slice(string_buf) + ); + debug_assert!( + dep_name.value.slice(string_buf) + == items[j] + .key + .as_ref() + .expect("infallible: prop has key") + .as_string(&bump) + .expect("dependency key must be a string") + ); + } + for (j, dep_version) in value_dependencies.iter().enumerate() { + debug_assert!( + dep_version.value.slice(string_buf) + == this_versions[j].value.slice(string_buf) + ); + debug_assert!( + dep_version.value.slice(string_buf) + == items[j] + .value + .as_ref() + .expect("infallible: prop has value") + .as_string(&bump) + .expect("dependency value must be a string") + ); + } + } + } } } @@ -3133,7 +3210,7 @@ impl PackageManifest { extern_strings_consumed = dependency_names_cursor; // version_extern_strings trimmed below - // PORT NOTE: Zig: version_extern_strings = version_extern_strings[0 .. len - dependency_values.len] + // Zig: version_extern_strings = version_extern_strings[0 .. len - dependency_values.len] } let version_extern_strings_len = dependency_values_cursor; @@ -3192,8 +3269,23 @@ impl PackageManifest { ), }; - if cfg!(debug_assertions) { - // TODO(port): std.meta.eql sanity checks elided + #[cfg(debug_assertions)] + { + // Zig: std.meta.eql round-trip of the just-initialized + // slices. `init` is a pointer-offset round-trip, so slice + // identity (ptr + len) implies element equality. + let tags_rt = result.pkg.dist_tags.tags.get(&all_extern_strings); + debug_assert!(core::ptr::eq( + tags_rt.as_ptr(), + all_extern_strings[extern_strings_slice_start..].as_ptr() + )); + debug_assert!(tags_rt.len() == dist_tag_i); + let versions_rt = result.pkg.dist_tags.versions.get(&all_semver_versions); + debug_assert!(core::ptr::eq( + versions_rt.as_ptr(), + all_semver_versions[dist_tag_versions_start..].as_ptr() + )); + debug_assert!(versions_rt.len() == dist_tag_i); } extern_strings_cursor += dist_tag_i; @@ -3307,7 +3399,7 @@ impl PackageManifest { string_bytes, ) }); - // PORT NOTE: Zig sorted indices against semver_versions_ (which is unmutated + // Zig sorted indices against semver_versions_ (which is unmutated // until after sort) — equivalent to sorting against cloned_versions. debug_assert_eq!(indices.len(), versioned_packages_.len()); @@ -3363,7 +3455,7 @@ impl PackageManifest { } // all_extern_strings = all_extern_strings[0 .. len - extern_strings_remaining] - // PORT NOTE: trim by truncating the boxed slice via Vec round-trip + // trim by truncating the boxed slice via Vec round-trip let new_len = all_extern_strings.len() - extern_strings_remaining; let mut v = core::mem::take(&mut all_extern_strings).into_vec(); v.truncate(new_len); @@ -3394,9 +3486,6 @@ impl PackageManifest { result.pkg.has_extended_manifest = is_extended_manifest; if let Some(buf) = string_builder.ptr.take() { - // TODO(port): string_builder owns this allocation; copy out the - // written prefix. Add a `Builder::into_owned()` that yields - // `Box<[u8]>` without the truncate copy. let mut v = buf.into_vec(); v.truncate(string_builder.len); result.string_buf = v.into_boxed_slice(); diff --git a/src/install/padding_checker.rs b/src/install/padding_checker.rs index 894340bd691b..1d3f6fe3763c 100644 --- a/src/install/padding_checker.rs +++ b/src/install/padding_checker.rs @@ -31,23 +31,25 @@ //! There is one other way to introduce undefined memory into a struct, which this does not check for, and that is //! a union with unequal size fields. -// TODO(port): The Zig implementation is pure `comptime` reflection over `@typeInfo(T)` — +// The Zig implementation is pure `comptime` reflection over `@typeInfo(T)` — // it walks struct/union/array/optional/pointer field trees, recurses into children, and // `@compileError`s on any gap between `@offsetOf(T, field) + @sizeOf(field)` and the next // field's offset (and between the last field's end and `@sizeOf(T)`). // -// Rust has no `@typeInfo` equivalent. TODO(port): provide this as a proc-macro derive -// (`#[derive(AssertNoUninitializedPadding)]`) that emits the `const _: () = assert!(...)` -// checks below per-field, plus a marker trait so `assert_no_uninitialized_padding::()` -// is bounded on it. The free function here is kept as the call-site-compatible entry point. +// Rust has no `@typeInfo` equivalent, so the layout checking is done differently: +// per-field `const _: () = assert!(offset_of!...)` proofs live next to each serialized +// struct (e.g. `NpmPackage` / `PackageVersion` in npm.rs), and the `layout_asserts` +// module below pins every serialized type's size/align against the Zig extern-struct +// spec. The free function here is kept as the call-site-compatible entry point. /// Marker trait asserting that `Self` is `#[repr(C)]` (or `#[repr(transparent)]`/packed), /// contains no pointer fields, and has no implicit padding bytes anywhere in its layout -/// (recursively). Implemented by `#[derive(AssertNoUninitializedPadding)]`. +/// (recursively). /// /// # Safety -/// Implementing this by hand asserts the layout invariants above without the derive's -/// compile-time checks. Only do so for primitives and manually-audited `#[repr(C)]` types. +/// Implementing this by hand asserts the layout invariants above without compile-time +/// checks. Only do so for primitives and manually-audited `#[repr(C)]` types whose +/// explicit `_padding_*` fields are proven gap-free by `const` offset asserts. pub unsafe trait AssertNoUninitializedPadding {} /// Assertion that `T` has no uninitialized padding. See module docs. @@ -57,24 +59,25 @@ pub unsafe trait AssertNoUninitializedPadding {} /// `.pointer => |ptr| assertNoUninitializedPadding(ptr.child)` arm so callers could /// pass `@TypeOf(slice)` directly. /// -/// In Rust the actual layout checking lives in `#[derive(AssertNoUninitializedPadding)]` -/// on each serialized struct; this function is a zero-cost call-site marker that +/// In Rust the actual layout checking lives in the per-struct `const` offset asserts +/// and `layout_asserts` pins; this function is a zero-cost call-site marker that /// documents intent. It takes a type-witness value so call sites can mirror the Zig /// `assertNoUninitializedPadding(@TypeOf(value))` pattern (pass any value of `T` — /// or name `T` explicitly via turbofish and reference the fn item without calling). /// /// The trait bound is intentionally *not* applied here: Zig's `else => return` accepts /// all leaf types, and bounding the generic would force every `write_array` caller -/// to propagate `T: AssertNoUninitializedPadding` before the derive exists. +/// to propagate `T: AssertNoUninitializedPadding`. #[inline(always)] #[allow(dropping_copy_types, clippy::needless_pass_by_value)] pub fn assert_no_uninitialized_padding(_type_witness: T) { - // Body intentionally empty — the derive on `T` is the check. Matches Zig's - // runtime behaviour (the Zig version is `comptime`-only and codegens nothing). + // Body intentionally empty — the per-type `const` layout asserts are the check. + // Matches Zig's runtime behaviour (the Zig version is `comptime`-only and + // codegens nothing). } -// TODO(port): proc-macro — the derive should expand roughly to the following per type -// (shown as a declarative helper for reference; not invoked anywhere yet): +// Reference: what a manual padding audit of a serialized type must establish +// (this is the per-type expansion the Zig comptime walk performs): // // For each adjacent field pair (prev, field) in declaration order: // const _: () = assert!( diff --git a/src/install/patch_install.rs b/src/install/patch_install.rs index 2ba8e53b0cac..f5352534b39e 100644 --- a/src/install/patch_install.rs +++ b/src/install/patch_install.rs @@ -21,16 +21,10 @@ use crate::{ resolution::Resolution, }; -// Thin re-exports (mirroring Zig `pub const X = @import(...)` lines). +// Thin re-export (mirroring the Zig `pub const PatchedDep = @import(...)` line). The other +// Zig same-name re-exports are dropped: their canonical paths (`crate::DependencyID`, +// `crate::PackageID`, `crate::resolution::Resolution`, ...) are what callers use. pub use crate::lockfile::PatchedDep; -pub use crate::resolution::Resolution as ResolutionExport; -pub use crate::{ - DependencyID as DependencyIDExport, PackageID as PackageIDExport, - PackageInstall as PackageInstallExport, bun_hash_tag as bun_hash_tag_export, -}; -// TODO(port): the Zig file re-exports these under the same names; Rust cannot re-export and `use` -// the same identifier twice in one module without aliasing. Collapse the duplicate -// `*Export` aliases above once module layout is settled. bun_output::declare_scope!(InstallPatch, visible); @@ -152,7 +146,7 @@ impl PatchTask { /// Destroy a heap-allocated `PatchTask` previously created by /// `new_calc_patch_hash` / `new_apply_patch_hash`. /// - /// PORT NOTE: Zig `deinit` freed each owned field then `bun.destroy(this)`. In Rust the + /// Zig `deinit` freed each owned field then `bun.destroy(this)`. In Rust the /// owned fields (`Box<[u8]>`, `Vec`, `Log`, `Option<...>`) drop automatically, so no /// `impl Drop` body is needed. Per PORTING.md, `deinit` is never exposed as the public API; /// because `PatchTask` is held via raw pointer through the intrusive `next`/thread-pool @@ -188,7 +182,7 @@ impl PatchTask { "runFromThreadPoolImpl {}", <&'static str>::from(&self.callback) ); - // PORT NOTE: Zig used nested `defer { defer wake(); push(this); }`. There are no early + // Zig used nested `defer { defer wake(); push(this); }`. There are no early // returns in the body, so the equivalent ordering (body → push → wake) is inlined below. match &mut self.callback { Callback::CalcHash(_) => { @@ -196,7 +190,7 @@ impl PatchTask { if let Callback::CalcHash(ch) = &mut self.callback { ch.result = result; } - // PORT NOTE: reshaped for borrowck — `calc_hash` borrows `&mut self`, so we + // Reshaped for borrowck — `calc_hash` borrows `&mut self`, so we // cannot hold a `&mut ch` across the call. } Callback::Apply(_) => { @@ -222,7 +216,6 @@ impl PatchTask { manager: &mut PackageManager, log_level: LogLevel, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set bun_output::scoped_log!( InstallPatch, "runFromThreadMainThread {}", @@ -264,11 +257,10 @@ impl PatchTask { let _ = apply .logger .print(std::ptr::from_mut(Output::error_writer())); - // PORT NOTE: Zig called `apply.logger.deinit()` here under `defer`. The `Log` is a - // field and will be dropped with the task; explicit early drop is skipped to avoid - // double-drop. If `Log::deinit` is reset-to-empty (idempotent), an explicit - // `apply.logger.clear()` could be restored here. - // TODO(port): confirm Log drop semantics + // Zig called `apply.logger.deinit()` here under `defer`; that deinit is + // reset-to-empty (`msgs.clearAndFree()`), so the later deinit in Zig's + // `PatchTask.deinit` is a no-op. In Rust the `Log` field simply drops with + // the task — same semantics, just freed marginally later. } } @@ -277,7 +269,6 @@ impl PatchTask { manager: &mut PackageManager, log_level: LogLevel, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set // TODO only works for npm package // need to switch on version.tag and handle each case appropriately let Callback::CalcHash(calc_hash) = &mut self.callback else { @@ -317,7 +308,7 @@ impl PatchTask { let dep_id = state.dependency_id; let pkg: Package = *manager.lockfile.packages.get(pkg_id as usize); - // PORT NOTE: `Package` is not `Copy` in the Rust port; capture the + // `Package` is not `Copy` in the Rust port; capture the // scalar fields we need after `determine_preinstall_state` consumes // it (Zig's `packages.get()` returns by-value-copy). let pkg_meta_id = pkg.meta.id; @@ -447,7 +438,7 @@ impl PatchTask { return Ok(()); } }; - // PORT NOTE: `defer this.manager.allocator.free(patchfile_txt)` — `patchfile_txt` is owned + // Zig: `defer this.manager.allocator.free(patchfile_txt)` — `patchfile_txt` is owned // (`Vec`/`Box<[u8]>`) and drops at end of scope. let patchfile = match bun_patch::parse_patch_file(&patchfile_txt) { Ok(p) => p, @@ -459,7 +450,7 @@ impl PatchTask { return Ok(()); } }; - // PORT NOTE: `defer patchfile.deinit(bun.default_allocator)` — handled by Drop. + // Zig: `defer patchfile.deinit(bun.default_allocator)` — handled by Drop. // 2. Create temp dir to do all the modifications let mut tmpname_buf = [0u8; 1024]; @@ -481,7 +472,7 @@ impl PatchTask { let (resolution_label, resolution_tag) = { // TODO: fix this threadsafety issue. - // PORT NOTE: not `self.manager()` — `&mut self.callback` is live. + // Not `self.manager()` — `&mut self.callback` is live. // BACKREF; the lockfile is read-only while apply tasks run // off-thread (same contract as the Zig pointer dereference here). let manager = self.manager.get(); @@ -500,11 +491,11 @@ impl PatchTask { .expect("OOM"); (label, resolution.tag) }; - // PORT NOTE: `defer allocator.free(resolution_label)` — Vec drops at scope end. + // Zig: `defer allocator.free(resolution_label)` — Vec drops at scope end. // 3. copy the unpatched files into temp dir let cache_dir_subpath_z: &ZStr = patch.cache_dir_subpath_without_patch_hash.as_zstr(); - // PORT NOTE: borrowck — `tempdir_name` borrows `tmpname_buf` mutably, but + // Borrowck — `tempdir_name` borrows `tmpname_buf` mutably, but // `PackageInstall` also wants `&mut tmpname_buf[..]` for // `destination_dir_subpath_buf`. Zig aliased the two; `PackageInstall` // assumes `destination_dir_subpath` is a prefix slice *into* @@ -516,7 +507,7 @@ impl PatchTask { let mut dest_subpath_buf = [0u8; 1024]; dest_subpath_buf[..tempdir_name.len() + 1] .copy_from_slice(tempdir_name.as_bytes_with_nul()); - // PORT NOTE: not `self.manager()` — `&mut self.callback` is live. + // Not `self.manager()` — `&mut self.callback` is live. // BACKREF — read-only lockfile access; same contract as the Zig // pointer dereference here. let lockfile = &self.manager.get().lockfile; @@ -662,7 +653,7 @@ impl PatchTask { let stat: sys::Stat = match sys::stat(absolute_patchfile_path) { sys::Result::Err(e) => { if e.get_errno() == sys::Errno::ENOENT { - // PORT NOTE: not `self.manager()` — `&mut self.callback` is live. + // Not `self.manager()` — `&mut self.callback` is live. // BACKREF — read-only lockfile access on the worker thread; // same contract as the Zig pointer dereference here. let manager = self.manager.get(); @@ -755,7 +746,7 @@ impl PatchTask { } pub fn notify(&mut self) { - // PORT NOTE: Zig `defer this.manager.wake()` then `push`. No early returns; inline order. + // Zig: `defer this.manager.wake()` then `push`. No early returns; inline order. let mgr = self.manager.as_ptr(); // SAFETY: `self.manager` is a long-lived BACKREF (Zig `*PackageManager`); // only touches the lock-free queue and event-loop wake atomics. @@ -783,8 +774,8 @@ impl PatchTask { .unwrap_or_else(|| panic!("This is a bug")); let patchfile_path: Box<[u8]> = Box::from(patchdep.path.slice(&manager.lockfile.buffers.string_bytes)); - // TODO(port): Zig used `dupeZ` (NUL-terminated). The field is typed `[]const u8` and only - // used as a byte slice, so `Box<[u8]>` without trailing NUL should be equivalent. Verify. + // Zig used `dupeZ` (NUL-terminated), but the field is typed `[]const u8` and only ever + // consumed as a byte slice (`join_z_buf` input, log formatting), so no trailing NUL. let tempdir = manager.get_temporary_directory().handle.fd(); let pt = Box::new(PatchTask { @@ -817,13 +808,13 @@ impl PatchTask { ) -> *mut PatchTask { let pkg_name = pkg_manager.lockfile.packages.items_name()[pkg_id as usize]; - // PORT NOTE: borrowck — `compute_cache_dir_and_subpath` borrows `&mut PackageManager` + // Borrowck — `compute_cache_dir_and_subpath` borrows `&mut PackageManager` // while `pkg_name.slice(..)` and `resolution` borrow `pkg_manager.lockfile` immutably. // Clone the slice/resolution out first. let pkg_name_slice = pkg_name .slice(&pkg_manager.lockfile.buffers.string_bytes) .to_vec(); - // PORT NOTE: `Resolution` is `Copy`; copy out so the lockfile borrow ends + // `Resolution` is `Copy`; copy out so the lockfile borrow ends // before `compute_cache_dir_and_subpath` reborrows `pkg_manager` mutably. let resolution_clone: Resolution = pkg_manager.lockfile.packages.items_resolution()[pkg_id as usize]; diff --git a/src/install/pnpm.rs b/src/install/pnpm.rs index edff063b9316..cb7d39a2cf5f 100644 --- a/src/install/pnpm.rs +++ b/src/install/pnpm.rs @@ -20,10 +20,9 @@ use crate::npm::{self}; use crate::resolution::{self, Resolution, TaggedValue}; use crate::{DependencyID, INVALID_PACKAGE_ID, PackageID, PackageManager}; -// PORT NOTE: reshaped for borrowck. Zig keeps a single `var string_buf = -// lockfile.stringBuf()` for the whole function, but in Rust that locks out -// every other `lockfile.*` access. Construct a fresh `Buf` per append so the -// mutable borrow ends immediately. +// A single long-lived `Buf` for the whole function would lock out every other +// `lockfile.*` access. Construct a fresh `Buf` per append so the mutable +// borrow ends immediately. macro_rules! sbuf { ($lockfile:expr) => { semver::string::Buf { @@ -33,12 +32,11 @@ macro_rules! sbuf { }; } -// PORT NOTE: Zig freely passes `lockfile.buffers.string_bytes.items` alongside -// `&mut lockfile`. In Rust we keep the borrows field-disjoint instead — every -// concurrent mutation in this file touches `buffers.dependencies`, -// `buffers.resolutions`, `packages`, etc., never `string_bytes` itself, so a -// plain `lockfile.buffers.string_bytes.as_slice()` at the use site is sound -// and checked. The one exception (`append_package_dedupe` taking `&mut self`) +// Borrows are kept field-disjoint — every concurrent mutation in this file +// touches `buffers.dependencies`, `buffers.resolutions`, `packages`, etc., +// never `string_bytes` itself, so a plain +// `lockfile.buffers.string_bytes.as_slice()` at the use site is sound and +// checked. The one exception (`append_package_dedupe` taking `&mut self`) // reads the slice from `self` internally. macro_rules! string_bytes { ($lockfile:expr) => { @@ -577,7 +575,7 @@ pub(crate) fn migrate_pnpm_lockfile<'a>( Err(_) => return Err(invalid_pnpm_lockfile()), }; - // PORT NOTE: copy `Expr` out by value so the `&mut manager` + // Copy `Expr` out by value so the `&mut manager` // borrow held by `workspace_pkg_json` ends here — `manager` // is reborrowed below for `parse_append_importer_dependencies`. let workspace_root: Expr = workspace_pkg_json.root; @@ -640,7 +638,7 @@ pub(crate) fn migrate_pnpm_lockfile<'a>( for _pkg_id in 0..workspace_pkgs_end { let pkg_id: PackageID = u32::try_from(_pkg_id).expect("int cast"); - // PORT NOTE: own the bytes — the `'next_dep` loop body mutates + // Own the bytes — the `'next_dep` loop body mutates // `lockfile.buffers.string_bytes` (via `sbuf!`) and takes // `&mut *lockfile` (`append_package_dedupe`), so a borrow that // spans the loop would conflict. @@ -1496,9 +1494,9 @@ fn parse_append_importer_dependencies( if strings::has_prefix(specifier_str, b"catalog:") { let catalog_group_name_str = &specifier_str[b"catalog:".len()..]; let catalog_group_name = sbuf!(lockfile).append(catalog_group_name_str)?; - // PORT NOTE: reshaped for borrowck — `CatalogMap::get` needs - // both `&mut self.catalogs` and `&self`; temporarily move - // catalogs out so the disjoint fields can be borrowed. + // `CatalogMap::get` needs both `&mut self.catalogs` and + // `&self`; temporarily move catalogs out so the disjoint + // fields can be borrowed. let catalogs = core::mem::take(&mut lockfile.catalogs); let dep_result = catalogs.get(lockfile, catalog_group_name, name.value); lockfile.catalogs = catalogs; diff --git a/src/install/postinstall_optimizer.rs b/src/install/postinstall_optimizer.rs index 20f8fe02d045..3cf3cf594221 100644 --- a/src/install/postinstall_optimizer.rs +++ b/src/install/postinstall_optimizer.rs @@ -1,8 +1,8 @@ use bun_collections::VecExt; use std::sync::LazyLock; -use bun_collections::ArrayHashMap; -// PORT NOTE: `Expr` here is the T2 `bun_ast::Expr` (re-exported via +use bun_collections::{ArrayHashMap, ArrayIdentityContextU64}; +// `Expr` here is the T2 `bun_ast::Expr` (re-exported via // `crate::bun_json`), not the T4 `bun_ast::Expr`. The sole caller // (`lockfile::Package::parse_with_json`) holds a JSON-parsed `bun_json::Expr`, // so binding to the lower-tier type avoids a cross-tier mismatch. @@ -20,8 +20,9 @@ pub enum PostinstallOptimizer { Ignore, } -// TODO(port): was comptime in Zig — verify `string_hash` can be `const fn` and -// switch to `const` array if so. +// `string_hash` is `Wyhash11` (not `const fn`; only the std-Wyhash final4 +// variant has a const re-port), so this stays a `LazyLock` rather than a +// `const` array as in the Zig comptime original. static DEFAULT_NATIVE_BINLINKS_NAME_HASHES: LazyLock<[PackageNameHash; 2]> = LazyLock::new(|| { [ semver::string::Builder::string_hash(b"esbuild"), @@ -34,8 +35,8 @@ struct DefaultIgnore { minimum_version: semver::Version, } -// TODO(port): was comptime in Zig — `Version::parse_utf8` is unlikely to be `const fn`; keep -// LazyLock unless a const path is found. +// `Version::parse_utf8` is not `const fn`, so this stays a `LazyLock` rather +// than a comptime constant as in Zig. static DEFAULT_IGNORE: LazyLock<[DefaultIgnore; 1]> = LazyLock::new(|| { [DefaultIgnore { name_hash: semver::string::Builder::string_hash(b"sharp"), @@ -49,7 +50,7 @@ impl PostinstallOptimizer { expr: &js_ast::Expr, value: PostinstallOptimizer, ) -> Result { - // PORT NOTE: Zig `expr.asArray()` returns null for both non-array AND empty + // Zig `expr.asArray()` returns null for both non-array AND empty // array, so the `items.len == 0` check below is dead in Zig too — preserved // for diff parity. let Some(mut array) = expr.as_array() else { @@ -60,20 +61,30 @@ impl PostinstallOptimizer { } while let Some(entry) = array.next() { - if entry.is_string() { - // PORT NOTE: Zig `asString(allocator)` would convert UTF-16→UTF-8; JSON - // string literals are always UTF-8/non-rope, so `as_utf8_string_literal` - // suffices and avoids threading a bump arena. - // TODO(port): if a UTF-16 EString ever reaches here, route a `&Bump`. - let Some(str) = entry.as_utf8_string_literal() else { - continue; - }; + let js_ast::ExprData::EString(s) = &entry.data else { + continue; + }; + // JSON parsing never folds strings into ropes (same invariant + // `as_utf8_string_literal` asserted before this was inlined). + debug_assert!(s.next.is_none()); + // Zig `entry.asString(allocator)` transcodes UTF-16 → UTF-8 before + // hashing. The JSON lexer emits UTF-16 `EString`s for non-ASCII + // package.json strings, so both representations must hash the same + // UTF-8 bytes. + let hash = if s.is_utf8() { + let str = s.slice8(); if str.is_empty() { continue; } - let hash = semver::string::Builder::string_hash(str); - list.dynamic.put(hash, value)?; - } + semver::string::Builder::string_hash(str) + } else { + let utf8 = bun_core::strings::to_utf8_alloc(s.slice16()); + if utf8.is_empty() { + continue; + } + semver::string::Builder::string_hash(&utf8) + }; + list.dynamic.put(hash, value)?; } Ok(true) @@ -132,11 +143,10 @@ impl PostinstallOptimizer { } } -// TODO(port): Zig used `std.ArrayHashMapUnmanaged(PackageNameHash, PostinstallOptimizer, -// install.ArrayIdentityContext.U64, false)` — i.e. an *identity* hash context (key is already -// a hash). `bun_collections::ArrayHashMap` should be configured for identity hashing on u64 keys, -// or expose a `ArrayHashMap` variant. -pub type Map = ArrayHashMap; +// Zig: `std.ArrayHashMapUnmanaged(PackageNameHash, PostinstallOptimizer, +// install.ArrayIdentityContext.U64, false)` — the key is already a hash, so use the +// identity context rather than re-hashing it. +pub type Map = ArrayHashMap; #[derive(Default)] pub struct List { diff --git a/src/install/repository.rs b/src/install/repository.rs index bdf49e1c5346..ff34c3774ae1 100644 --- a/src/install/repository.rs +++ b/src/install/repository.rs @@ -13,11 +13,12 @@ use crate::dependency as Dependency; use crate::hosted_git_info; use crate::install::{self as Install, ExtractData, PackageManager}; -// TODO(port): bun.ThreadlocalBuffers — Zig returns a raw pointer into thread-local -// storage so callers can return slices that outlive the access. Rust thread_local! -// closures cannot express this without unsafe. TODO(refactor): either (a) make -// try_ssh/try_https take an out-buffer, or (b) wrap in a type that hands out -// a raw `*mut PathBuffer` via UnsafeCell with documented single-use invariant. +// bun.ThreadlocalBuffers — Zig returns a raw pointer into thread-local storage so +// callers can return slices that outlive the access (`try_ssh`/`try_https` hand a +// slice straight to `download`). Rust `thread_local!` closures cannot express that +// without unsafe, so this mirrors the Zig scheme: a leaked per-thread allocation +// with per-field projection accessors and a documented single-use-per-field +// invariant (see `tl_bufs()` / the `TlBufs` accessor docs below). struct TlBufs { final_path_buf: PathBuffer, ssh_path_buf: PathBuffer, @@ -230,7 +231,7 @@ pub(crate) struct SharedEnv { env: Option, } -// PORT NOTE: Zig's `pub var shared_env` is a process-global anon-struct whose +// Zig's `pub var shared_env` is a process-global anon-struct whose // `get()` lazily clones `other.map` once and returns the `DotEnv.Map` handle by // value (Zig struct copy — both copies alias the same backing storage). Rust's // `Map` owns its storage and is not `Copy`, so we hand out a `&'static Map` into @@ -369,12 +370,11 @@ pub trait RepositoryExt: Sized { /// free fn because trait methods cannot be private; called only from this /// file's `download`/`find_commit`/`checkout`. fn exec(env: &bun_dotenv::Map, argv: &[&[u8]]) -> Result, Error> { - // PORT NOTE: Zig passed `DotEnv.Map` by struct-copy (shallow). Rust's + // Zig passed `DotEnv.Map` by struct-copy (shallow). Rust's // `Map` is move-only; clone via `clone_with_allocator` so callers can // hand us a shared `&Map` (matches `PackageManagerTask` call sites). let mut env = bun_core::handle_oom(env.clone_with_allocator()); let std_map = env.std_env_map()?; - // TODO(port): narrow error set // Zig used `std.process.Child.run` on both Windows and POSIX (identical // arms). `bun_spawn::run` is its Rust port — POSIX argv/envp marshalling @@ -491,7 +491,7 @@ impl RepositoryExt for Repository { string_buf: &[u8], dep: &Install::Dependency, ) -> Vec { - // PORT NOTE: Zig took `*Lockfile` and indexed `buffers.dependencies[dep_id]` + // Zig took `*Lockfile` and indexed `buffers.dependencies[dep_id]` // / `string_bytes`. Callers (`parse_with_json`) hold a split `StringBuilder` // borrow on `string_bytes`, so accept the two pieces directly. let buf = string_buf; @@ -558,7 +558,8 @@ impl RepositoryExt for Repository { } fn try_ssh(url: &[u8]) -> Option<&[u8]> { - // TODO(port): lifetime — returns slice into thread-local buffer; see tl_bufs(). + // May return a slice into the thread-local `ssh_path_buf` (see `tl_bufs()`); + // it is only valid until the next `try_ssh` call on this thread. let ssh_path_buf = TlBufs::ssh_path_buf(); // Do not cast explicit http(s) URLs to SSH if url.starts_with(b"http") { @@ -628,7 +629,9 @@ impl RepositoryExt for Repository { } fn try_https(url: &[u8]) -> Option<&[u8]> { - // TODO(port): lifetime — returns slice into thread-local buffer; see tl_bufs(). + // May return a slice into the thread-local `final_path_buf` (see `tl_bufs()`); + // it is only valid until the next use of `TlBufs::final_path_buf()` on this + // thread (another `try_https` call, or `checkout`'s `get_fd_path`). let final_path_buf = TlBufs::final_path_buf(); if url.starts_with(b"http") { return Some(url); @@ -700,7 +703,6 @@ impl RepositoryExt for Repository { bun_core::fmt::hex_int_lower::<16>(task_id.get()) ) .map_err(|_| err!("NoSpaceLeft"))?; - // TODO(port): narrow error set let written = total - cursor.len() - 1; bun_core::ZStr::from_buf(&folder_name_buf[..], written) }; @@ -779,7 +781,6 @@ impl RepositoryExt for Repository { bun_core::fmt::hex_int_lower::<16>(task_id.get()) ) .map_err(|_| err!("NoSpaceLeft"))?; - // TODO(port): narrow error set let written = total - cursor.len(); &folder_name_buf[..written] }; @@ -825,9 +826,9 @@ impl RepositoryExt for Repository { } }; + // Zig returned a (possibly interior) slice into `exec`'s allocation; here the + // trimmed bytes are copied so the return value owns its storage. Ok(strings::trim(&out, b" \t\r\n").to_vec()) - // TODO(port): Zig returned a slice into `exec`'s allocation without trimming - // in-place; here we own `out` and copy the trimmed slice. Revisit ownership. } fn checkout( diff --git a/src/install/resolution.rs b/src/install/resolution.rs index 07f14a45a7b4..9e7dbeba7e26 100644 --- a/src/install/resolution.rs +++ b/src/install/resolution.rs @@ -6,7 +6,6 @@ use bstr::BStr; use bun_core::fmt::{PathFormatOptions, PathSep, fmt_path_u8 as fmt_path}; use bun_semver as semver; use bun_semver::String; -// PORT NOTE: Zig `String.Buf` → `bun_semver::string::Buf<'_>`. use bun_core::strings; use bun_semver::string::Buf as StringBuf; use bun_semver::version::VersionInt; @@ -283,7 +282,7 @@ impl ResolutionType { } dependency::VersionTag::Npm => { let version_literal = string_buf.append(res_str)?; - // PORT NOTE: this fn returns `Resolution` (= `ResolutionType`), + // This fn returns `Resolution` (= `ResolutionType`), // not `Self`, so parse at `u64` regardless of the impl's SemverInt. let parsed = semver::Version::parse(version_literal.sliced(string_buf.bytes.as_slice())); @@ -494,7 +493,7 @@ impl ResolutionType { } } -// PORT NOTE: the duck-typed `Builder` Zig comptime param maps to the +// The duck-typed `Builder` Zig comptime param maps to the // `bun_semver::StringBuilder` trait (`count` + `append`); local alias kept // so dependents that named `resolution::StringBuilderLike` still resolve. pub use bun_semver::StringBuilder as StringBuilderLike; @@ -581,7 +580,7 @@ impl<'a, SemverInt: VersionInt> URLFormatter<'a, SemverInt> { ), Tag::Folder => writer.write_all(res.folder().slice(buf)), Tag::RemoteTarball => writer.write_all(res.remote_tarball().slice(buf)), - // PORT NOTE: `Repository::format_as` still goes through `fmt::Write` + // `Repository::format_as` still goes through `fmt::Write` // (and uses `BStr` internally); git/github URLs are ASCII in // practice so byte-exactness is preserved. A follow-up shard owns // `repository.rs` if that ever needs a byte-level path too. @@ -604,7 +603,7 @@ impl<'a, SemverInt: VersionInt> URLFormatter<'a, SemverInt> { } } -// PORT NOTE: kept for the ~dozen call sites that interpolate into +// Kept for the ~dozen call sites that interpolate into // `format_args!` for terminal/log output (Output::err, pretty_errorln, …), // where lossy U+FFFD on the rare non-UTF-8 byte is acceptable. File-producing // callers MUST use [`URLFormatter::write_to`] instead. @@ -727,7 +726,7 @@ impl<'a, SemverInt: VersionInt> Formatter<'a, SemverInt> { } } -// PORT NOTE: kept for terminal/log call sites (Output::err, tree printer, …). +// Kept for terminal/log call sites (Output::err, tree printer, …). // Persisted-to-disk callers (Yarn.rs) MUST use [`Formatter::write_to`]. impl<'a, SemverInt: VersionInt> fmt::Display for Formatter<'a, SemverInt> { fn fmt(&self, writer: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/src/install/resolvers/folder_resolver.rs b/src/install/resolvers/folder_resolver.rs index 400afa4a925d..8009b4e5b9d4 100644 --- a/src/install/resolvers/folder_resolver.rs +++ b/src/install/resolvers/folder_resolver.rs @@ -87,7 +87,7 @@ impl<'a> fmt::Display for PackageWorkspaceSearchPathFormatter<'a> { } // Zig: std.HashMapUnmanaged(u64, FolderResolution, IdentityContext(u64), 80) -// PORT NOTE: bun_collections::HashMap currently ignores the context/load-factor +// bun_collections::HashMap currently ignores the context/load-factor // type params (backed by std HashMap); identity hashing is a TODO(perf). pub type Map = HashMap>; @@ -100,7 +100,7 @@ pub fn hash(normalized_path: &[u8]) -> u64 { } // ── NewResolver(comptime tag: Resolution.Tag) type ──────────────────────── -// PORT NOTE: `Resolution.Tag` (Zig nested decl) is `crate::resolution::Tag`; +// `Resolution.Tag` (Zig nested decl) is `crate::resolution::Tag`; // const-generic requires `#[derive(ConstParamTy)]` (already on `Tag`). pub struct NewResolver<'a, const TAG: ResolutionTag> { pub folder_path: &'a [u8], @@ -242,7 +242,7 @@ fn normalize_package_json_path<'a>( remain[normalized.len() + 1..normalized.len() + PACKAGE_JSON_LEN] .copy_from_slice(b"package.json"); let remain_after = remain.len() - (normalized.len() + PACKAGE_JSON_LEN); - // PORT NOTE: reshaped for borrowck — compute abs len from remaining capacity + // Reshaped from the Zig original for borrowck — compute abs len from remaining capacity let abs_len = joined_len - remain_after; abs = &joined[0..abs_len]; // We store the folder name without package.json @@ -273,7 +273,7 @@ fn read_package_json_from_disk( let mut package: LockfilePackage = Default::default(); - // PORT NOTE: Zig passed `manager.lockfile`, `manager`, `manager.log` as + // Borrow splitting: Zig passed `manager.lockfile`, `manager`, `manager.log` as // three separate args; Rust borrowck rejects the overlap on `&mut self`, // so split via raw pointer once here. `lockfile` and `log` are disjoint // fields of `PackageManager`, and `parse{,_with_json}` only reaches @@ -293,8 +293,9 @@ fn read_package_json_from_disk( bun_perf::trace(bun_perf::PerfEvent::FolderResolverReadPackageJSONFromDiskWorkspace); // SAFETY: `manager_ptr` was just derived from the live `&mut PackageManager` - // argument; `log` points into a separate `Log` allocation (see PORT NOTE - // above), so this `&mut` reborrow aliases no other live reference. + // argument; `log` points into a separate `Log` allocation (see the + // borrow-splitting comment above), so this `&mut` reborrow aliases no + // other live reference. let json = unsafe { &mut *manager_ptr } .workspace_package_json_cache .get_with_path(log, abs.as_bytes(), Default::default()) @@ -305,7 +306,7 @@ fn read_package_json_from_disk( let root: Expr = json.root; let source: *const bun_ast::Source = &raw const json.source; - // SAFETY: see PORT NOTE above on borrow splitting. + // SAFETY: see the borrow-splitting comment above. unsafe { let lockfile: *mut Lockfile = &raw mut *(*manager_ptr).lockfile; package.parse_with_json::( @@ -326,7 +327,7 @@ fn read_package_json_from_disk( let file = File::openat(Fd::cwd(), abs.as_bytes(), O::RDONLY, 0)?; // defer file.close() body.reset(); - // PORT NOTE: toManaged/moveToUnmanaged dance is a no-op in Rust + // Zig's toManaged/moveToUnmanaged dance is a no-op in Rust // (Vec owns its allocator). let read_result = file .read_to_end_with_array_list(&mut body.list, bun_sys::SizeHint::ProbablySmall) @@ -337,7 +338,7 @@ fn read_package_json_from_disk( bun_ast::Source::init_path_string(abs.as_bytes(), body.list.as_slice()) }; - // SAFETY: see PORT NOTE above on borrow splitting. + // SAFETY: see the borrow-splitting comment above. unsafe { let lockfile: *mut Lockfile = &raw mut *(*manager_ptr).lockfile; package.parse::( @@ -430,7 +431,7 @@ pub fn get_or_put( }; let abs_hash = hash(abs.as_bytes()); - // PORT NOTE: reshaped for borrowck — Zig used getOrPut to reserve the slot before reading + // Reshaped for borrowck — Zig used getOrPut to reserve the slot before reading // package.json; here we check first, compute, then insert, because read_package_json_from_disk // needs &mut manager. if let Some(existing) = manager.folders.get(&abs_hash) { diff --git a/src/install/windows-shim/BinLinkingShim.rs b/src/install/windows-shim/BinLinkingShim.rs index 90dcd93b5d83..97b54424d8c7 100644 --- a/src/install/windows-shim/BinLinkingShim.rs +++ b/src/install/windows-shim/BinLinkingShim.rs @@ -36,6 +36,11 @@ impl VersionFlag { /// std.math.maxInt(u13) const MAX: VersionFlag = VersionFlag((1u16 << 13) - 1); + + #[inline] + pub const fn bits(self) -> u16 { + self.0 + } } // Zig: `packed struct(u16)` with mixed bool + u13 fields → `#[repr(transparent)]` @@ -144,7 +149,7 @@ mod host { /// has produced the PE — but any actual *use* of the data still fails loudly. /// Call this from every site that writes `EMBEDDED_EXECUTABLE_DATA` to disk. /// - /// PORT NOTE: this MUST be a process exit, not `panic!()`. The only caller + /// IMPORTANT: this MUST be a process exit, not `panic!()`. The only caller /// (`bin::Linker::create_windows_shim`) runs on the parallel-install thread /// pool; the workspace is `panic = "unwind"`, so a `panic!()` here unwinds /// and kills the worker thread — the main install loop's pending-task counter @@ -203,25 +208,23 @@ mod host { #[derive(Copy, Clone)] pub struct Shebang<'a> { - // PORT NOTE: borrows into the caller's input buffer (see `parse` doc) + // Borrows into the caller's input buffer (see `parse` doc). pub launcher: &'a [u8], pub utf16_len: u32, pub is_node_or_bun: bool, } impl<'a> Shebang<'a> { - pub fn init( - launcher: &'a [u8], - is_node_or_bun: bool, - ) -> Result, bun_core::Error> { - // TODO(port): narrow error set (Zig inferred empty error set here) - Ok(Shebang { + // Zig declared `!Shebang` but the inferred error set is empty; return the + // value directly. + pub fn init(launcher: &'a [u8], is_node_or_bun: bool) -> Shebang<'a> { + Shebang { launcher, // TODO: what if this is invalid utf8? utf16_len: u32::try_from(simdutf::length::utf16::from::utf8(launcher)) .expect("int cast"), is_node_or_bun, - }) + } } /// std.fs.path.extension but utf16 @@ -299,7 +302,7 @@ mod host { }; // std.mem.tokenizeScalar(u8, line, ' ') — manual port preserving `.rest()` semantics. - // PORT NOTE: reshaped — Rust split() iterator has no `.rest()`. + // Reshaped — Rust split() iterator has no `.rest()`. let mut idx: usize = 0; // skip leading delimiters, then take token while idx < line.len() && line[idx] == b' ' { @@ -331,10 +334,10 @@ mod host { } let is_node_or_bun = eql_comptime(program, b"bun") || eql_comptime(program, b"node"); - return Shebang::init(rest, is_node_or_bun).map(Some); + return Ok(Some(Shebang::init(rest, is_node_or_bun))); } - Shebang::init(line, false).map(Some) + Ok(Some(Shebang::init(line, false))) } pub fn encoded_length(&self) -> usize { @@ -362,9 +365,12 @@ mod host { l } - /// The buffer must be exactly the correct length given by encoded_length + /// The buffer must be exactly the correct length given by encoded_length. + /// + /// Zig declared `!void` but the inferred error set is empty; the `Result` + /// is kept only because the caller (`bin.rs`) treats it as fallible API — + /// this always returns `Ok(())`. pub fn encode_into(&self, buf: &mut [u8]) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set debug_assert!(buf.len() == self.encoded_length()); debug_assert!(self.bin_path[0] != b'/' as u16); diff --git a/src/install/windows-shim/bun_shim_impl.rs b/src/install/windows-shim/bun_shim_impl.rs index 86b34592da63..4a22fda01a73 100644 --- a/src/install/windows-shim/bun_shim_impl.rs +++ b/src/install/windows-shim/bun_shim_impl.rs @@ -1,5 +1,3 @@ -//! TODO(port): correct this documentation comment post-rust port -//! //! This program is a shim for node_modules/.bin scripts. //! //! This is needed because: @@ -10,7 +8,7 @@ //! which is a HUGE dx win for developers. //! //! The approach implemented is a `.bunx` file which sits right next to the renamed -//! launcher exe. We read that (see BinLinkingShim.zig for the creation of this file) +//! launcher exe. We read that (see `BinLinkingShim.rs` for the creation of this file) //! and then we call NtCreateProcess to spawn the correct child process. //! //! Every attempt possible to make this file as minimal as possible has been made. @@ -36,8 +34,10 @@ //! Prior Art: //! - https://github.com/ScoopInstaller/Shim/blob/master/src/shim.cs //! -//! The compiled binary is 13312 bytes and is `@embedFile`d into Bun itself. -//! When this file is updated, the new binary should be compiled and BinLinkingShim.VersionFlag.current should be updated. +//! This file is compiled twice: into bun.exe (for the bunx fast paths below) and as +//! the standalone `bun_shim_impl.exe` PE (see `main.rs` / the `shim_standalone` +//! feature), which is then `include_bytes!`-embedded into Bun by `BinLinkingShim.rs`. +//! When the encoding changes, `BinLinkingShim::VersionFlag::CURRENT` should be bumped. //! //! Theorized and written by @paperclover during one of the most entranced all-nighters of her life. @@ -103,7 +103,8 @@ mod nt { pub(super) safe fn NtClose(Handle: HANDLE) -> Status; } - // TODO(port): move to _sys (or bun_sys::windows::ntdll) + // Declared locally (not in `bun_sys::windows::ntdll`) so the standalone PE + // build, whose `w` alias is `crate::compat`, needs no extra re-export. // SAFETY: ntdll syscalls; signatures match WDK headers. Kept `unsafe fn` // (not `safe fn`) because both write through caller-supplied out-pointers // (`IoStatusBlock`, `Buffer`) — validity is a genuine caller precondition. @@ -112,7 +113,7 @@ mod nt { #[link_name = "NtReadFile"] pub(super) fn NtReadFile( FileHandle: HANDLE, // [in] - // PORT NOTE: Zig `?w.HANDLE` is pointer-sized via null-niche. Rust + // Zig `?w.HANDLE` is pointer-sized via null-niche. Rust // `Option<*mut c_void>` is NOT (raw pointers can already be null → // no niche → 16-byte tagged enum, passed by-reference under Win64 // ABI). Use a plain HANDLE and pass null_mut() for "no event". @@ -187,8 +188,8 @@ macro_rules! debug { { bun_output::scoped_log!(bun_shim_impl, $fmt $(, $arg)*); } #[cfg(feature = "shim_standalone")] { - // TODO(port): standalone build has no std logger; this was `std.log.debug`. - // Left as no-op to keep the binary tiny; revisit if standalone debug logging is needed. + // The standalone build has no logger (Zig used `std.log.debug` there); + // deliberately a no-op to keep the binary tiny. let _ = ($($arg,)*); } } @@ -279,7 +280,7 @@ impl core::fmt::Display for FailReason { } writer.write_str("error: ")?; - // PORT NOTE: Zig used `switch (reason) { inline else => |r| ... }` to make `r` comptime + // Zig used `switch (reason) { inline else => |r| ... }` to make `r` comptime // and resolve the template at compile time. We dispatch at runtime; the template lookup // is a const fn so the cost is a single match. if IS_STANDALONE && *self == FailReason::CouldNotDirectLaunch { @@ -556,8 +557,15 @@ fn launcher(bun_ctx: Ctx) -> LauncherRet let mut buf1 = MaybeUninit::<[u16; BUF1_LEN]>::uninit(); let mut buf2 = MaybeUninit::<[u16; BUF2_U16_LEN]>::uninit(); - // TODO(port): the Zig source slices these as `[comptime buf1.len..]` on a `[*]T` cast, - // whose semantics are unclear; from usage they are base-of-buffer raw pointers. + // The Zig source derives these views as `@as([*]u8, @ptrCast(&buf1[0]))[comptime + // buf1.len..]` (and `[buf1.len / 2..]` for the u16 view) — open-ended slicing of a + // many-item pointer, i.e. both views advanced by `buf1.len` *bytes* to the midpoint + // of the array. The u8 and u16 views stay mutually consistent, so all of the Zig + // code's relative arithmetic works; the only observable effect is that Zig used + // half the declared capacity (and its `read_max_len` overcounted the true + // remaining space). The Rust port uses true base-of-buffer pointers for both + // views: identical behavior for every in-bounds path, full capacity, and a + // correctly bounded `read_max_len`. // Derive each view from a single `as_mut_ptr()` call so the raw pointers share one // borrow tag (a second `as_mut_ptr()` would invalidate the first under Stacked Borrows). let buf1_u16: *mut u16 = buf1.as_mut_ptr().cast::(); @@ -712,7 +720,7 @@ fn launcher(bun_ctx: Ctx) -> LauncherRet // if this is the end of the string then this becomes an empty slice, // otherwise it is a slice of just the arguments // - // PORT NOTE: Zig ReleaseSmall reads one past `Length` here and hits the + // Zig ReleaseSmall reads one past `Length` here and hits the // PEB CommandLine NUL terminator, exiting the loop. Rust slice indexing // always bounds-checks, so we must guard the upper bound explicitly. while i < cmd_line_u16.len() && cmd_line_u16[i] == ' ' as u16 { @@ -934,9 +942,11 @@ fn launcher(bun_ctx: Ctx) -> LauncherRet let flags_u16: u16 = flags.bits(); debug!("FlagsInt: {}", flags_u16); debug!("Flags:"); - // TODO(port): Zig used `inline for` over `std.meta.fieldNames(Flags)`. Replace with a - // manual dump or a `Debug` impl on `Flags`. - debug!(" {:#06x}", flags.bits()); + // Zig: `inline for` over `std.meta.fieldNames(Flags)` — unrolled by hand. + debug!(" is_node_or_bun: {}", flags.is_node_or_bun()); + debug!(" is_node: {}", flags.is_node()); + debug!(" has_shebang: {}", flags.has_shebang()); + debug!(" version_tag: {}", flags.version_tag().bits()); } if !flags.is_valid() { @@ -1274,7 +1284,7 @@ fn launcher(bun_ctx: Ctx) -> LauncherRet // Prepare stdio for the child process, as after this we are going to *immediatly* exit // it is likely that the c-runtime's atexit will not be called as we end the process ourselves. // - // PORT NOTE: gated on `Launch` so `ReadWithoutLaunch` does not irreversibly mutate the + // Gated on `Launch` so `ReadWithoutLaunch` does not irreversibly mutate the // parent process's stdio state. In Zig the `read_without_launch` instantiation would // compile-error at the later `bun_ctx.environment` access, so it never reaches here; // Rust's trait abstraction defeats that comptime guard, hence the explicit mode check. @@ -1641,7 +1651,8 @@ impl BunCtx for &FromBunRunContext { pub fn try_startup_from_bun_js(context: FromBunRunContext) { debug_assert!(!context.base_path_slice().starts_with(&NT_OBJECT_PREFIX)); const _: () = assert!(!IS_STANDALONE); - // TODO(port): `comptime assert(bun.FeatureFlags.windows_bunx_fast_path)` — wire up FeatureFlags const. + // Zig: `comptime assert(bun.FeatureFlags.windows_bunx_fast_path)`. + const _: () = assert!(bun_core::feature_flags::WINDOWS_BUNX_FAST_PATH); match launcher::<{ LauncherMode::Launch }, _>(&context) { LauncherRet::LaunchFellThrough => {} LauncherRet::Read(_) => unreachable!(), @@ -1709,7 +1720,7 @@ impl BunCtx for &FromBunShellContext { } } -// PORT NOTE: Zig `union` (untagged). Rust enums are tagged; the discriminant overhead is +// Zig `union` (untagged). Rust enums are tagged; the discriminant overhead is // negligible here and gives us safe matching. pub enum ReadWithoutLaunchResult { /// enum which has a predefined custom formatter @@ -1728,7 +1739,8 @@ pub enum ReadWithoutLaunchResult { pub fn read_without_launch(context: FromBunShellContext) -> ReadWithoutLaunchResult { debug_assert!(!context.base_path_slice().starts_with(&NT_OBJECT_PREFIX)); const _: () = assert!(!IS_STANDALONE); - // TODO(port): `comptime assert(bun.FeatureFlags.windows_bunx_fast_path)` — wire up FeatureFlags const. + // Zig: `comptime assert(bun.FeatureFlags.windows_bunx_fast_path)`. + const _: () = assert!(bun_core::feature_flags::WINDOWS_BUNX_FAST_PATH); match launcher::<{ LauncherMode::ReadWithoutLaunch }, _>(&context) { LauncherRet::Read(r) => r, LauncherRet::LaunchFellThrough => unreachable!(), @@ -1740,17 +1752,18 @@ pub fn read_without_launch(context: FromBunShellContext) -> ReadWithoutLaunchRes #[inline] pub(crate) fn main() -> ! { const _: () = assert!(IS_STANDALONE); - // TODO(port): `comptime assert(builtin.single_threaded)` / `!link_libc` / `!link_libcpp` — - // these are build-config assertions; enforce in the standalone crate's Cargo.toml/build.rs. + // Zig asserted `builtin.single_threaded` / `!link_libc` / `!link_libcpp` here. + // Rust has no equivalent builtins; the standalone crate enforces the same + // build shape structurally via `#![no_std]` / `#![no_main]` and its + // CRT-free link configuration (see `main.rs` and `scripts/build/rust.ts`). launcher::<{ LauncherMode::Launch }, _>(()); unreachable!(); } // ───── helpers ───── -/// Zig `std.unicode.fmtUtf16Le`. -// TODO(port): provide a proper UTF-16-LE Display adapter in `bun_str`; for now this lossy -// debug-only formatter is sufficient (only used under `if DBG`). +/// Zig `std.unicode.fmtUtf16Le`. Lossy (replacement-char) formatter — only +/// used for debug logging under `if DBG`, so lossiness is fine. fn fmt16(s: &[u16]) -> impl core::fmt::Display + '_ { struct F<'a>(&'a [u16]); impl core::fmt::Display for F<'_> { diff --git a/src/install/yarn.rs b/src/install/yarn.rs index 9e3581c6bff9..dc9256ee65f1 100644 --- a/src/install/yarn.rs +++ b/src/install/yarn.rs @@ -1,4 +1,5 @@ use bun_collections::VecExt; +use std::borrow::Cow; use std::io::Write as _; use bun_collections::{HashMap, StringHashMap}; @@ -7,7 +8,7 @@ use bun_install::bin::Bin; use bun_install::dependency::{self, Dependency, DependencyExt as _}; use bun_install::install::{self, DependencyID, PackageID, PackageManager}; use bun_install::integrity::Integrity; -// PORT NOTE: `bun_install::lockfile` is the column-accessor stub used by the +// `bun_install::lockfile` is the column-accessor stub used by the // audit/why CLI walkers; the yarn migrator needs the real Lockfile/Tree/ // LoadResult enum, so import from `lockfile_real` and alias it back to // `lockfile` so the qualified `lockfile::DependencySlice` etc. paths below @@ -19,7 +20,7 @@ use crate::lockfile_real::package::{ }; use crate::lockfile_real::{self as lockfile, LoadResult, Lockfile, tree, tree::Tree}; use bun_install::npm; -// PORT NOTE: `Package.resolution` is the file-backed `resolution_real::ResolutionType` +// `Package.resolution` is the file-backed `resolution_real::ResolutionType` // (tag + zero-padded `Value` union), constructed via `init(TaggedValue::*)`; the // `bun_install::resolution` stub keeps `Value` as a struct-of-fields and has no `init`. use crate::bun_json; @@ -31,10 +32,9 @@ use bun_paths::PathBuffer; use bun_semver::{self as Semver, SlicedString, String as SemverString}; use bun_sys::Fd; -// TODO(port): lifetime — Entry/YarnLock borrow from the input `data: &[u8]` passed to -// `migrate_yarn_lockfile`. LIFETIMES.tsv had no rows for this file (no *T fields), so -// `'a` here is the BORROW_PARAM classification applied to slice fields. Verify the few -// owned slices (specs inner strings, file, git_repo_name) don't need `Box<[u8]>` instead. +// Entry/YarnLock borrow from the input `data: &[u8]` passed to `migrate_yarn_lockfile`; +// `resolved` may instead own a rewritten URL (see `Cow` below) and `git_repo_name` is +// always owned. pub struct YarnLock<'a> { pub entries: Vec>, @@ -43,7 +43,9 @@ pub struct YarnLock<'a> { pub struct Entry<'a> { pub specs: Vec<&'a [u8]>, pub version: &'a [u8], - pub resolved: Option<&'a [u8]>, + // Usually borrows from the input; owned when `parse_git_url` rewrites a + // `github:` spec to a `https://github.com/...` URL. + pub resolved: Option>, pub integrity: Option<&'a [u8]>, pub dependencies: Option>, pub optional_dependencies: Option>, @@ -84,9 +86,10 @@ pub struct ParsedGitUrl<'a> { pub commit: Option<&'a [u8]>, pub owner: Option<&'a [u8]>, pub repo: Option<&'a [u8]>, - // TODO(port): in Zig, `url` may be either a borrow into `version` or a freshly - // allocPrint'd "https://github.com/{path}". Here we keep an optional owned buffer - // so the borrow case stays zero-copy. Callers must check `owned_url` first. + // In Zig, `url` may be either a borrow into `version` or a freshly allocPrint'd + // "https://github.com/{path}". Here we keep an optional owned buffer so the borrow + // case stays zero-copy. Callers must check `owned_url` first: when it is `Some`, + // it supersedes `url` (see `into_resolved`). pub owned_url: Option>, } @@ -212,7 +215,6 @@ impl<'a> Entry<'a> { _yarn_lock: &YarnLock<'a>, version: &'a [u8], ) -> Result, Error> { - // TODO(port): narrow error set let mut url: &[u8] = version; let mut commit: Option<&[u8]> = None; let mut owner: Option<&[u8]> = None; @@ -245,7 +247,8 @@ impl<'a> Entry<'a> { buf.extend_from_slice(b"https://github.com/"); buf.extend_from_slice(path_without_commit); owned_url = Some(buf); - // url now points into owned_url; callers must read owned_url when Some + // `url` still borrows the stripped input; callers must prefer + // `owned_url` when it is Some (Zig reassigns `url` to this buffer). } else if strings::index_of(url, b"github.com").is_some() { let mut remaining = url; if let Some(idx) = strings::index_of(remaining, b"github.com/") { @@ -335,7 +338,6 @@ impl<'a> YarnLock<'a> { } pub(crate) fn parse(&mut self, content: &'a [u8]) -> Result<(), Error> { - // TODO(port): narrow error set let mut lines = strings::split(content, b"\n"); let mut current_entry: Option> = None; let mut current_specs: Vec<&'a [u8]> = Vec::new(); @@ -469,36 +471,37 @@ impl<'a> YarnLock<'a> { ); } else if Entry::is_git_dependency(value) { let git_info = Entry::parse_git_url(self, value)?; - // TODO(port): dropped logic — Zig reassigns `url` to the - // allocPrint'd `https://github.com/{path}` buffer for the - // `github:` branch and stores that as `resolved`. Here - // `git_info.url` still borrows the stripped input slice and - // `owned_url` is discarded, so github: URLs resolve INCORRECTLY. - // Fix: change Entry.resolved to Cow<'a, [u8]> (or store the - // owned buffer on Entry) so `owned_url` can be assigned here. - entry.resolved = Some(git_info.url); entry.commit = git_info.commit; if let Some(repo_name) = git_info.repo { entry.git_repo_name = Some(Box::<[u8]>::from(repo_name)); } + // Zig reassigns `url` to the allocPrint'd + // `https://github.com/{path}` buffer for the `github:` + // branch; that buffer is `owned_url` here. + entry.resolved = Some(match git_info.owned_url { + Some(owned) => Cow::Owned(owned), + None => Cow::Borrowed(git_info.url), + }); } else if Entry::is_npm_alias(value) { let alias_info = Entry::parse_npm_alias(value); entry.version = alias_info.version; } else if Entry::is_remote_tarball(value) { - entry.resolved = Some(value); + entry.resolved = Some(Cow::Borrowed(value)); } } else if key == b"resolved" { - entry.resolved = Some(value); + entry.resolved = Some(Cow::Borrowed(value)); if Entry::is_git_dependency(value) { let git_info = Entry::parse_git_url(self, value)?; - // TODO(port): same github: owned_url issue as the `version` branch - // above — Entry.resolved needs Cow<'a, [u8]> to hold the rewritten - // `https://github.com/...` buffer. - entry.resolved = Some(git_info.url); entry.commit = git_info.commit; if let Some(repo_name) = git_info.repo { entry.git_repo_name = Some(Box::<[u8]>::from(repo_name)); } + // As in the `version` branch: prefer the rewritten + // `https://github.com/...` buffer for `github:` specs. + entry.resolved = Some(match git_info.owned_url { + Some(owned) => Cow::Owned(owned), + None => Cow::Borrowed(git_info.url), + }); } } else if key == b"integrity" { entry.integrity = Some(value); @@ -535,7 +538,7 @@ impl<'a> YarnLock<'a> { } fn find_entry_by_spec(&self, spec: &[u8]) -> Option<&Entry<'a>> { - // PORT NOTE: Zig returns `?*Entry` (mutable ptr) but every caller only + // Zig returns `?*Entry` (mutable ptr) but every caller only // reads `.workspace` / `.specs`, so `&self` suffices and avoids the // borrowck conflict with the outer `entries.iter()` loops. for entry in self.entries.iter() { @@ -597,8 +600,7 @@ fn process_deps( manager: &mut PackageManager, yarn_entry_to_package_id: &[PackageID], ) -> Result { - // TODO(port): narrow error set - // PORT NOTE: returns count instead of slice to avoid borrowck conflict with caller's bufs + // Returns count instead of slice to avoid borrowck conflict with caller's bufs. let mut count: usize = 0; // PERF(port): was stack-fallback alloc (1024 bytes) — profile if it shows up on a hot path @@ -670,8 +672,8 @@ struct RootDep { #[derive(Clone)] struct VersionInfo { version: Vec, - // TODO(port): Zig stores `string` (borrow from input). Using Vec here to avoid - // a second lifetime on the local map; could switch to &'a [u8]. + // Zig stores a borrow from the input; an owned Vec avoids a second + // lifetime on the local map. package_id: PackageID, yarn_idx: usize, } @@ -683,8 +685,7 @@ pub(crate) fn migrate_yarn_lockfile<'a>( data: &[u8], dir: Fd, ) -> Result, Error> { - // TODO(port): narrow error set - // todo yarn v2+ support + // yarn v2+ (berry) lockfiles are not supported; only the v1 format migrates. if !strings::index_of(data, b"# yarn lockfile v1").is_some() { return Err(bun_core::err!("UnsupportedYarnLockfileVersion")); } @@ -696,7 +697,7 @@ pub(crate) fn migrate_yarn_lockfile<'a>( install::initialize_store(); bun_core::analytics::Features::yarn_migration_inc(1); - // PORT NOTE: reshaped for borrowck. Zig keeps a single `var string_buf = + // reshaped for borrowck. Zig keeps a single `var string_buf = // this.stringBuf()` for the whole function, but in Rust that would hold // `&mut this.buffers.string_bytes` + `&mut this.string_pool` for the // function's lifetime and lock out every other `this.*` access. Instead, @@ -742,7 +743,7 @@ pub(crate) fn migrate_yarn_lockfile<'a>( }; drop(package_json_fd); // close now; fd no longer needed past path resolution - // PORT NOTE: Zig passes `comptime opts: js_lexer.JSONOptions`; the Rust + // Zig passes `comptime opts: js_lexer.JSONOptions`; the Rust // port spells the 8 option flags out as const generics (stable Rust has // no struct const-generics). Unspecified Zig fields default to false. let json_bump = bun_alloc::Arena::new(); @@ -940,7 +941,8 @@ pub(crate) fn migrate_yarn_lockfile<'a>( } } - let name: &[u8] = if let (true, Some(resolved)) = (is_npm_alias, entry.resolved) { + let name: &[u8] = if let (true, Some(resolved)) = (is_npm_alias, entry.resolved.as_deref()) + { Entry::get_package_name_from_resolved_url(resolved) .unwrap_or_else(|| Entry::get_name_from_spec(entry.specs[0])) } else if is_direct_url { @@ -1036,12 +1038,13 @@ pub(crate) fn migrate_yarn_lockfile<'a>( } } - let base_name: &[u8] = if let (true, Some(resolved)) = (is_npm_alias, entry.resolved) { - Entry::get_package_name_from_resolved_url(resolved) - .unwrap_or_else(|| Entry::get_name_from_spec(entry.specs[0])) - } else { - Entry::get_name_from_spec(entry.specs[0]) - }; + let base_name: &[u8] = + if let (true, Some(resolved)) = (is_npm_alias, entry.resolved.as_deref()) { + Entry::get_package_name_from_resolved_url(resolved) + .unwrap_or_else(|| Entry::get_name_from_spec(entry.specs[0])) + } else { + Entry::get_name_from_spec(entry.specs[0]) + }; let package_id = yarn_entry_to_package_id[yarn_idx]; if (package_id as usize) < package_id_to_yarn_idx.len() @@ -1055,7 +1058,7 @@ pub(crate) fn migrate_yarn_lockfile<'a>( let name_to_use: &[u8] = 'blk: { if entry.commit.is_some() && entry.git_repo_name.is_some() { break 'blk entry.git_repo_name.as_deref().unwrap(); - } else if let Some(resolved) = entry.resolved { + } else if let Some(resolved) = entry.resolved.as_deref() { if is_direct_url_dep || Entry::is_remote_tarball(resolved) || resolved.ends_with(b".tgz") @@ -1084,7 +1087,7 @@ pub(crate) fn migrate_yarn_lockfile<'a>( let name_hash = string_hash(name_to_use); - // PORT NOTE: reshaped for borrowck — compute the resolution before the + // reshaped for borrowck — compute the resolution before the // `this.packages.append(...)` call so the per-field `sbuf!()` borrows of // `this.buffers.string_bytes` don't overlap the two-phase reservation // on `this.packages`. @@ -1103,7 +1106,7 @@ pub(crate) fn migrate_yarn_lockfile<'a>( break 'blk Resolution::init(ResolutionValue::Folder(sbuf!().append(file)?)); } } else if let Some(commit) = entry.commit { - if let Some(resolved) = entry.resolved { + if let Some(resolved) = entry.resolved.as_deref() { let mut owner_str: &[u8] = b""; let mut repo_str: &[u8] = resolved; @@ -1146,7 +1149,7 @@ pub(crate) fn migrate_yarn_lockfile<'a>( } } break 'blk Resolution::default(); - } else if let Some(resolved) = entry.resolved { + } else if let Some(resolved) = entry.resolved.as_deref() { if is_direct_url_dep { break 'blk Resolution::init(ResolutionValue::RemoteTarball( sbuf!().append(resolved)?, @@ -1228,7 +1231,7 @@ pub(crate) fn migrate_yarn_lockfile<'a>( })?; } - // PORT NOTE: Zig holds two `items(.field)` slices simultaneously; the + // Zig holds two `items(.field)` slices simultaneously; the // derive's `&mut self` accessors can't alias, so we re-borrow per write // below via `this.packages.items_*_mut()[idx] = …` instead of caching. @@ -1309,7 +1312,7 @@ pub(crate) fn migrate_yarn_lockfile<'a>( let dependencies_start = dependencies_buf.as_mut_ptr(); let resolutions_start = resolutions_buf.as_mut_ptr(); - // PORT NOTE: reshaped for borrowck — iterate by index and re-borrow + // reshaped for borrowck — iterate by index and re-borrow // `yarn_lock.entries[yarn_idx]` for each map so the shared borrow of // `yarn_lock` passed into `process_deps` doesn't overlap an iterator. if let Some(deps) = yarn_lock.entries[yarn_idx].dependencies.as_ref() { @@ -1440,7 +1443,7 @@ pub(crate) fn migrate_yarn_lockfile<'a>( ) .expect("unreachable"); - // PORT NOTE: reshaped for borrowck — find_entry_by_spec via index search + // reshaped for borrowck — find_entry_by_spec via index search // instead of returning &mut to avoid overlapping borrow with the loop below. let dep_entry_specs: Option> = { let mut found: Option> = None; @@ -1511,7 +1514,7 @@ pub(crate) fn migrate_yarn_lockfile<'a>( versions.sort_by_key(|a| a.package_id); let original_name_hash = string_hash(base_name); - // PORT NOTE: reshaped for borrowck — Zig matches on the entry only to + // reshaped for borrowck — Zig matches on the entry only to // call `existing_ids.deinit()` before `remove`; Rust's `remove` drops // the value (and thus the `Ids` Vec) automatically, so the match is // unnecessary and we avoid the overlapping `get_mut`/`remove` borrow. @@ -1712,7 +1715,7 @@ pub(crate) fn migrate_yarn_lockfile<'a>( continue; } - if let Some(resolved) = entry.resolved { + if let Some(resolved) = entry.resolved.as_deref() { if let Some(real_name) = Entry::get_package_name_from_resolved_url(resolved) { for spec in entry.specs.iter() { let alias_name = Entry::get_name_from_spec(spec); @@ -2043,7 +2046,6 @@ pub(crate) fn migrate_yarn_lockfile<'a>( let result = LoadResult::Ok(lockfile::LoadResultOk { lockfile: this, - // TODO(port): LoadResult.ok stores *Lockfile; lifetime/ownership not yet resolved migrated: lockfile::Migrated::Yarn, loaded_from_binary_lockfile: false, serializer_result: Default::default(), diff --git a/src/install_jsc/dependency_jsc.rs b/src/install_jsc/dependency_jsc.rs index 3ef276570ab3..3fb719c030ba 100644 --- a/src/install_jsc/dependency_jsc.rs +++ b/src/install_jsc/dependency_jsc.rs @@ -33,7 +33,7 @@ pub(crate) fn version_to_js( BunString::static_(<&'static str>::from(dep.tag).as_bytes()).to_js(global)?, ); - // PORT NOTE: `dependency::Version` keeps `Value` as a `#[repr(C)] union` + // `dependency::Version` keeps `Value` as a `#[repr(C)] union` // (discriminant in `Version.tag`); the tag-checked accessors on // `DependencyVersion` (`npm()`, `git()`, …) wrap the union read. match dep.tag { @@ -168,7 +168,7 @@ pub fn dependency_from_js(global: &JSGlobalObject, frame: &CallFrame) -> JsResul }; let name_slice = name_value.to_slice(global)?; - // PORT NOTE: reshaped for borrowck — Zig built `name`/`alias`/`buf` as + // Reshaped for borrowck — Zig built `name`/`alias`/`buf` as // overlapping slices into a StringBuilder's single allocation. Rust's // `StringBuilder::append` returns `&[u8]` borrowing `&mut self`, so we // can't hold two appended slices at once. Instead, build into an owned diff --git a/src/install_jsc/hosted_git_info_jsc.rs b/src/install_jsc/hosted_git_info_jsc.rs index 04f719a948b4..843673e4cdc2 100644 --- a/src/install_jsc/hosted_git_info_jsc.rs +++ b/src/install_jsc/hosted_git_info_jsc.rs @@ -75,8 +75,9 @@ pub fn js_parse_url(go: &JSGlobalObject, callframe: &CallFrame) -> JsResult p, @@ -114,7 +115,8 @@ pub fn js_from_url(go: &JSGlobalObject, callframe: &CallFrame) -> JsResult p, diff --git a/src/install_jsc/ini_jsc.rs b/src/install_jsc/ini_jsc.rs index a5a84fef2168..f8bee14437d7 100644 --- a/src/install_jsc/ini_jsc.rs +++ b/src/install_jsc/ini_jsc.rs @@ -41,7 +41,7 @@ impl IniTestingAPIs { // (all `allocator.create`/`toOwnedSlice` below now use the global mimalloc) let envjs = frame.argument(1); - // PORT NOTE: reshaped for borrowck — Zig returned either a VM-owned *Loader or + // Reshaped for borrowck — Zig returned either a VM-owned *Loader or // an arena-allocated *Loader from a labeled block. Per PORTING.md §Forbidden // (`Box::leak` is banned), keep both `Map` and `Loader` owned in fn-scope // `Option`s and hand out a raw `*mut Loader` uniformly. Both drop at fn @@ -143,7 +143,7 @@ impl IniTestingAPIs { }; // `defer { *.deref() }` deleted — bun_core::String impls Drop. - // PORT NOTE: `jsc.JSObject.create(.{ .field = val, ... }, global)` reflects over + // `jsc.JSObject.create(.{ .field = val, ... }, global)` reflects over // an anon struct's fields at comptime. Rust has no field reflection; mirror with // a local `PojoFields` impl (the bun_jsc-convention until `#[derive(PojoFields)]` // lands) so each `bun.String → JSValue` encoding interleaves with `put()` and @@ -206,7 +206,7 @@ impl IniTestingAPIs { let utf8str = bunstr.to_utf8(); let env = global.bun_vm().as_mut().transpiler.env_mut(); - // TODO(port): lifetime — `Parser::init` ties `src: &'a [u8]` and + // `Parser::init` ties `src: &'a [u8]` and // `env: &'a mut DotEnvLoader<'a>` to one invariant `'a`; the VM-owned // env is `'static`, so erase `src` to match. SAFETY: `parser` is dropped // before `utf8str` (drop order is reverse of declaration); no borrow @@ -214,7 +214,7 @@ impl IniTestingAPIs { let src: &'static [u8] = bun_ast::IntoStr::into_str(utf8str.slice()); let mut parser = Parser::init(b"", src, env); - // PORT NOTE: borrowck — `Parser::parse` takes `&'a Arena` (Zig passed + // Borrowck — `Parser::parse` takes `&'a Arena` (Zig passed // `parser.arena.arena()`); split the borrow via raw ptr so the bump // outlives the `&mut parser` for the call. let arena_ptr: *const bun_alloc::Arena = &raw const parser.arena; diff --git a/src/install_jsc/install_binding.rs b/src/install_jsc/install_binding.rs index 2a436db92ee8..31c9c4e3c6a6 100644 --- a/src/install_jsc/install_binding.rs +++ b/src/install_jsc/install_binding.rs @@ -22,7 +22,7 @@ pub mod bun_install_js_bindings { obj } - // PORT NOTE: lives at module scope (not in an `impl`) because the + // Lives at module scope (not in an `impl`) because the // `#[bun_jsc::host_fn]` Free-kind shim body emits `#fn_name(__g, __f)` without // a `Self::` qualifier, so the wrapped fn must resolve unqualified. #[bun_jsc::host_fn] @@ -67,7 +67,7 @@ pub mod bun_install_js_bindings { let mut lockfile_ = Lockfile::default(); - // PORT NOTE: reshaped for borrowck — Zig walked + // Reshaped for borrowck — Zig walked // `globalObject.bunVM().transpiler.resolver` through chained pointer // dereferences. `bun_vm()` returns `*mut VirtualMachine` (raw, mirroring // Zig's `*VirtualMachine`); deref locally so the env-loader fixup and the diff --git a/src/install_jsc/lib.rs b/src/install_jsc/lib.rs index 1882fc02176e..bf13ed5b6dce 100644 --- a/src/install_jsc/lib.rs +++ b/src/install_jsc/lib.rs @@ -4,19 +4,8 @@ //! `JSValue`/`JSGlobalObject`/`CallFrame` references. //! //! Host-fn bodies for hosted_git_info / dependency / update_request / npm / -//! install_binding compile against the `bun_jsc` + `bun_install` surface. -//! `npm_jsc::js_parse_manifest` body uses `PackageManifest::{name, versions, -//! string_buf}` from `bun_install::npm`. Remaining gaps are the exact missing -//! lower-tier symbols (`JSFunction::create` ↔ -//! `#[bun_jsc::host_fn]` shim-name bridge for associated fns, -//! `bun_bundler::Transpiler` mutable field access via `bun_vm()`, -//! `bun_resolver::Resolver::get_package_manager`, `bun_ini::load_npmrc` real -//! signature) and tagged `// TODO(port): bun_X::Y`. -//! -//! NOTE: `cargo check -p bun_install_jsc` is currently hard-blocked on -//! transitive lower-tier compile failures in `bun_css` / `bun_http` / -//! `bun_js_parser` (via `bun_jsc → bun_bundler`); no edits in this crate can -//! be cargo-verified until those un-gate. +//! install_binding compile against the `bun_jsc` + `bun_install` surface and +//! are wired into the runtime via the `dispatch_js2native` re-exports. pub mod dependency_jsc; pub mod hosted_git_info_jsc; diff --git a/src/install_jsc/npm_jsc.rs b/src/install_jsc/npm_jsc.rs index 018c67534445..8528efe2e9c0 100644 --- a/src/install_jsc/npm_jsc.rs +++ b/src/install_jsc/npm_jsc.rs @@ -78,7 +78,7 @@ impl ManifestBindings { } } -// PORT NOTE: lives at module scope (not `impl ManifestBindings`) because the +// Lives at module scope (not `impl ManifestBindings`) because the // `#[bun_jsc::host_fn]` Free-kind shim body emits `#fn_name(__g, __f)` without // a `Self::` qualifier, so the wrapped fn must resolve unqualified. #[bun_jsc::host_fn] @@ -106,8 +106,6 @@ pub(crate) fn js_parse_manifest(global: &JSGlobalObject, frame: &CallFrame) -> J let registry_str = scopeguard::guard(args[1].to_bun_string(global)?, |s| s.deref()); let registry = registry_str.to_utf8(); - // PORT NOTE: Zig used `std.fs.cwd().openFile`; PORTING.md bans std::fs, so go - // through bun_sys (read-only open). let manifest_file = match bun_sys::openat_a( bun_sys::Fd::cwd(), manifest_filename.slice(), @@ -124,7 +122,7 @@ pub(crate) fn js_parse_manifest(global: &JSGlobalObject, frame: &CallFrame) -> J } }; - // PORT NOTE: Zig built a borrowing `bun.URL` struct literal (host/hostname/ + // Zig built a borrowing `bun.URL` struct literal (host/hostname/ // href/origin/protocol all slicing `registry`). The Rust `Scope.url` field // is `OwnedURL`, which stores only the href buffer and re-derives components // via `URL::parse` on demand. `load_by_file`/`read_all` only consult @@ -139,8 +137,6 @@ pub(crate) fn js_parse_manifest(global: &JSGlobalObject, frame: &CallFrame) -> J let maybe_package_manifest = match npm::package_manifest::Serializer::load_by_file( &scope, - // PORT NOTE: Zig wrapped std.fs.File via `bun.sys.File.from(...)`; we already - // opened a bun_sys::File above, so pass directly. &manifest_file, ) { Ok(m) => m, diff --git a/src/install_jsc/update_request_jsc.rs b/src/install_jsc/update_request_jsc.rs index 1c064a09436c..6c180c5d5a6c 100644 --- a/src/install_jsc/update_request_jsc.rs +++ b/src/install_jsc/update_request_jsc.rs @@ -9,8 +9,8 @@ pub fn from_js(global: &JSGlobalObject, input: JSValue) -> JsResult { // PERF(port): was arena bulk-free — profile if hot // PERF(port): was stack-fallback — profile if hot - // PORT NOTE: `to_slice_clone` returns `ZigStringSlice`; convert to owned - // `Vec` via `.into_vec()` since the Zig arena is gone. + // `to_slice_clone` returns `ZigStringSlice`; convert to owned `Vec` + // via `.into_vec()` since there is no arena backing the slices here. let mut all_positionals: Vec> = Vec::new(); let mut log = Log::init(); @@ -39,7 +39,6 @@ pub fn from_js(global: &JSGlobalObject, input: JSValue) -> JsResult { let mut array = update_request::Array::default(); - // PORT NOTE: reshaped for borrowck — build a `&[&[u8]]` view over the owned buffers let positionals_view: Vec<&[u8]> = all_positionals.iter().map(|s| s.as_slice()).collect(); let update_requests = match UpdateRequest::parse_with_error( diff --git a/src/install_types/NodeLinker.rs b/src/install_types/NodeLinker.rs index 9b4e57f9ac1c..0bd8d166fc78 100644 --- a/src/install_types/NodeLinker.rs +++ b/src/install_types/NodeLinker.rs @@ -47,8 +47,9 @@ pub mod npm { /// `bun.Wyhash11.hash(0, strings.withoutTrailingSlash(default_url))` /// — i.e. hash of `b"https://registry.npmjs.org"` (no trailing `/`). - // TODO(port): once bun_wyhash::Wyhash11::hash is `const fn`, fold this - // back to a `pub const`. For now compute on first use. + // Computed on use because `bun_wyhash::Wyhash11::hash` is not a + // `const fn` (only `Wyhash::hash_const` — a different algorithm — + // exists). Cheap and cold; not worth a cached static. #[inline] pub fn default_url_hash() -> u64 { use bun_wyhash::Wyhash11; @@ -287,9 +288,8 @@ impl PnpmMatcher { return false; } - // PORT NOTE: Zig `bun.String.fromBytes(name)`. `from_bytes` not yet on - // bun_string surface; package names are ASCII so `borrow_utf8` is an - // equivalent zero-copy borrow for the regex match call. + // Zig used `bun.String.fromBytes(name)`; package names are ASCII so + // `borrow_utf8` is an equivalent zero-copy borrow for the regex match. let name_str = BunString::borrow_utf8(name); match self.behavior { diff --git a/src/install_types/resolver_hooks.rs b/src/install_types/resolver_hooks.rs index 6604abf0f8fb..c6bfb3ab4060 100644 --- a/src/install_types/resolver_hooks.rs +++ b/src/install_types/resolver_hooks.rs @@ -1413,7 +1413,7 @@ impl WakeHandler { pub fn get_on_dependency_error( &self, ) -> unsafe fn(*mut c_void, &Dependency, DependencyID, bun_core::Error) { - // PORT NOTE: Zig casts `t.handler` (the wrong field) to the dep-error fn type — this is + // Zig casts `t.handler` (the wrong field) to the dep-error fn type — this is // a Zig bug. The port reads `on_dependency_error` instead; preserving the bug would // require an unsound transmute between fn-pointer signatures. self.on_dependency_error.unwrap() diff --git a/src/io/ParentDeathWatchdog.rs b/src/io/ParentDeathWatchdog.rs index bdcf10852c35..8b7de1e6611f 100644 --- a/src/io/ParentDeathWatchdog.rs +++ b/src/io/ParentDeathWatchdog.rs @@ -55,9 +55,9 @@ pub struct ParentDeathWatchdog; /// convention for "terminated because the controlling end went away". pub const EXIT_CODE: u8 = 128 + 1; -// PORT NOTE: Zig used plain `var` globals (unsynchronized). Converted to -// atomics/OnceLock per docs/PORTING.md §Global mutable state — same -// single-writer-at-startup discipline, but no `static mut` aliasing. +// Zig used plain `var` globals (unsynchronized). Converted to atomics/OnceLock +// per docs/PORTING.md §Global mutable state — same single-writer-at-startup +// discipline, but no `static mut` aliasing. static ENABLED: AtomicBool = AtomicBool::new(false); static ORIGINAL_PPID: core::sync::atomic::AtomicI32 = core::sync::atomic::AtomicI32::new(0); @@ -165,7 +165,6 @@ fn kill_sync_pgroups_and_descendants() { } } -// TODO(port): move to _sys #[cfg(target_os = "macos")] unsafe extern "C" { // safe: no args; no preconditions. @@ -532,7 +531,7 @@ fn kill_tree_rooted_at(root: libc::pid_t, expected_ppid_of_root: libc::pid_t) { to_kill.push(root); let _ = to_visit.try_reserve(1); to_visit.push(root); - // PORT NOTE: Zig swallowed OOM on the to_visit push; Rust push() after a + // Zig swallowed OOM on the to_visit push; Rust push() after a // failed try_reserve would still attempt (and abort on OOM). In practice // a 1-element reserve never fails; matching exact Zig OOM semantics is // not worth the complexity here. @@ -680,7 +679,7 @@ fn list_child_pids_linux(parent: libc::pid_t, out: &mut [libc::pid_t]) -> Option let mut w = &mut path_buf[..]; write!(w, "/proc/{}/task", parent).ok()?; let n = 64 - w.len(); - // PORT NOTE: reshaped for borrowck — recompute slice after write. + // Reshaped for borrowck — recompute slice after write. &path_buf[..n] }; @@ -688,7 +687,7 @@ fn list_child_pids_linux(parent: libc::pid_t, out: &mut [libc::pid_t]) -> Option Ok(fd) => fd, Err(_) => return None, }; - // PORT NOTE: Zig `defer task_fd.close()`; `Fd` is Copy and does not impl Drop. + // Zig `defer task_fd.close()`; `Fd` is Copy and does not impl Drop. let _task_fd_guard = scopeguard::guard(task_fd, |fd| { let _ = bun_sys::close(fd); }); diff --git a/src/io/PipeReader.rs b/src/io/PipeReader.rs index 37f31e8d0b09..6df5248e9fdd 100644 --- a/src/io/PipeReader.rs +++ b/src/io/PipeReader.rs @@ -153,7 +153,7 @@ pub struct PosixBufferedReader { pub vtable: BufferedReaderVTable, pub flags: PosixFlags, pub count: usize, - // PORT NOTE: MaxBuf uses hand-rolled dual-ownership (Subprocess + reader) via + // MaxBuf uses hand-rolled dual-ownership (Subprocess + reader) via // `add_to_pipereader`/`remove_from_pipereader`, not Arc — see MaxBuf.rs. pub maxbuf: Option>, } @@ -223,12 +223,11 @@ impl PosixBufferedReader { count: 0, maxbuf: None, }; - // PORT NOTE: `other.buffer().* = init(default_allocator)` and - // `other.handle = .closed` handled by mem::replace/mem::take above. other.flags.insert(PosixFlags::IS_DONE); other._offset = 0; MaxBuf::transfer_to_pipereader(&mut other.maxbuf, &mut self.maxbuf); - // PORT NOTE: reshaped for borrowck — capture *mut Self before borrowing field. + // Capture *mut Self before borrowing `handle` so the owner pointer + // doesn't conflict with the field borrow. let owner = std::ptr::from_mut(self).cast::(); self.handle .set_owner(Owner::new(PollTag::BufferedReader, owner.cast())); @@ -239,7 +238,8 @@ impl PosixBufferedReader { pub fn set_parent(&mut self, parent: *mut c_void) { self.vtable.parent = parent; - // PORT NOTE: reshaped for borrowck — capture *mut Self before borrowing field. + // Capture *mut Self before borrowing `handle` so the owner pointer + // doesn't conflict with the field borrow. let owner = std::ptr::from_mut(self).cast::(); self.handle .set_owner(Owner::new(PollTag::BufferedReader, owner.cast())); @@ -329,8 +329,8 @@ impl PosixBufferedReader { pub fn final_buffer(&mut self) -> &mut Vec { if self.flags.contains(PosixFlags::MEMFD) { if let PollOrFd::Fd(fd) = self.handle { - // PORT NOTE: Zig `defer this.handle.close(null, {})` — close after - // the read regardless of result. `self.handle` owns the fd; + // The handle is closed after the read regardless of result. + // `self.handle` owns the fd; // borrow a non-owning `File` view so the temporary doesn't // close it on drop (handle.close() below does). let result = sys::File::borrow(&fd) @@ -403,7 +403,7 @@ impl PosixBufferedReader { } pub fn register_poll(&mut self) { - // PORT NOTE: reshaped for borrowck — hoist vtable-derived scalars and + // Hoist vtable-derived scalars and // normalize self.handle to Poll before taking the single &mut borrow, // so no raw-pointer escape is needed. let ev = self.vtable.event_loop(); @@ -539,7 +539,7 @@ impl PosixBufferedReader { } } - // PORT NOTE: reshaped for borrowck — takes &vtable instead of &mut Self so + // Takes &vtable instead of &mut Self so // call sites can pass &parent._buffer alongside without a raw-pointer escape. #[inline] fn drain_chunk(vtable: &BufferedReaderVTable, chunk: &[u8], has_more: ReadState) -> bool { @@ -599,9 +599,9 @@ impl PosixBufferedReader { ); } - // PORT NOTE: reshaped for borrowck — `resizable_buffer` is no longer passed - // separately; functions access `parent._buffer` directly. In Zig the buffer - // pointer was always `parent.buffer()` anyway. + // Unlike the Zig original, `resizable_buffer` is not passed separately; + // functions access `parent._buffer` directly (in Zig the buffer pointer + // was always `parent.buffer()` anyway). fn read_blocking_pipe( parent: &mut PosixBufferedReader, fd: Fd, @@ -710,7 +710,6 @@ impl PosixBufferedReader { } if streaming { - // PORT NOTE: reshaped for borrowck — re-slice from _buffer. let new_len = parent._buffer.len(); let chunk = &parent._buffer[new_len - bytes_read..new_len]; if !parent.vtable.on_read_chunk( @@ -860,7 +859,7 @@ impl PosixBufferedReader { // Keep reading as much as we can if (stack_buffer_len - head_start) < stack_buffer_cutoff { - // PORT NOTE: `&& !received_hup` mirrors the + // `&& !received_hup` mirrors the // after-inner-loop flush below (line ~855). // Without it, a peer close (HUP) with >cutoff // bytes still buffered makes a parent that @@ -1015,7 +1014,6 @@ impl PosixBufferedReader { if parent.vtable.is_streaming_enabled() { if parent._buffer.len() > 128_000 { - // PORT NOTE: `defer resizable_buffer.clearRetainingCapacity()` inlined below. let keep_going = parent .vtable .on_read_chunk(&parent._buffer, ReadState::Progress); @@ -1066,11 +1064,11 @@ impl PosixBufferedReader { Self::read_blocking_pipe(parent, fd, size_hint, received_hup); } - - // PORT NOTE: `comptime { bun.meta.banFieldType(@This(), bool); }` dropped — - // bitflags! ensures bools are packed. } +// Keep boolean state in the `PosixFlags` bitflags field — no loose `bool` +// fields on `PosixBufferedReader` (Zig enforced this via `banFieldType`). + impl Drop for PosixBufferedReader { fn drop(&mut self) { MaxBuf::remove_from_pipereader(&mut self.maxbuf); @@ -1151,7 +1149,7 @@ impl WindowsBufferedReader { pub fn from(&mut self, other: &mut WindowsBufferedReader, parent: *mut c_void) { debug_assert!(other.source.is_some() && self.source.is_none()); - // PORT NOTE: keep self.vtable; move other's state in. + // Keep self.vtable; move other's state in. self.flags = other.flags; self._buffer = mem::take(other.buffer()); self._offset = other._offset; @@ -1505,7 +1503,6 @@ impl WindowsBufferedReader { return; } - // PORT NOTE: defer block inlined after body — see below. let len: usize = usize::try_from(nread_int).expect("int cast"); this._offset += len; // we got some data lets get the current iov @@ -1535,8 +1532,8 @@ impl WindowsBufferedReader { ); } - // PORT NOTE: this is the Zig `defer { ... }` body, inlined after the body - // because both body paths fall through (void return). + // This is the Zig original's `defer { ... }` body, inlined here + // because both body paths above fall through (void return). // if we are not paused we keep reading until EOF or err if !this.flags.contains(WindowsFlags::IS_PAUSED) { // Re-snapshot — `on_read` may have mutated `this.source`. @@ -1574,7 +1571,7 @@ impl WindowsBufferedReader { Some(Self::on_file_read), ) } - // PORT NOTE: Zig PipeReader.zig:1113 tags this `.write` even + // Zig PipeReader.zig:1113 tags this `.write` even // though the syscall is `uv_fs_read` (a Zig bug). Match the // spec for now so user-visible `error.syscall` stays // bit-identical; fix upstream in Zig first. @@ -1647,7 +1644,7 @@ impl WindowsBufferedReader { Some(Self::on_file_read), ) } - // PORT NOTE: Zig PipeReader.zig:1163 tags this `.write` even though the + // Zig PipeReader.zig:1163 tags this `.write` even though the // syscall is `uv_fs_read` (a Zig bug). Match the spec for now so // user-visible `error.syscall` stays bit-identical; fix upstream in // Zig first. @@ -1796,7 +1793,7 @@ impl WindowsBufferedReader { }; if !source.is_closed() { // closeImpl will take care of freeing the source. - // PORT NOTE: Zig nulls `source` *before* calling closeImpl, which + // Zig nulls `source` *before* calling closeImpl, which // makes that call a no-op (latent Zig leak). We cannot mirror that // verbatim: in Zig nulling a `?*Pipe` leaks; in Rust dropping // `Box` frees a uv_pipe_t still linked into the loop's @@ -1865,11 +1862,11 @@ impl WindowsBufferedReader { let should_continue = self._on_read_chunk(slice, has_more); - // PORT NOTE: Spec parents that stream (IOReader.zig:161, + // Spec parents that stream (IOReader.zig:161, // shell/subproc.zig:1230) call `this.reader.startWithCurrentPipe()` // from inside their `onReadChunk` callback on Windows. The Rust // shell IOReader port cannot re-derive `&mut Self` from inside the - // vtable callback (Stacked-Borrows; see shell/IOReader.rs PORT NOTE), + // vtable callback (Stacked-Borrows; see the comment in shell/IOReader.rs), // so the call is omitted there. The re-arm half of that call is // already handled by `on_file_read`'s defer block / `uv_read_start`, // but its other side effect — `buffer().clearRetainingCapacity()` @@ -1898,11 +1895,11 @@ impl WindowsBufferedReader { // we cannot sync read pipes on Windows so we just check if we are paused to resume the reading self.unpause(); } - - // PORT NOTE: `comptime { bun.meta.banFieldType(WindowsBufferedReader, bool); }` dropped — - // bitflags! ensures bools are packed. } +// Keep boolean state in the `WindowsFlags` bitflags field — no loose `bool` +// fields on `WindowsBufferedReader` (Zig enforced this via `banFieldType`). + #[cfg(windows)] impl Drop for WindowsBufferedReader { fn drop(&mut self) { @@ -1910,7 +1907,7 @@ impl Drop for WindowsBufferedReader { // Do NOT take() source here and let it drop: Box/Box own // live uv handles registered with the loop. Let close_impl perform the // take + into_raw hand-off so the uv close callback reclaims them. - // PORT NOTE: Zig `WindowsBufferedReader.deinit` (PipeReader.zig:979) + // Zig `WindowsBufferedReader.deinit` (PipeReader.zig:979) // skips closeImpl when `source.isClosed()` — a uv_close is already // pending on that allocation, so closing again would double-close and // freeing the Box would UAF the handle libuv still references. Mirror diff --git a/src/io/PipeWriter.rs b/src/io/PipeWriter.rs index e98473560247..3952232d896f 100644 --- a/src/io/PipeWriter.rs +++ b/src/io/PipeWriter.rs @@ -22,13 +22,6 @@ use crate::source::Source; bun_core::define_scoped_log!(log, PipeWriter, hidden); -// TODO(port): bun_sys::Error::oom — `oom()` is a private free fn in -// `bun_sys::error`; promote to assoc fn or re-export, then drop this shim. -#[inline] -fn oom_err() -> sys::Error { - sys::Error::from_code(sys::E::ENOMEM, sys::Tag::write) -} - // ────────────────────────────────────────────────────────────────────────── // WriteResult / WriteStatus // ────────────────────────────────────────────────────────────────────────── @@ -130,7 +123,7 @@ pub trait PosixPipeWriter { } fn on_poll(&mut self, size_hint: isize, received_hup: bool) { - // PORT NOTE: reshaped for borrowck — capture buffer.len() before further &mut self calls. + // reshaped for borrowck — capture buffer.len() before further &mut self calls. let buffer_len = self.get_buffer().len(); log!("onPoll({})", buffer_len); if buffer_len == 0 && !received_hup { @@ -548,7 +541,7 @@ impl PosixBufferedWriter { self.parent = Some(bun_ptr::ParentRef::from( core::ptr::NonNull::new(parent).expect("set_parent: parent must not be null"), )); - // PORT NOTE: reshaped for borrowck — capture *mut Self before borrowing field. + // reshaped for borrowck — capture *mut Self before borrowing field. let owner = std::ptr::from_mut(self).cast::(); self.handle .set_owner(Owner::new(Parent::POLL_OWNER_TAG, owner.cast())); @@ -569,8 +562,10 @@ impl PosixBufferedWriter { } } - /// Zig accepts `bun.FD`, `*bun.MovableIfWindowsFd`, or `bun.MovableIfWindowsFd`. - // TODO(port): MovableIfWindowsFd overload — add an Into bound or a separate fn. + /// Zig accepts `bun.FD`, `*bun.MovableIfWindowsFd`, or `bun.MovableIfWindowsFd` + /// and reduces them all to the POSIX fd (`rawfd.getPosix()`); on POSIX a + /// `MovableIfWindowsFd` never transfers ownership, so Rust callers pass the + /// plain `Fd` (via `MovableIfWindowsFd::get_posix()` when needed). pub fn start(&mut self, rawfd: Fd, pollable: bool) -> sys::Result<()> { let fd = rawfd; self.pollable = pollable; @@ -696,9 +691,13 @@ unsafe impl bun_ptr::LaunderedSelf } impl PosixStreamingWriter { - // TODO: configurable? - // TODO(port): std.heap.page_size_min — pick correct const for target. - const CHUNK_SIZE: usize = 4096; + // Zig `std.heap.page_size_min` — the smallest page size the target + // supports (16K on Apple Silicon, 4K elsewhere among our targets). + const CHUNK_SIZE: usize = if cfg!(all(target_os = "macos", target_arch = "aarch64")) { + 16384 + } else { + 4096 + }; /// Raw backref to the owning `Parent`. Returned as `*mut` (never `&mut`) /// because this writer is an intrusive field of `Parent` and a `&mut Parent` @@ -794,7 +793,7 @@ impl PosixStreamingWriter { pub fn set_parent(&mut self, parent: *mut Parent) { self.parent = parent; - // PORT NOTE: reshaped for borrowck — capture *mut Self before borrowing field. + // reshaped for borrowck — capture *mut Self before borrowing field. let owner = std::ptr::from_mut(self).cast::(); self.handle .set_owner(Owner::new(Parent::POLL_OWNER_TAG, owner.cast())); @@ -852,7 +851,7 @@ impl PosixStreamingWriter { let before_len = self.outgoing.size(); if self.outgoing.write_utf16(buf).is_err() { - return WriteResult::Err(oom_err()); + return WriteResult::Err(sys::Error::oom()); } let buf_len = self.outgoing.size() - before_len; @@ -873,7 +872,7 @@ impl PosixStreamingWriter { const CHECK_ASCII: bool = false; if self.outgoing.write_latin1::(buf).is_err() { - return WriteResult::Err(oom_err()); + return WriteResult::Err(sys::Error::oom()); } let buf_len = self.outgoing.size() - before_len; @@ -943,7 +942,7 @@ impl PosixStreamingWriter { // this is streaming, but we buffer the data below `chunk_size` to // reduce the number of writes if self.outgoing.write(buf).is_err() { - return WriteResult::Err(oom_err()); + return WriteResult::Err(sys::Error::oom()); } // noop, but need this to have a chance @@ -959,7 +958,7 @@ impl PosixStreamingWriter { if self.outgoing.size() > 0 { // make sure write is in-order if self.outgoing.write(buf).is_err() { - return WriteResult::Err(oom_err()); + return WriteResult::Err(sys::Error::oom()); } return self.try_write_newly_buffered_data(); @@ -970,7 +969,7 @@ impl PosixStreamingWriter { match rc { WriteResult::Pending(amt) => { if self.outgoing.write(&buf[amt..]).is_err() { - return WriteResult::Err(oom_err()); + return WriteResult::Err(sys::Error::oom()); } self.parent_on_write(amt, WriteStatus::Pending); Self::register_poll(self); @@ -978,7 +977,7 @@ impl PosixStreamingWriter { WriteResult::Wrote(amt) => { if amt < buf.len() { if self.outgoing.write(&buf[amt..]).is_err() { - return WriteResult::Err(oom_err()); + return WriteResult::Err(sys::Error::oom()); } self.parent_on_write(amt, WriteStatus::Pending); Self::register_poll(self); @@ -1794,8 +1793,8 @@ impl StreamBuffer { } pub fn maybe_shrink(&mut self) { - // TODO(port): std.heap.pageSize() — using 4096; query the actual page size. - let page = 4096usize; + // Zig `std.heap.pageSize()` — runtime page size of the host. + let page = bun_core::page_size(); if self.list.capacity() > page { // Zig: expandToCapacity() then shrinkAndFree(page) — i.e. truncate the // buffer's content to `page` bytes AND release the excess capacity. @@ -1857,7 +1856,6 @@ impl StreamBuffer { buffer_u16: Option<&[u16]>, kind: WriteKind, ) -> Result<&'a [u8], OOM> { - // TODO(refactor): comptime fn-ptr identity dispatch → enum tag; consider unifying with write_internal. match kind { WriteKind::Latin1 => { let buffer = buffer_u8.unwrap(); @@ -1883,7 +1881,7 @@ impl StreamBuffer { } } - // PORT NOTE: Zig round-trips through `Vec::::moveFromList` to call + // Zig round-trips through `Vec::::moveFromList` to call // `writeLatin1`; the underlying op is `allocateLatin1IntoUTF8WithList`, // which we call on the `Vec` directly. let len = self.list.len(); @@ -2398,7 +2396,7 @@ impl WindowsStreamingWriter { let result = (|| { let remain = match self.outgoing.write_or_fallback(Some(buffer), None, kind) { Ok(r) => r, - Err(_) => return WriteResult::Err(oom_err()), + Err(_) => return WriteResult::Err(sys::Error::oom()), }; let initial_len = remain.len(); let mut remain = remain; @@ -2436,7 +2434,7 @@ impl WindowsStreamingWriter { WriteKind::Utf16 => unreachable!(), }; if r.is_err() { - return WriteResult::Err(oom_err()); + return WriteResult::Err(sys::Error::oom()); } if had_buffered_data { return WriteResult::Pending(0); @@ -2458,7 +2456,7 @@ impl WindowsStreamingWriter { .write_or_fallback(None, Some(buffer), WriteKind::Utf16) { Ok(r) => r, - Err(_) => return WriteResult::Err(oom_err()), + Err(_) => return WriteResult::Err(sys::Error::oom()), }; let initial_len = remain.len(); let mut remain = remain; @@ -2491,7 +2489,7 @@ impl WindowsStreamingWriter { let had_buffered_data = self.outgoing.is_not_empty(); if self.outgoing.write_utf16(buffer).is_err() { - return WriteResult::Err(oom_err()); + return WriteResult::Err(sys::Error::oom()); } if had_buffered_data { return WriteResult::Pending(0); diff --git a/src/io/heap.rs b/src/io/heap.rs index 6e9b79265728..a5425ef96f2c 100644 --- a/src/io/heap.rs +++ b/src/io/heap.rs @@ -21,7 +21,7 @@ use core::ptr; /// [1]: https://en.wikipedia.org/wiki/Pairing_heap /// [2]: https://www.boost.org/doc/libs/1_64_0/doc/html/intrusive/intrusive_vs_nontrusive.html // -// PORT NOTE: Zig's `Intrusive(T, Context, less)` takes `less` as a comptime fn-pointer +// Zig's `Intrusive(T, Context, less)` takes `less` as a comptime fn-pointer // parameter. Rust cannot use fn pointers as const generics on stable, so the comparator // is folded into a trait on `Context` (`HeapContext::less`). This preserves // monomorphization (no indirect call) at the cost of requiring the caller to impl the diff --git a/src/io/keep_alive.rs b/src/io/keep_alive.rs index dd40e69ae2ac..2765d77a3cec 100644 --- a/src/io/keep_alive.rs +++ b/src/io/keep_alive.rs @@ -121,7 +121,10 @@ impl KeepAlive { return; } self.status = Status::Inactive; - // TODO(port): vm.pending_unref_counter must be Atomic; Zig uses @atomicRmw .Add .monotonic + // Cross-thread increment: Zig does `@atomicRmw .Add .monotonic` on + // `vm.pending_unref_counter`; the Rust counter is an `AtomicI32` + // RMW'd with `Ordering::Relaxed` for the same reason (see + // `jsc::VirtualMachine::pending_unref_counter`). #[cfg(not(windows))] vm.increment_pending_unref_counter(); // TODO: https://github.com/oven-sh/bun/pull/4410#discussion_r1317326194 diff --git a/src/io/lib.rs b/src/io/lib.rs index a85d2a3a3444..96b724367ec7 100644 --- a/src/io/lib.rs +++ b/src/io/lib.rs @@ -122,7 +122,7 @@ bun_dispatch::link_interface! { pub EventLoopCtx[Js, Mini] { fn platform_event_loop_ptr() -> *mut bun_uws_sys::Loop; fn file_polls_ptr() -> *mut Store; - // PORT NOTE: `alloc_file_poll() -> *mut FilePoll` was removed — it + // `alloc_file_poll() -> *mut FilePoll` was removed — it // returned an *uninitialized* hive slot, and any caller forming // `&mut FilePoll` over it hit validity-invariant UB on the niche- // bearing enum fields. `FilePoll::init` now goes through @@ -293,7 +293,6 @@ pub mod file_poll { pub mod heap; // `source.rs` is Windows-only (libuv pipe/tty/file wrappers). On POSIX the // `Source` type is never constructed; callers are themselves `#[cfg(windows)]`. -// TODO(port): bun_sys::windows::libuv — verify compiles on Windows in CI. #[path = "MaxBuf.rs"] pub mod max_buf; #[path = "openForWriting.rs"] @@ -558,8 +557,6 @@ mod safe_c { pub(super) safe fn kqueue() -> c_int; #[cfg(any(target_os = "linux", target_os = "android"))] pub(super) safe fn epoll_create1(flags: c_int) -> c_int; - #[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))] - pub(super) safe fn eventfd(initval: libc::c_uint, flags: c_int) -> c_int; // Out-param `tp` is `&mut timespec` (non-null, valid for write); libc // only writes the slot and reports failure via the return value — // bad `clk_id` → `EINVAL`, never UB. @@ -570,8 +567,8 @@ mod safe_c { // ─── platform type aliases ──────────────────────────────────────────────────── -/// `bun_sys::linux` doesn't exist yet; use `libc` constants directly. -/// TODO(port): bun_sys::linux — replace with that module once available. +/// `bun_sys` has no `linux` constants module; alias the `libc` constants +/// locally so the call sites below read like the Zig (`bun.linux.*`) original. #[cfg(any(target_os = "linux", target_os = "android"))] mod linux { pub(crate) use libc::epoll_event; @@ -1110,7 +1107,7 @@ impl IoRequestLoop { self.cached_now.set(ts); } - // PORT NOTE: Zig nests the `extern "c" fn clock_gettime_monotonic` decl + // Zig nests the `extern "c" fn clock_gettime_monotonic` decl // inside the `Loop` namespace (io.zig:314); Rust forbids `extern` blocks // inside `impl`, so it's hoisted to `windows_ffi` at module scope. @@ -1324,7 +1321,8 @@ pub struct CloseAction<'a> { // ─── Pollable ───────────────────────────────────────────────────────────────── -// TODO(port): repr must match `bun.TaggedPointer.Tag` (15-bit tag in TaggedPtr). +// Invariant: the repr must fit `bun.TaggedPointer.Tag` (the 15-bit tag packed +// into `Pollable`'s u64 below) — keep `#[repr(u16)]` and values < 2^15. #[repr(u16)] #[derive(Clone, Copy, PartialEq, Eq)] pub enum PollableTag { @@ -1467,10 +1465,8 @@ pub enum Flags { } pub type FlagsSet = enumset::EnumSet; -// TODO(port): `pub const Struct = std.enums.EnumFieldStruct(Flags, bool, false);` — a struct with -// one `bool` field per variant. Unused in this file; provide if external callers need it. -// PORT NOTE: Zig used a `comptime action: enum` const-generic. `adt_const_params` +// Zig used a `comptime action: enum` const-generic. `adt_const_params` // is nightly-only and the body never uses ACTION in a type position — it just // `match`es on it — so demote to a runtime parameter (PORTING.md §Idiom-map). // Three call sites, each with a literal variant — trivially inlined; kqueue @@ -1700,12 +1696,11 @@ impl Poll { log!("error() = {:?}", errno); // SAFETY: poll is the `io_poll` field of a live owner; link-time // extern body matches on `tag`. - // TODO(port): bun_sys::Tag::epoll_ctl unsafe { __bun_io_pollable_on_io_error( tag, poll, - &sys::Error::from_code(errno, sys::Tag::TODO), + &sys::Error::from_code(errno, sys::Tag::epoll_ctl), ) }; } else { @@ -1716,7 +1711,7 @@ impl Poll { } #[cfg(any(target_os = "linux", target_os = "android"))] - // PORT NOTE: `flag` was a comptime param in Zig; `enumset::EnumSetType` cannot be a + // `flag` was a comptime param in Zig; `enumset::EnumSetType` cannot be a // const generic, so it's a runtime arg. The `match` below preserves the exhaustiveness check. pub fn register_for_epoll( &mut self, @@ -1776,8 +1771,7 @@ impl Poll { let errno = sys::get_errno(ctl); if errno != E::SUCCESS { - // TODO(port): bun_sys::Tag::epoll_ctl - return Err(sys::Error::from_code(errno, sys::Tag::TODO)); + return Err(sys::Error::from_code(errno, sys::Tag::epoll_ctl)); } // Only mark if it successfully registered. // If it failed to register, we don't want to unregister it later if @@ -1788,7 +1782,7 @@ impl Poll { self.flags.insert(match flag { Flags::PollReadable => Flags::PollReadable, Flags::PollProcess => { - // PORT NOTE: Zig's `Environment.isLinux` is true on Android too. + // Zig's `Environment.isLinux` is true on Android too. if cfg!(any(target_os = "linux", target_os = "android")) { Flags::PollReadable } else { @@ -1902,11 +1896,11 @@ impl FilePollRef { #[cfg(windows)] { let _ = force; - if self.inner().unregister(loop_) { - Ok(()) - } else { - Err(sys::Error::from_code(sys::E::INVAL, sys::Tag::TODO)) - } + // Zig's windows `FilePoll.unregister` (windows_event_loop.zig:179) + // always returns true — the bool is vestigial, so there is no + // error path to surface here. + let _ = self.inner().unregister(loop_); + Ok(()) } } #[inline] @@ -2042,13 +2036,10 @@ pub mod waker { } pub fn init() -> Result { - // TODO(port): migrate to bun_sys::eventfd (the wrapper exists); - // currently falls back to crate::safe_c::eventfd. - let raw = crate::safe_c::eventfd(0, 0); - if raw < 0 { - return Err(bun_core::Error::from_errno(bun_sys::last_errno())); + match bun_sys::eventfd(0, 0) { + Ok(fd) => Ok(Self::init_with_file_descriptor(fd)), + Err(err) => Err(bun_core::Error::from_errno(i32::from(err.errno))), } - Ok(Self::init_with_file_descriptor(Fd::from_native(raw))) } #[inline] diff --git a/src/io/openForWriting.rs b/src/io/openForWriting.rs index 926a7f8a9bbd..95d7b80b9eaa 100644 --- a/src/io/openForWriting.rs +++ b/src/io/openForWriting.rs @@ -3,11 +3,9 @@ use bun_core::ZStr; use bun_sys::FdExt; use bun_sys::{self, Fd, Mode}; -// PORT NOTE: Zig's `input_path: anytype` type-switches on `@TypeOf(input_path)` between +// Zig's `input_path: anytype` type-switches on `@TypeOf(input_path)` between // `bun.webcore.PathOrFileDescriptor` and `[:0]const u8` / `[:0]u8`. Rust has no type-switch, // so this is expressed as a sealed trait whose impls encode each `switch (PathT)` arm. -// TODO(port): verify callers — if only one input type is ever used per call site, consider -// monomorphizing into two free fns instead of the trait. pub trait OpenForWritingInput { fn open_for_writing_result( &self, diff --git a/src/io/pipes.rs b/src/io/pipes.rs index 7cb1e3ffb8ea..87e946d0859c 100644 --- a/src/io/pipes.rs +++ b/src/io/pipes.rs @@ -77,7 +77,7 @@ impl PollOrFd { // 7) ON ANOTHER THREAD: close(3) = 0, // 8) kevent(2, EVFILT_READ, EV_ADD | EV_ENABLE | EV_DISPATCH, 0, 0, 0) = 0 // 9) ??? No more events for fd 2 - // PORT NOTE: reshaped for borrowck — take ownership of the Box before + // Take ownership of the Box before // calling deinit_force_unregister, then leave self = Closed. let old = core::mem::replace(self, PollOrFd::Closed); if let PollOrFd::Poll(poll) = old { diff --git a/src/io/posix_event_loop.rs b/src/io/posix_event_loop.rs index 630461f43bf4..7ba1ab646e39 100644 --- a/src/io/posix_event_loop.rs +++ b/src/io/posix_event_loop.rs @@ -11,7 +11,7 @@ use bun_uws_sys::Loop as UwsLoop; pub type Loop = UwsLoop; -// PORT NOTE: `addActive`/`subActive` live on `PosixLoop` in Zig (uws_sys/Loop.zig) +// Note: `addActive`/`subActive` live on `PosixLoop` in Zig (uws_sys/Loop.zig) // but the Rust `bun_uws_sys::Loop` only exposes `inc`/`dec`/`ref_`/`unref`. The // `active` counter is a public field, so inline the saturating math here until // `bun_uws_sys` grows `add_active`/`sub_active`. On Windows the uws loop has no @@ -81,7 +81,7 @@ unsafe extern "Rust" { /// Kind of fd a `FilePoll` (or pipe reader/writer) is wrapping. Lives here so /// `bun_io` (which now depends on this crate) and `FilePoll::file_type` share /// one definition; `bun_io::pipes` re-exports it for downstream callers. -// PORT NOTE: Zig defines this in src/io/pipes.zig; sunk one tier to break the +// Note: Zig defines this in src/io/pipes.zig; sunk one tier to break the // io↔aio cycle (FilePoll::file_type was the only aio→io edge). #[derive(Copy, Clone, Eq, PartialEq)] pub enum FileType { @@ -136,7 +136,7 @@ pub fn js_vm_ctx() -> EventLoopCtx { #[cfg(all(target_os = "macos", debug_assertions))] type KQueueGenerationNumber = usize; #[cfg(all(unix, not(all(target_os = "macos", debug_assertions))))] -type KQueueGenerationNumber = u8; // PORT NOTE: Zig uses `u0`; smallest Rust int is u8. Gated by cfg below. +type KQueueGenerationNumber = u8; // Note: Zig uses `u0`; smallest Rust int is u8. Gated by cfg below. // PORTING.md §Global mutable state: counter → Atomic. Debug-only diagnostic; // `Relaxed` matches Zig's `+%=` (no synchronization implied). @@ -179,7 +179,7 @@ fn make_kevent( #[cfg(any(target_os = "macos", target_os = "freebsd"))] const EV_EOF: u16 = 0x8000; -// PORT NOTE: Zig's `kqueue_or_epoll` comptime literal was only spliced into a +// Note: Zig's `kqueue_or_epoll` comptime literal was only spliced into a // `panicLog` that the Rust port routes through `bun_output::panic!` (which // already names the syscall via `Tag`). No remaining call site → dropped. @@ -338,7 +338,7 @@ impl FilePoll { FileType::Pipe } - // PORT NOTE: Zig `onKQueueEvent`/`onEpollEvent` take `_: *Loop` (unused, raw-pointer + // Note: Zig `onKQueueEvent`/`onEpollEvent` take `_: *Loop` (unused, raw-pointer // semantics). The Rust signatures drop the loop parameter entirely: holding a // protected `&mut Loop` across `on_update` would alias the fresh `&mut Loop` // that downstream `__bun_run_file_poll` handlers conjure via @@ -389,7 +389,7 @@ impl FilePoll { readable } - // PORT NOTE: not `impl Drop` — FilePoll is pool-allocated (HiveArray) and explicitly + // Note: not `impl Drop` — FilePoll is pool-allocated (HiveArray) and explicitly // put back via `Store::put`; Drop would be wrong here. pub fn deinit(&mut self) { let ctx = get_vm_ctx(self.allocator_type); @@ -534,7 +534,7 @@ impl FilePoll { /// Build a fully-initialized `FilePoll` value for `Store::get_init`. /// - /// PORT NOTE: the previous `&mut *pool.get()` + field-assign pattern was + /// Note: the previous `&mut *pool.get()` + field-assign pattern was /// instant validity UB — `FilePoll.owner`/`allocator_type` are enums with /// niches, and `&mut FilePoll` over an uninitialized hive slot asserts a /// valid discriminant. It also left `generation_number` uninitialized on @@ -559,7 +559,7 @@ impl FilePoll { } } - // PORT NOTE: Zig branches on @TypeOf(vm) for *PackageManager, EventLoopHandle, else. + // Note: Zig branches on @TypeOf(vm) for *PackageManager, EventLoopHandle, else. // Callers normalize to EventLoopCtx before calling. pub fn init(vm: EventLoopCtx, fd: Fd, flags: FlagsSet, owner: Owner) -> *mut FilePoll { let value = Self::new_value(vm, fd, flags, owner); @@ -707,7 +707,7 @@ impl FilePoll { flags |= EPOLL::IN; } - // PORT NOTE: Zig uses `linux.epoll_event{ .data = .{ .ptr = ... } }`; + // Note: Zig uses `linux.epoll_event{ .data = .{ .ptr = ... } }`; // libc::epoll_event flattens the union to a single `u64` field. let mut event = linux::epoll_event { events: flags, @@ -925,7 +925,7 @@ impl FilePoll { fd: Fd, force_unregister: bool, ) -> sys::Result<()> { - // PORT NOTE: reshaped for borrowck (Zig `defer this.deactivate(loop)`) — compute the + // Note: reshaped for borrowck (Zig `defer this.deactivate(loop)`) — compute the // syscall result first, then unconditionally deactivate. Avoids the raw-pointer scopeguard // the literal translation would require. #[cfg(any( @@ -1332,7 +1332,11 @@ impl fmt::Display for FlagsFormatter { // Store // ────────────────────────────────────────────────────────────────────────── -// TODO(port): Zig uses `if (bun.heap_breakdown.enabled) 0 else 128` for the hive size. +// Zig used `if (bun.heap_breakdown.enabled) 0 else 128` so the macOS +// Instruments heap-breakdown build routes every FilePoll through its tagged +// zone; the Rust `bun_alloc::heap_breakdown` is a no-op outside those builds, +// so the 128-slot hive is unconditional here (same choice as +// `RuntimeTranspilerStore`'s TranspilerJob hive). #[cfg(not(windows))] const HIVE_SIZE: usize = 128; #[cfg(not(windows))] @@ -1445,7 +1449,7 @@ impl Store { // `Pollable` mirrors Zig `bun.TaggedPointerUnion(.{FilePoll})`. // -// PORT NOTE: `bun_collections::TaggedPtrUnion<(FilePoll,)>` cannot be +// Note: `bun_collections::TaggedPtrUnion<(FilePoll,)>` cannot be // instantiated here — `impl_tagged_ptr_union!` would generate // `impl TypeList for (FilePoll,)`, which trips the orphan rule (foreign trait // on a tuple). Since the union has exactly one variant, wrap the raw diff --git a/src/io/source.rs b/src/io/source.rs index 38a15e489b56..c08ac08c6617 100644 --- a/src/io/source.rs +++ b/src/io/source.rs @@ -86,7 +86,7 @@ pub enum FileState { impl Default for File { fn default() -> Self { - // PORT NOTE: std.mem.zeroes(File) — hand-written because `state` is an enum field + // Hand-written equivalent of zero-initialization because `state` is an enum field // (PORTING.md forbids blanket zeroed() over enums). FileState::Deinitialized == 0. Self { fs: bun_core::ffi::zeroed(), diff --git a/src/io/windows_event_loop.rs b/src/io/windows_event_loop.rs index 1d3dfe873dbc..ee97b0b55e9d 100644 --- a/src/io/windows_event_loop.rs +++ b/src/io/windows_event_loop.rs @@ -98,7 +98,7 @@ impl FilePoll { .as_ptr() } - // PORT NOTE: not `impl Drop` — FilePoll lives in a HiveArray pool slot, not a Box; + // Note: not `impl Drop` — FilePoll lives in a HiveArray pool slot, not a Box; // teardown returns the slot to the pool via `Store::put`. pub fn deinit(&mut self) { self.deinit_with_vm(js_vm_ctx()); @@ -201,7 +201,7 @@ impl FilePoll { } /// Only intended to be used from EventLoop.Pollable - // PORT NOTE: Zig takes `*Loop = *uv.Loop` here (`vm.event_loop_handle.?`), + // Note: Zig takes `*Loop = *uv.Loop` here (`vm.event_loop_handle.?`), // but the cycle-broken `EventLoopCtx::platform_event_loop` vtable is typed // `*mut bun_uws_sys::Loop` (the uws `WindowsLoop` wrapper) so the // impl-crate bodies (`VirtualMachine::uws_loop` / `MiniEventLoop::loop_ptr`) @@ -381,8 +381,9 @@ unsafe impl Send for Waker {} unsafe impl Sync for Waker {} impl Waker { + // `Result` kept (despite being infallible here) for signature parity with + // the POSIX wakers, whose `init` can fail (eventfd / kqueue). pub fn init() -> Result { - // TODO(port): narrow error set Ok(Waker { loop_: bun_ptr::BackRef::from( ptr::NonNull::new(WindowsLoop::get()).expect("WindowsLoop::get() singleton"), @@ -402,15 +403,10 @@ impl Waker { self.loop_.uv_loop } - // TODO(port): Zig used @compileError here; on Windows these must never be linked. - pub fn get_fd(&self) -> Fd { - unreachable!("Waker.getFd is unsupported on Windows"); - } - - // TODO(port): Zig used @compileError here; on Windows these must never be linked. - pub fn init_with_file_descriptor(_fd: Fd) -> Waker { - unreachable!("Waker.initWithFileDescriptor is unsupported on Windows"); - } + // Zig declared `getFd`/`initWithFileDescriptor` as `@compileError` here: + // on Windows they must never be referenced. The Rust equivalent is to not + // define them at all — POSIX-only call sites are `cfg`-gated, so a stray + // Windows use fails the build just like Zig's `@compileError`. pub fn wait(&self) { // Do NOT go through `WindowsLoop::wait(&mut self)`: that would diff --git a/src/js_parser/fold.rs b/src/js_parser/fold.rs index f9b208246ca7..782aed07fc0d 100644 --- a/src/js_parser/fold.rs +++ b/src/js_parser/fold.rs @@ -139,7 +139,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // module linking just to rewrite these EDot expressions. if p.options.bundle { if p.import_items_for_namespace.contains_key(&id.ref_) { - // PORT NOTE: reshaped for borrowck — Zig held `*ImportItemForNamespaceMap` + // Note: reshaped for borrowck — Zig held `*ImportItemForNamespaceMap` // across `p.newSymbol`; split into lookup → (maybe new_symbol) → re-borrow. let existing = p .import_items_for_namespace @@ -389,7 +389,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O return None; } - // PORT NOTE: reshaped for borrowck — Zig held the + // Note: reshaped for borrowck — Zig held the // `getOrPut` entry across `p.newSymbol`. let ref_ = if let Some(existing) = p.commonjs_named_exports.get(name) @@ -619,7 +619,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O return None; } - // PORT NOTE: reshaped for borrowck — see exports_ref arm above. + // Note: reshaped for borrowck — see exports_ref arm above. let ref_ = if let Some(existing) = p.commonjs_named_exports.get(name) { @@ -818,7 +818,6 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O pub fn check_if_defined_helper(&mut self, expr: Expr) -> Result { let p = self; - // TODO(port): narrow error set let flags = if matches!(expr.data, js_ast::ExprData::EIdentifier(_)) { E::UnaryFlags::WAS_ORIGINALLY_TYPEOF_IDENTIFIER } else { @@ -845,7 +844,6 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O pub fn maybe_defined_helper(&mut self, identifier_expr: Expr) -> Result { let p = self; - // TODO(port): narrow error set let test_ = Self::check_if_defined_helper(p, identifier_expr)?; let object_ref = p .find_symbol(bun_ast::Loc::EMPTY, b"Object") diff --git a/src/js_parser/lexer.rs b/src/js_parser/lexer.rs index 1ba724ae8584..b82328463f62 100644 --- a/src/js_parser/lexer.rs +++ b/src/js_parser/lexer.rs @@ -14,9 +14,6 @@ use bun_core::{Environment, feature_flags as FeatureFlags}; use identifier as js_identifier; // MOVE-IN: Indentation now lives in this crate (was bun_js_printer::Options::Indentation). use bun_ast::{Indentation, IndentationCharacter}; -// TODO(port): arena threading — js_parser is an AST crate; many `arena.*` calls below -// should use `&'bump bumpalo::Bump`. For now we keep a `&dyn Allocator`-ish slot and -// route owned buffers through `Vec`/`Box`. use bun_alloc::Arena; // Unicode ID-Start/ID-Continue tables moved DOWN to `bun_core` (pure data; @@ -179,7 +176,6 @@ pub type NewLexer<'a, J: JsonOptionsT = DefaultJsonOptions> = LexerType< { ::GUESS_INDENTATION }, >; -// TODO(port): `thiserror` not in this crate's deps; hand-roll Display/Error. #[derive(Debug, Clone, Copy, PartialEq, Eq, strum::IntoStaticStr)] pub enum Error { UTF8Fail, @@ -188,8 +184,8 @@ pub enum Error { UnexpectedSyntax, JSONStringsMustUseDoubleQuotes, ParserError, - // TODO(port): Zig `error.Backtrack` is returned from `expected()` but not declared in - // the local error set; modeled here as an extra variant. + // Zig `error.Backtrack` is returned from `expected()` but not declared in + // the local error set; modeled here as an extra variant. Intentional. Backtrack, } bun_core::impl_tag_error!(Error); @@ -241,7 +237,7 @@ pub struct ScanResult<'a> { pub contents: &'a [u8], } -// PORT NOTE: Zig's `FakeArrayList16` (fixed-slice writer with the `append` surface of an +// Note: Zig's `FakeArrayList16` (fixed-slice writer with the `append` surface of an // ArrayList) is dead — every `decodeEscapeSequences` callsite in lexer.zig passes // `std.array_list.Managed(u16)`. Dropped instead of porting; `decode_escape_sequences` is // monomorphized to `Vec`. @@ -373,21 +369,19 @@ pub struct LexerType< pub string_literal_raw_format: StringLiteralRawFormat, pub temp_buffer_u16: Vec, - /// Only used for JSON stringification when bundling - /// This is a zero-bit type unless we're parsing JSON. - // TODO(port): Zig uses `if (is_json) bool else void` for zero-cost when !is_json. - // PERF(port): always-bool here wastes 1 byte in non-JSON instantiations — profile if hot. + /// Only used for JSON stringification when bundling. + /// (Zig made this `void` unless parsing JSON; always a bool here.) pub is_ascii_only: bool, pub track_comments: bool, pub all_comments: Vec, - // TODO(port): Zig field type is `if (guess_indentation) struct{..} else void`. - // PERF(port): always-present here — profile if hot. + /// Only meaningful when `GUESS_INDENTATION` is set. + /// (Zig made this `void` unless guessing indentation; always present here.) pub indent_info: IndentInfo, } // Convenience: associated constants mirroring Zig's `const json = json_options;` etc. -// PORT NOTE: Rust macros must emit complete items; the macro now wraps the +// Note: Rust macros must emit complete items; the macro now wraps the // entire `impl { ... }` block instead of just the header. macro_rules! lexer_impl_header { ($($body:tt)*) => { @@ -507,7 +501,8 @@ lexer_impl_header! { return Ok(()); } - // TODO(port): Zig dupes `notes` with `self.log.msgs.arena`. + // Zig dupes `notes` with `self.log.msgs.arena`; the Log API takes an + // owned `Box<[Data]>` here (error path only, allocation cost is moot). let notes_owned: Box<[bun_ast::Data]> = notes.to_vec().into_boxed_slice(); self.log() .add_range_error_fmt_with_notes(Some(self.source), r, notes_owned, args); @@ -563,7 +558,7 @@ lexer_impl_header! { /// truncate the Vecs to their snapshotted lengths. `log`/`source`/`arena` /// are left untouched. pub fn restore(&mut self, original: &LexerSnapshot<'a>) { - // TODO(port): keep this list in sync with the struct fields. + // Keep this field list in sync with `snapshot()` and the Lexer struct fields. self.current = original.current; self.start = original.start; self.end = original.end; @@ -627,7 +622,7 @@ lexer_impl_header! { text: &[u8], buf: &mut Vec, ) -> Result<(), Error> { - // PORT NOTE: monomorphized — Zig is generic over `comptime BufType: type` but every + // Note: monomorphized — Zig is generic over `comptime BufType: type` but every // caller passes `std.array_list.Managed(u16)`; `FakeArrayList16` was dead in the source. if IS_JSON { self.is_ascii_only = false; @@ -1170,7 +1165,7 @@ lexer_impl_header! { &self.contents[self.current..] } - /// PORT NOTE: split into an `#[inline(always)]` ASCII/EOF fast path plus + /// Note: split into an `#[inline(always)]` ASCII/EOF fast path plus /// an outlined multibyte tail. `step()` is called from ~50 sites inside /// the giant `next()` switch and inlines into it; with the multibyte /// decode in the same body LLVM declined to inline `next_codepoint` @@ -1269,8 +1264,7 @@ lexer_impl_header! { pub fn scan_identifier_with_escapes( &mut self, kind: IdentifierKind, - ) -> Result, bun_core::Error> { - // TODO(port): narrow error set + ) -> Result, Error> { let mut result = ScanResult { token: T::TEndOfFile, contents: b"".as_slice(), @@ -1334,7 +1328,7 @@ lexer_impl_header! { let original_text = self.raw(); debug_assert!(self.temp_buffer_u16.is_empty()); - // PORT NOTE: reshaped for borrowck — we move temp_buffer_u16 out, use it, then + // Note: reshaped for borrowck — we move temp_buffer_u16 out, use it, then // clear and put it back (mirrors `defer clearRetainingCapacity()`). let mut tmp = core::mem::take(&mut self.temp_buffer_u16); tmp.reserve(original_text.len()); @@ -1343,9 +1337,9 @@ lexer_impl_header! { if let Err(e) = decode_res { tmp.clear(); self.temp_buffer_u16 = tmp; - return Err(e.into()); + return Err(e); } - result.contents = self.utf16_to_string(&tmp)?; + result.contents = self.utf16_to_string(&tmp); tmp.clear(); self.temp_buffer_u16 = tmp; @@ -1587,8 +1581,7 @@ lexer_impl_header! { self.step_with(contents); if self.code_point == 0x5C { self.identifier = self - .scan_identifier_with_escapes(IdentifierKind::Private) - .map_err(|_| Error::SyntaxError)? // TODO(port): error coercion + .scan_identifier_with_escapes(IdentifierKind::Private)? .contents; } else { if !is_identifier_start(self.code_point) { @@ -1601,8 +1594,7 @@ lexer_impl_header! { } if self.code_point == 0x5C { self.identifier = self - .scan_identifier_with_escapes(IdentifierKind::Private) - .map_err(|_| Error::SyntaxError)? + .scan_identifier_with_escapes(IdentifierKind::Private)? .contents; } else { self.identifier = self.raw(); @@ -2178,8 +2170,7 @@ lexer_impl_header! { } else { // @branchHint(.unlikely) let scan_result = self - .scan_identifier_with_escapes(IdentifierKind::Normal) - .map_err(|_| Error::SyntaxError)?; // TODO(port): error coercion + .scan_identifier_with_escapes(IdentifierKind::Normal)?; self.identifier = scan_result.contents; self.token = scan_result.token; } @@ -2196,8 +2187,7 @@ lexer_impl_header! { } let scan_result = self - .scan_identifier_with_escapes(IdentifierKind::Normal) - .map_err(|_| Error::SyntaxError)?; + .scan_identifier_with_escapes(IdentifierKind::Normal)?; self.identifier = scan_result.contents; self.token = scan_result.token; } @@ -2220,8 +2210,7 @@ lexer_impl_header! { } if self.code_point == 0x5C { let scan_result = self - .scan_identifier_with_escapes(IdentifierKind::Normal) - .map_err(|_| Error::SyntaxError)?; + .scan_identifier_with_escapes(IdentifierKind::Normal)?; self.identifier = scan_result.contents; self.token = scan_result.token; } else { @@ -2519,7 +2508,7 @@ lexer_impl_header! { if !IS_JSON { let pragma_trigger_pos = self.end; // Position OF #/@ // Use remaining() which starts *after* the consumed #/@ - // PORT NOTE: reshaped for borrowck — `remaining()` borrows + // Note: reshaped for borrowck — `remaining()` borrows // `self.contents`; `scan_pragma` needs `&mut self`. // Detach via `StoreStr` (arena-owned, lives for parse). let chunk = js_ast::StoreStr::new(self.remaining()); @@ -2664,8 +2653,8 @@ lexer_impl_header! { pub fn range(&self) -> Range { Range { loc: bun_ast::usize2loc(self.start), - // TODO(port): std.math.lossyCast — saturate on overflow - len: (self.end - self.start) as i32, + // Zig `std.math.lossyCast` — saturate on overflow. + len: i32::try_from(self.end - self.start).unwrap_or(i32::MAX), } } @@ -2808,8 +2797,8 @@ lexer_impl_header! { #[inline] fn assert_not_json(&self) { if IS_JSON { - // TODO(port): Zig uses @compileError; Rust const generics can't compile-error - // here without nightly. Could gate JSX methods to non-JSON instantiations. + // Zig used @compileError; stable Rust can't fail the build on a + // const-generic branch, so this is a runtime check on a dead path. unreachable!("JSON should not reach this point"); } } @@ -2886,14 +2875,11 @@ lexer_impl_header! { } } - pub fn utf16_to_string( - &self, - js: JavascriptString<'_>, - ) -> Result<&'a [u8], bun_core::Error> { - // TODO(port): arena routing — Zig: strings.toUTF8AllocWithType(lexer.arena, js) - // PERF(port): goes through global Vec then dupes into the arena. + pub fn utf16_to_string(&self, js: JavascriptString<'_>) -> &'a [u8] { + // Zig wrote straight into the arena (`strings.toUTF8AllocWithType(lexer.arena, js)`); + // here we transcode into a temporary Vec and dupe into the arena. let owned = strings::to_utf8_alloc_with_type(js); - Ok(self.arena.alloc_slice_copy(&owned)) + self.arena.alloc_slice_copy(&owned) } pub fn next_inside_jsx_element(&mut self) -> Result<(), Error> { @@ -3352,7 +3338,7 @@ lexer_impl_header! { base = 16; } - // PORT NOTE: std.fmt.parseInt(i32, ..) — bytes-based parser; source bytes are + // Note: std.fmt.parseInt(i32, ..) — bytes-based parser; source bytes are // not guaranteed UTF-8 so we never round-trip through &str (PORTING.md §Strings). // Also reject values outside the Unicode range (0..=0x10FFFF); otherwise // `push_codepoint_utf16` hits `debug_assert`s in `u16_lead`/`u16_trail` @@ -3518,7 +3504,7 @@ lexer_impl_header! { // them. and LineTerminatorSequences are normalized to // for both TV and TRV. An explicit EscapeSequence is needed to // include a or sequence. - // TODO(port): MutableString — using arena-backed Vec here. + // Zig used MutableString; an owned Vec scratch buffer here. let mut bytes: Vec = text.to_vec(); let mut end: usize = 0; let mut i: usize = 0; @@ -3713,7 +3699,8 @@ lexer_impl_header! { if is_big_integer_literal { self.identifier = text; } else if is_invalid_legacy_octal_literal { - // TODO(port): std.fmt.parseFloat — bytes-based; using bun_core::wtf::parse_double + // Zig used `std.fmt.parseFloat`; `bun_core::wtf::parse_double` is the + // bytes-based equivalent (legacy-octal edge path only). match bun_core::wtf::parse_double(text) { Ok(num) => { self.number = num; diff --git a/src/js_parser/lib.rs b/src/js_parser/lib.rs index 2101f95078d7..031098a241b2 100644 --- a/src/js_parser/lib.rs +++ b/src/js_parser/lib.rs @@ -4,8 +4,8 @@ //! `[]T` struct field in the Zig points into either the source text or the //! parser arena and is bulk-freed at end-of-parse. Per PORTING.md, lifetime //! params are not added to AST structs; arena-owned slices are typed as -//! `StoreSlice` / `StoreStr` here. TODO(refactor): thread a crate-wide -//! `'bump` and rewrite these to `&'bump [T]` / `&'bump mut [T]`. +//! `StoreSlice` / `StoreStr` here. A future refactor could thread a +//! crate-wide `'bump` and rewrite these to `&'bump [T]` / `&'bump mut [T]`. // `lexer::NewLexer` projects trait associated consts into // eight `const bool` slots (Zig: `NewLexer(comptime json_options)`). Field @@ -560,7 +560,7 @@ pub mod defines { parts.push(Box::from(tail)); let mut initial_values: &[DotDefine] = &[]; - // PORT NOTE: reshaped for borrowck — getOrPut split into get/insert. + // Note: reshaped for borrowck — getOrPut split into get/insert. if let Some(existing) = self.dots.get_mut(tail) { for part in existing.iter_mut() { if are_parts_equal(&part.parts, &parts) { @@ -599,371 +599,6 @@ pub mod defines { } pub use defines::{Define, DefineData}; -pub mod defines_full_draft { - use bstr::BStr; - use bun_collections::{ArrayHashMap, StringHashMap}; - use bun_core::strings; - - use bun_ast::base::Ref; - use bun_ast::e as E; - use bun_ast::expr; - - use crate::lexer as js_lexer; - use bun_ast::StoreRef; - - // Zig: `bun.StringArrayHashMap(string)` / `bun.StringHashMap(DefineData)` - pub type RawDefines = ArrayHashMap, Box<[u8]>>; - pub type UserDefines = StringHashMap; - pub type UserDefinesArray = ArrayHashMap, DefineData>; - - pub type IdentifierDefine = DefineData; - - #[derive(Clone)] - pub struct DotDefine { - // Zig stored borrowed `[][]const u8` into the user-define key strings; - // the Rust port owns the part bytes (small, allocated once at startup) - // so the `RawDefines` map can be dropped after `Define::init`. - pub parts: Vec>, - pub data: DefineData, - } - - bitflags::bitflags! { - // Zig: `packed struct(u8) { _padding: u3, valueless, can_be_removed_if_unused, - // call_can_be_unwrapped_if_unused: E.CallUnwrap (u2), method_call_must_be_replaced_with_undefined }` - // Packed LSB-first → bit positions below match the Zig layout exactly. - #[derive(Copy, Clone, Default)] - pub struct DefineDataFlags: u8 { - const VALUELESS = 1 << 3; - const CAN_BE_REMOVED_IF_UNUSED = 1 << 4; - // bits 5..7 hold `E::CallUnwrap` (2 bits) — read via accessor below. - const METHOD_CALL_MUST_BE_REPLACED_WITH_UNDEFINED = 1 << 7; - } - } - const CALL_UNWRAP_SHIFT: u8 = 5; - const CALL_UNWRAP_MASK: u8 = 0b11 << CALL_UNWRAP_SHIFT; - - #[derive(Clone)] - pub struct DefineData { - pub value: expr::Data, - // Zig stored `original_name_ptr: ?[*]const u8` + `original_name_len: u32` - // borrowing into caller-owned strings (defines.zig:24-25 — the 48→40-byte - // packing trick). The Rust port owns the `RawDefines` value bytes - // (`Box<[u8]>`), so borrowing would be a use-after-free once the - // `RawDefines` map is dropped after `Define::init`. Own the bytes here - // instead — these are tiny startup-time copies. - pub original_name: Option>, - pub flags: DefineDataFlags, - } - - impl Default for DefineData { - fn default() -> Self { - Self { - value: expr::Data::EUndefined(E::Undefined {}), - original_name: None, - flags: DefineDataFlags::empty(), - } - } - } - - impl DefineData { - #[inline] - pub fn original_name(&self) -> Option<&[u8]> { - match &self.original_name { - Some(name) if !name.is_empty() => Some(name.as_ref()), - _ => None, - } - } - - /// True if accessing this value is known to not have any side effects. For - /// example, a bare reference to "Object.create" can be removed because it - /// does not have any observable side effects. - #[inline] - pub fn can_be_removed_if_unused(&self) -> bool { - self.flags - .contains(DefineDataFlags::CAN_BE_REMOVED_IF_UNUSED) - } - - /// True if a call to this value is known to not have any side effects. For - /// example, a bare call to "Object()" can be removed because it does not - /// have any observable side effects. - #[inline] - pub fn call_can_be_unwrapped_if_unused(&self) -> E::CallUnwrap { - // 2-bit field; explicit match keeps bit-pattern 3 sound. - match (self.flags.bits() & CALL_UNWRAP_MASK) >> CALL_UNWRAP_SHIFT { - 0 => E::CallUnwrap::Never, - 1 => E::CallUnwrap::IfUnused, - 2 => E::CallUnwrap::IfUnusedAndToStringSafe, - _ => E::CallUnwrap::Never, - } - } - - #[inline] - pub fn method_call_must_be_replaced_with_undefined(&self) -> bool { - self.flags - .contains(DefineDataFlags::METHOD_CALL_MUST_BE_REPLACED_WITH_UNDEFINED) - } - - #[inline] - pub fn valueless(&self) -> bool { - self.flags.contains(DefineDataFlags::VALUELESS) - } - - pub fn init_boolean(value: bool) -> DefineData { - DefineData { - value: expr::Data::EBoolean(E::Boolean { value }), - flags: DefineDataFlags::CAN_BE_REMOVED_IF_UNUSED, - ..Default::default() - } - } - - pub fn init_static_string(str_: &'static E::String) -> DefineData { - DefineData { - // Zig `@constCast` — Expr.Data stores StoreRef (NonNull); the static is never mutated. - value: expr::Data::EString(StoreRef::from_static(str_)), - flags: DefineDataFlags::CAN_BE_REMOVED_IF_UNUSED, - ..Default::default() - } - } - - pub fn merge(a: &DefineData, b: &DefineData) -> DefineData { - let mut flags = DefineDataFlags::empty(); - if a.can_be_removed_if_unused() { - flags |= DefineDataFlags::CAN_BE_REMOVED_IF_UNUSED; - } - flags = DefineDataFlags::from_bits_retain( - flags.bits() | ((a.call_can_be_unwrapped_if_unused() as u8) << CALL_UNWRAP_SHIFT), - ); - // TODO: investigate if this is correct. This is what it was before. But that looks strange. - if a.method_call_must_be_replaced_with_undefined() - || b.method_call_must_be_replaced_with_undefined() - { - flags |= DefineDataFlags::VALUELESS; - flags |= DefineDataFlags::METHOD_CALL_MUST_BE_REPLACED_WITH_UNDEFINED; - } - DefineData { - value: b.value, - flags, - original_name: b.original_name.clone(), - } - } - - // REFACTOR_BUN_AST: `bun_js_parser` is a sibling of `bun_parsers`, so the - // JSON-value branch takes a parser callback (the bundler passes - // `bun_parsers::json::parse_env_json`). With the unified `Expr` type, - // the result is the same `bun_ast::Expr` the rest of the parser uses — - // the former `json_data_to_expr_data` lift is gone. - pub fn parse( - key: &[u8], - value_str: &[u8], - value_is_undefined: bool, - method_call_must_be_replaced_with_undefined: bool, - log: &mut bun_ast::Log, - bump: &bun_alloc::Arena, - parse_json: &dyn Fn( - &bun_ast::Source, - &mut bun_ast::Log, - &bun_alloc::Arena, - ) - -> core::result::Result, - ) -> core::result::Result { - for part in key.split(|&c| c == b'.') { - if !js_lexer::is_identifier(part) { - if strings::eql(part, key) { - log.add_error_fmt( - None, - bun_ast::Loc::default(), - format_args!( - "define key \"{}\" must be a valid identifier", - BStr::new(key) - ), - ); - } else { - log.add_error_fmt( - None, - bun_ast::Loc::default(), - format_args!( - "define key \"{}\" contains invalid identifier \"{}\"", - BStr::new(part), - BStr::new(value_str) - ), - ); - } - break; - } - } - - // check for nested identifiers - let mut is_ident = true; - for part in value_str.split(|&c| c == b'.') { - if !js_lexer::is_identifier(part) || js_lexer::keyword(part).is_some() { - is_ident = false; - break; - } - } - - let mut flags = DefineDataFlags::empty(); - if value_is_undefined { - flags |= DefineDataFlags::VALUELESS; - } - if method_call_must_be_replaced_with_undefined { - flags |= DefineDataFlags::METHOD_CALL_MUST_BE_REPLACED_WITH_UNDEFINED; - } - - if is_ident { - // Special-case undefined. it's not an identifier here - // https://github.com/evanw/esbuild/issues/1407 - let value = if value_is_undefined || value_str == b"undefined" { - expr::Data::EUndefined(E::Undefined {}) - } else { - expr::Data::EIdentifier( - E::Identifier::init(Ref::NONE).with_can_be_removed_if_unused(true), - ) - }; - flags |= DefineDataFlags::CAN_BE_REMOVED_IF_UNUSED; - return Ok(DefineData { - value, - original_name: if value_str.is_empty() { - None - } else { - Some(Box::<[u8]>::from(value_str)) - }, - flags, - }); - } - - // Value is JSON — round-trip through the env-JSON parser. - let source = bun_ast::Source { - contents: std::borrow::Cow::Owned(value_str.to_vec()), - path: bun_paths::fs::Path::init_with_namespace(b"defines.json", b"internal"), - ..Default::default() - }; - let expr = parse_json(&source, log, bump)?; - // Zig: `expr.data.deepClone(arena)` followed by `expr.isPrimitiveLiteral()`. - // With one `Expr` type the parser result is already in the target - // shape; `deep_clone` re-roots payloads in `bump`. - let cloned = expr.data.deep_clone(bump)?; - if expr.is_primitive_literal() { - flags |= DefineDataFlags::CAN_BE_REMOVED_IF_UNUSED; - } - Ok(DefineData { - value: cloned, - original_name: if value_str.is_empty() { - None - } else { - Some(Box::<[u8]>::from(value_str)) - }, - flags, - }) - } - } - - pub struct Define { - pub identifiers: StringHashMap, - pub dots: StringHashMap>, - pub drop_debugger: bool, - } - - impl Define { - // Zig: `pub const Data = DefineData;` — Rust callers import `DefineData` directly. - - pub fn for_identifier(&self, name: &[u8]) -> Option<&IdentifierDefine> { - if let Some(data) = self.identifiers.get(name) { - return Some(data); - } - // Draft module — pure-global table is wired into the canonical - // `crate::defines::Define` (this draft type is unused). - None - } - - pub fn insert( - &mut self, - bump: &bun_alloc::Arena, - key: &[u8], - value: DefineData, - ) -> core::result::Result<(), bun_alloc::AllocError> { - let _ = bump; - // If it has a dot, then it's a DotDefine. - // e.g. process.env.NODE_ENV - if let Some(last_dot) = strings::last_index_of_char(key, b'.') { - let tail = &key[last_dot + 1..]; - let remainder = &key[..last_dot]; - let count = remainder.iter().filter(|&&c| c == b'.').count() + 1; - // Zig allocated `[][]const u8` borrowing the input key; the Rust - // port owns the part bytes (tiny startup-time copies) so the - // caller can drop `key` after `Define::init`. - let mut parts: Vec> = Vec::with_capacity(count + 1); - for split in remainder.split(|&c| c == b'.') { - parts.push(Box::from(split)); - } - parts.push(Box::from(tail)); - - // "NODE_ENV" - let entry = self.dots.get_or_put(tail).unwrap().value_ptr; - for part in entry.iter_mut() { - // ["process", "env"] === ["process", "env"] - if are_parts_equal(&part.parts, &parts) { - part.data = DefineData::merge(&part.data, &value); - return Ok(()); - } - } - entry.push(DotDefine { data: value, parts }); - } else { - // e.g. IS_BROWSER - self.identifiers.put_assume_capacity(key, value); - } - Ok(()) - } - - pub fn init( - user_defines: Option, - string_defines: Option, - drop_debugger: bool, - omit_unused_global_calls: bool, - bump: &bun_alloc::Arena, - ) -> core::result::Result, bun_alloc::AllocError> { - let _ = omit_unused_global_calls; - let mut define = Box::new(Define { - identifiers: StringHashMap::default(), - dots: StringHashMap::default(), - drop_debugger, - }); - // TODO(port): Step 1/2 — load global_no_side_effect_* tables from - // bun_bundler::defines_table once that table moves down. Omitting - // here is safe-ish: only affects pure-annotation tree shaking. - - // Step 3. Load user data into hash tables - // (Zig: `iter.next()` over `StringHashMap` — consume the inner map.) - if let Some(mut user_defines) = user_defines { - for (k, v) in core::mem::take(&mut *user_defines).into_iter() { - define.insert(bump, &k, v)?; - } - } - // Step 4. Load environment data into hash tables. - // (Zig: `it.next()` over `StringArrayHashMap` — `ArrayHashMap` has - // no `IntoIterator`; walk insertion-order entries.) - if let Some(mut string_defines) = string_defines { - let mut it = string_defines.iterator(); - while let Some(entry) = it.next() { - define.insert(bump, &**entry.key_ptr, entry.value_ptr.clone())?; - } - } - Ok(define) - } - } - - fn are_parts_equal(a: &[Box<[u8]>], b: &[Box<[u8]>]) -> bool { - if a.len() != b.len() { - return false; - } - for i in 0..a.len() { - if !strings::eql(&a[i], &b[i]) { - return false; - } - } - true - } -} - // ─── from bun_js_printer::renamer (src/js_printer/renamer.zig) ────────────── // Only the slot-assignment helpers the parser calls (`P.rs:6658`) live here; // the full `NumberRenamer`/`MinifyRenamer` machinery stays in `bun_js_printer` diff --git a/src/js_parser/lower/lower_decorators.rs b/src/js_parser/lower/lower_decorators.rs index 5cc0ddfbfe53..0984263e0f7b 100644 --- a/src/js_parser/lower/lower_decorators.rs +++ b/src/js_parser/lower/lower_decorators.rs @@ -392,7 +392,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let mut v = BumpVec::::new_in(self.arena); v.extend_from_slice(prefix); if let Some(n) = n { - // PORT NOTE: bumpalo Vec doesn't impl io::Write; format into a + // bumpalo Vec doesn't impl io::Write; format into a // bump String and copy the bytes. let s = bun_alloc::arena_format!(in self.arena, "{}", n); v.extend_from_slice(s.as_bytes()); @@ -2257,7 +2257,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O break; } let insert_at = if let Some(j) = super_index { j + 1 } else { 0 }; - // PORT NOTE: BumpVec has no `splice`; rebuild. + // BumpVec has no `splice`; rebuild. let mut spliced = BumpVec::::with_capacity_in( body_stmts.len() + constructor_inject_stmts.len(), bump, @@ -2362,7 +2362,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O comma_parts.push(ba); } - // PORT NOTE: Zig used a local anonymous-struct fn; can't capture + // Zig used a local anonymous-struct fn; can't capture // `&mut self` in a Rust closure while also calling `p.method()`, so // inline both call sites against a `&[Stmt]` slice array. for stmts_list in [&pre_eval_stmts[..], &prefix_stmts[..]] { diff --git a/src/js_parser/lower/lower_esm_exports_hmr.rs b/src/js_parser/lower/lower_esm_exports_hmr.rs index d2aaac43c619..b9f0d75a128d 100644 --- a/src/js_parser/lower/lower_esm_exports_hmr.rs +++ b/src/js_parser/lower/lower_esm_exports_hmr.rs @@ -8,7 +8,7 @@ use crate::p::P; use crate::parser::{ReactRefresh, Ref, TempRef}; use bun_ast::{self as js_ast, B, Binding, E, Expr, G, S, Stmt}; -// PORT NOTE: `P::generate_temp_ref` is ``-gated in P.rs (round-6 +// Note: `P::generate_temp_ref` is ``-gated in P.rs (round-6 // re-gate); replicate it here so this file can un-gate independently. Body is // a 1:1 port of P.zig `generateTempRefWithScope` with `scope = current_scope`. // `P::will_use_renamer` is private — its body is inlined. @@ -48,7 +48,7 @@ pub(crate) struct ConvertESMExportsForHmr<'a> { pub export_props: Vec, pub stmts: Vec, } -// PORT NOTE: Zig used `std.ArrayListUnmanaged` with `p.arena` for the four +// Note: Zig used `std.ArrayListUnmanaged` with `p.arena` for the four // collections; in Rust the parser arena is a `bumpalo::Bump`, but the consumers // (`Vec::move_from_list` for `export_props`, arena copy for `stmts`) want // global-heap `Vec` anyway. Kept as `Vec` so callers can construct via @@ -66,9 +66,9 @@ pub(crate) struct DeduplicatedImportResult { } impl<'a> ConvertESMExportsForHmr<'a> { - // PORT NOTE: `

` unbounded generic → concrete `P<'p, TS, SCAN>`. - // TODO(port): Zig `p: anytype` also accepts AstBuilder; add `ParserLike` trait bound - // when bundle_v2 lands. + // Note: `

` unbounded generic → concrete `P<'p, TS, SCAN>`. Zig's + // `p: anytype` also accepted `AstBuilder`; the Rust `AstBuilder` instead + // open-codes the equivalent transform (see bundler/AstBuilder.rs). pub(crate) fn convert_stmt<'p, const TS: bool, const SCAN: bool>( &mut self, p: &mut P<'p, TS, SCAN>, @@ -83,7 +83,7 @@ impl<'a> ConvertESMExportsForHmr<'a> { st.is_export = false; let mut new_len: usize = 0; - // PORT NOTE: reshaped for borrowck — index loop instead of `|*decl_ptr|`. + // Note: reshaped for borrowck — index loop instead of `|*decl_ptr|`. let decls_len = st.decls.len_u32() as usize; for i in 0..decls_len { // explicit field copies (G::Decl is not `Copy`) to avoid aliasing @@ -215,7 +215,7 @@ impl<'a> ConvertESMExportsForHmr<'a> { }, }; if can_be_moved_to_inner_scope { - // PORT NOTE: `StmtOrExpr` is not `Copy`; read by ptr to avoid moving + // Note: `StmtOrExpr` is not `Copy`; read by ptr to avoid moving // out of the StoreRef deref. // SAFETY: StoreRef points into a live arena; value is POD-shaped. let value = unsafe { core::ptr::read(&raw const st.value) }.to_expr(); @@ -491,7 +491,7 @@ impl<'a> ConvertESMExportsForHmr<'a> { if stmt.items.is_empty() { stmt.items = items; } else { - // PORT NOTE: Zig `std.mem.concat` — allocate concatenated slice in arena. + // Note: Zig `std.mem.concat` — allocate concatenated slice in arena. // ClauseItem fields are all bitwise-copyable; copy raw to avoid Clone bound. let prev_len = stmt.items.len(); let concat = p.arena.alloc_slice_fill_with(prev_len + items_len, |_| { @@ -527,7 +527,7 @@ impl<'a> ConvertESMExportsForHmr<'a> { let symbol = &mut p.symbols[namespace_ref.inner_index() as usize]; symbol.use_count_estimate = 0; symbol.link.set(stmt.namespace_ref); - // PORT NOTE: Zig `@hasField(@typeInfo(@TypeOf(p)).pointer.child, "symbol_uses")` + // Note: Zig `@hasField(@typeInfo(@TypeOf(p)).pointer.child, "symbol_uses")` // gated this on whether the concrete `p` type carries `symbol_uses`. The // concrete `P` always does; once a `ParserLike` trait is introduced for // AstBuilder, that variant should override this to a no-op. @@ -696,7 +696,7 @@ impl<'a> ConvertESMExportsForHmr<'a> { pub(crate) fn finalize<'p, const TS: bool, const SCAN: bool>( &mut self, p: &mut P<'p, TS, SCAN>, - // PORT NOTE: Zig took `all_parts: []Part` and freely re-derived + // Note: Zig took `all_parts: []Part` and freely re-derived // `&mut all_parts[len-1]` while `ctx.last_part` aliased the same slot. // Rust forbids that aliasing (Stacked Borrows: `&mut [Part]` asserts // exclusive access to every element). Caller passes the `[0..len-1]` @@ -791,7 +791,7 @@ impl<'a> ConvertESMExportsForHmr<'a> { self.last_part .import_record_indices .append_slice(part.import_record_indices.slice()); - // PORT NOTE: reshaped for borrowck — Zig zipped keys()/values(); index loop avoids + // Note: reshaped for borrowck — Zig zipped keys()/values(); index loop avoids // holding two shared borrows of `part.symbol_uses` while &mut-borrowing `last_part`. for i in 0..part.symbol_uses.count() { let k = part.symbol_uses.keys()[i]; @@ -804,7 +804,7 @@ impl<'a> ConvertESMExportsForHmr<'a> { } } part.stmts = bun_ast::StoreSlice::EMPTY; - // PORT NOTE: `declared_symbols` already cleared via `mem::take` above + // Note: `declared_symbols` already cleared via `mem::take` above // (Zig set `entries.len = 0` after `appendList`). part.tag = bun_ast::PartTag::DeadDueToInlining; part.dependencies.clear_retaining_capacity(); @@ -821,7 +821,7 @@ impl<'a> ConvertESMExportsForHmr<'a> { .declared_symbols .append_list(&p.declared_symbols)?; - // PORT NOTE: Zig assigned the ArrayList's `items` slice directly. `Stmt` is `Copy`; + // Note: Zig assigned the ArrayList's `items` slice directly. `Stmt` is `Copy`; // copy into the parser arena so the `StoreSlice` outlives this struct. let stmts = core::mem::take(&mut self.stmts); self.last_part.stmts = bun_ast::StoreSlice::new_mut(p.arena.alloc_slice_copy(&stmts)); diff --git a/src/js_parser/p.rs b/src/js_parser/p.rs index 88d199bf5c25..a347be6d4c17 100644 --- a/src/js_parser/p.rs +++ b/src/js_parser/p.rs @@ -105,13 +105,10 @@ pub struct ParserFeatures { // `NewParser` is just an alias for the generic struct. pub type NewParser<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> = P<'a, TYPESCRIPT, SCAN_ONLY>; -// TODO(port): the Zig `NewParser(features)` call sites pass a struct literal; in Rust callers -// must spell out the three const params directly. // ─── Conditional field types (Zig: `if (only_scan_imports_and_do_not_visit) *T else T`) ─── // Zig switched the field type at comptime. Rust const generics cannot select a type, so we // store both variants behind an enum and gate access in methods. -// TODO(port): revisit with associated types / GATs. pub enum ImportRecordList<'a> { Owned(BumpVec<'a, ImportRecord>), Borrowed(&'a mut Vec), @@ -205,6 +202,9 @@ pub use crate::visit::*; // the same struct `visitExpr.rs` pushes into it (cross-call buffer reuse, // matching Zig's `p.binary_expression_stack`). pub use crate::visit::visit_binary::BinaryExpressionVisitor; +// Same pattern for the unused-expression simplifier's reusable stack +// (Zig: `p.binary_expression_simplify_stack`). +pub use crate::scan::scan_side_effects::BinaryExpressionSimplifyVisitor; pub struct RecentlyVisitedTSNamespace { pub expr: js_ast::ExprData, @@ -586,8 +586,10 @@ pub struct P<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> { // These are backed by stack fallback allocators in _parse, and are uninitialized until then. // PERF(port): was stack-fallback alloc — profile if hot. pub binary_expression_stack: ListManaged<'a, BinaryExpressionVisitor>, - // TODO(port): SideEffects::BinaryExpressionSimplifyVisitor (SideEffects.rs) - pub binary_expression_simplify_stack: ListManaged<'a, ()>, + // Reusable stack for `SideEffects::simplify_unused_binary_comma_expr` + // (Zig: `p.binary_expression_simplify_stack`); avoids per-call allocation + // once that path is wired to push into it. + pub binary_expression_simplify_stack: ListManaged<'a, BinaryExpressionSimplifyVisitor>, /// We build up enough information about the TypeScript namespace hierarchy to /// be able to resolve scope lookups and property accesses for TypeScript enum @@ -620,7 +622,7 @@ pub struct P<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> { /// Used for react refresh, it must be able to insert `const _s = $RefreshSig$();` pub nearest_stmt_list: Option>>, - // TODO(port): lifetime — points at a stack local saved/restored across calls + // Lifetime caution: points at a stack local saved/restored across calls. /// Name from assignment context for anonymous decorated class expressions. /// Set before visitExpr, consumed by lowerStandardDecoratorsImpl. pub decorator_class_name: Option<&'a [u8]>, @@ -628,7 +630,7 @@ pub struct P<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> { // Transposer helpers (Zig: `const ImportTransposer = ExpressionTransposer(P, ..., P.transposeImport);`) // -// PORT NOTE: Zig's `ExpressionTransposer` is a comptime type-generator that +// Zig's `ExpressionTransposer` is a comptime type-generator that // captures `*P` and recursively pushes `import()` / `require()` / `require.resolve()` // through `?:` arms. Routing that through `crate::ExpressionTransposer` would // require materialising `&mut P` while a `&mut self` borrow of the transposer @@ -712,7 +714,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> } // Zig: `const Binding2ExprWrapper = struct { pub const Namespace = Binding.ToExpr(P, P.wrapIdentifierNamespace); ... }` -// PORT NOTE: `Binding.ToExpr(P, fn)` is a comptime type-generator returning a +// `Binding.ToExpr(P, fn)` is a comptime type-generator returning a // struct that holds `*P` + arena and dispatches `wrapIdentifier` to the // captured fn. The Rust port type-erases `*P` (which is generic over // `<'a, TYPESCRIPT, J, SCAN_ONLY>`) into `binding::ToExprWrapper` - same shim @@ -859,7 +861,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O where T: js_ast::expr::IntoExprData, { - // PORT NOTE: Zig's `comptime Type == E.Call` check is done post-init by + // Zig's `comptime Type == E.Call` check is done post-init by // matching on the constructed `Data` (Rust has no comptime type-eq). // Semantically equivalent — the import-record side-effect is order- // independent of `Expr.init`'s Store allocation. @@ -917,7 +919,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // stay individually ` // blocked_on:` below. // ═══════════════════════════════════════════════════════════════════════════ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_ONLY> { - pub const ALLOW_MACROS: bool = true /* TODO(port): feature_flag::IS_MACRO_ENABLED */; + /// Zig: `FeatureFlags.is_macro_enabled = !env.isWasm and !env.isWasi`. + pub const ALLOW_MACROS: bool = !cfg!(target_family = "wasm"); /// use this instead of checking p.source.index /// because when not bundling, p.source.index is `0` @@ -1180,7 +1183,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let _ = self.check_dynamic_specifier(arg, arg.loc, "require.resolve()"); // Zig: `arena.alloc(Expr, 1); args[0] = arg; ExprNodeList.fromOwnedSlice(args)`. - // PORT NOTE: Vec::from_owned_slice wants Box<[T]>; init_one is the + // Vec::from_owned_slice wants Box<[T]>; init_one is the // single-element equivalent (matches transpose_require below). self.new_expr( E::Call { @@ -1439,7 +1442,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // blocked_on: is_binding_used; SideEffects::to_boolean; Part fields; named_exports key type pub fn tree_shake(&mut self, parts: &mut &'a mut [js_ast::Part], merge: bool) { let mut parts_ = core::mem::take(parts); - // PORT NOTE: Zig used `defer` to merge parts after the loop. We replicate by + // Zig used `defer` to merge parts after the loop. We replicate by // running the merge logic explicitly after the while-loop below. let default_export_ref = self @@ -1453,7 +1456,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let last_end = parts_.len(); for i in 0..parts_.len() { - // PORT NOTE: Zig copied `Part` by value (POD struct). Rust `Part` is + // Zig copied `Part` by value (POD struct). Rust `Part` is // not `Clone`, so borrow it for the dead-check; the only mutation // is the swap into `parts_[parts_end]` at the bottom. let part = &parts_[i]; @@ -1570,7 +1573,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } parts_ = &mut parts_[..parts_end]; - // PORT NOTE: reshaped for borrowck — Zig wrote parts_.len = parts_end + // reshaped for borrowck — Zig wrote parts_.len = parts_end if last_end == parts_.len() { break; } @@ -1694,7 +1697,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O /// Zig: `p.b(t, loc)` — bump-allocate a binding payload and wrap it in `Binding`. /// `BindingAlloc` (Binding.rs round-G2) replaces the Zig `@TypeOf(t)` switch. /// - /// PORT NOTE: Zig's `p.b(t: anytype)` had a `@typeInfo == .pointer` arm that + /// Zig's `p.b(t: anytype)` had a `@typeInfo == .pointer` arm that /// dispatched to `Binding.init(t, loc)` (wrap-existing-allocation) instead of /// `Binding.alloc`. That arm is intentionally dropped here: every Zig caller /// passes `t` by value, so only the alloc path was ever exercised. If a future @@ -1738,7 +1741,6 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O alias: &'a [u8], r#ref: Ref, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set if let Some(name) = self.named_exports.get(alias) { // Duplicate exports are an error let notes: Box<[bun_ast::Data]> = Box::new([bun_ast::Data { @@ -1876,7 +1878,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Substitute an EImportIdentifier now if this has a namespace alias if opts.assign_target() == js_ast::AssignTarget::None && !opts.is_delete_target() { - // PORT NOTE: copy the alias out so the &self.symbols borrow is released + // copy the alias out so the &self.symbols borrow is released // before the &mut self calls below. let ns_alias_opt = self.symbols[ref_.inner_index() as usize] .namespace_alias @@ -2136,8 +2138,6 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O I: AsRef<[::Key]>, Sym: GenerateImportSymbols, { - // TODO(port): `imports: anytype` + `symbols: anytype` — modeled via a helper trait; - // verify shapes match the two call sites (RuntimeImports vs map). let arena = self.arena; let imports = imports.as_ref(); let import_record_i = @@ -2298,7 +2298,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let import_record_index = self.add_import_record_by_range(ImportKind::Stmt, bun_ast::Range::NONE, import_path); - // PORT NOTE: Zig used `if (hot_module_reloading) B.Object.Property else js_ast.ClauseItem` + // Zig used `if (hot_module_reloading) B.Object.Property else js_ast.ClauseItem` // as the comptime item type. Rust const-generics can't select a type // for a local, so we keep two arena vecs and only fill the one the // const-generic arm selects (the other stays empty / zero-cost). @@ -3349,13 +3349,13 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let scope_ref = &*scope; if !scope_ref.kind_stops_hoisting() { let arena = self.arena; - // PORT NOTE: Zig captured `var symbols = p.symbols.items;` and asserted it + // Zig captured `var symbols = p.symbols.items;` and asserted it // wasn't resized; we re-borrow `self.symbols` after each `new_symbol` call. // Check for collisions that would prevent to hoisting "var" symbols up to the enclosing function scope if let Some(scope_parent) = scope_ref.parent { let scope_strict_mode = scope_ref.strict_mode; - // PORT NOTE: reshaped for borrowck — Zig iterated `scope.members` while + // reshaped for borrowck — Zig iterated `scope.members` while // pushing to `scope.generated` and inserting into ancestor scopes' members. // The loop never inserts into `scope.members` itself (only ancestors), so // snapshotting `(name_ptr, Member)` pairs up front is semantically identical @@ -3495,7 +3495,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O { // Silently merge this symbol into the existing symbol self.symbols[symbol_idx].link.set(member_in_scope.ref_); - // PORT NOTE: Zig also wrote `entry.key_ptr.* = name`; the Rust + // Zig also wrote `entry.key_ptr.* = name`; the Rust // `StringHashMap` get_or_put already stores the key on insert and // cannot hand out `&mut K` (see StringHashMapGetOrPut docs), so // the key write is a no-op here. @@ -3612,7 +3612,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let order = self.next_scope_in_order_for_visit_pass(); // Sanity-check that the scopes generated by the first and second passes match - // PORT NOTE: Zig `and` binds tighter than `or`, so the original + // Zig `and` binds tighter than `or`, so the original // `allow_assert and loc_mismatch or kind_mismatch` keeps the kind check // unconditional in release builds. Preserve that grouping here. let order_scope = order.scope_ref(); @@ -3639,7 +3639,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O Ok(()) } - // PORT NOTE: Zig took `comptime kind` (adt_const_params on stable). All + // Zig took `comptime kind` (adt_const_params on stable). All // call sites pass a literal so the branch on `kind` is trivially predicted. #[allow(non_snake_case)] pub fn push_scope_for_parse_pass( @@ -3694,7 +3694,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O last_i -= 1; } - // PORT NOTE: reshaped for borrowck — copy out loc before borrowing self mutably. + // reshaped for borrowck — copy out loc before borrowing self mutably. if let Some(prev_loc) = self.scopes_in_order[last_i].as_ref().map(|s| s.loc) { if prev_loc.start >= loc.start { self.log().level = bun_ast::Level::Verbose; @@ -3950,8 +3950,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O /// make it a compile error #[inline] pub fn mark_type_script_only(&self) { - // TODO(port): Zig used @compileError; const-generic specialization can't express - // a compile error in Rust. Could move TS-only methods behind a trait. + // Zig used `@compileError`; const-generic specialization can't express a + // compile error in stable Rust, so this is a runtime assertion instead. if !TYPESCRIPT { unreachable!(); } @@ -3996,7 +3996,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O self.panic("Internal error", format_args!("")); } - // PORT NOTE (spec parity): Zig P.zig:2700-2707 does `var children = + // Spec parity: Zig P.zig:2700-2707 does `var children = // parent.children;` (a *value copy* of the Vec header) then // `_ = children.pop();` — the pop mutates only the local copy, so // `parent.children` is left unchanged (contrast `discardScopesUpTo` @@ -4014,7 +4014,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O loc: bun_ast::Loc, was_originally_bare_import: bool, ) -> Result { - let is_macro = true /* TODO(port): feature_flag::IS_MACRO_ENABLED */ && (path.is_macro || crate::Macro::is_macro_path(path.text)); + let is_macro = Self::ALLOW_MACROS && (path.is_macro || crate::Macro::is_macro_path(path.text)); let mut stmt = stmt_; if is_macro { let id = self.add_import_record(ImportKind::Stmt, path.loc, path.text); @@ -4270,7 +4270,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let items_slice: &mut [js_ast::ClauseItem] = stmt.items.slice_mut(); for i in 0..items_slice.len() { - // PORT NOTE: Zig copied `ClauseItem` by value (POD struct). Rust's + // Zig copied `ClauseItem` by value (POD struct). Rust's // `ClauseItem` does not derive `Copy`; bit-copy via `ptr::read` — // all fields are POD (`StoreStr`/`Loc`/`LocRef`). // SAFETY: items_slice[i] is a live initialised `ClauseItem`; the @@ -4459,7 +4459,6 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O kind: js_ast::symbol::Kind, identifier: &'a [u8], ) -> Result { - // TODO(port): narrow error set let inner_index = self.symbols.len() as js_ast::base::RefInt; // @truncate self.symbols.push(Symbol { kind, @@ -4529,7 +4528,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let mut cur = self.current_scope_ref(); let current_scope_ptr: *mut js_ast::Scope = cur.as_ptr(); let children = &mut cur.children; - // PORT NOTE: Zig copied `var children = scope.children` + `defer scope.children = children`. + // Zig copied `var children = scope.children` + `defer scope.children = children`. // Vec isn't Copy in Rust; mutate the field in place via the handle instead. for _child in &self.scopes_in_order[scope_index..] { @@ -4730,7 +4729,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } // Otherwise, generate a new namespace object. - // PORT NOTE: Zig batched map+scope into one alloc and patched + // Zig batched map+scope into one alloc and patched // `exported_members` post-init. `StoreRef` is non-null so the field // can't be null-then-patch; two bump allocs from the same arena is the // same locality and avoids the self-referential init. @@ -5162,8 +5161,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // `ImportRecord.path` is `fs::Path<'static>` (PORTING.md: no struct // lifetime params yet). The parser-supplied path borrows arena-owned 'a bytes // which outlive the import_records list (both dropped with the parser arena), - // so the lifetime extension is sound here. TODO(refactor): thread `'a` through - // `bun_ast::ImportRecord` and remove this erasure. + // so the lifetime extension is sound here. Removing the erasure requires + // threading `'a` through `bun_ast::ImportRecord`. // SAFETY: see above — arena 'a outlives every ImportRecord stored in self.import_records. let path: fs::Path<'static> = unsafe { path.into_static() }; // No `impl Default for ImportRecord` (range/path/kind have no Zig defaults) — @@ -5258,8 +5257,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O #[cold] pub fn panic(&mut self, fmt: &'static str, args: core::fmt::Arguments) -> ! { - // TODO(port): forward to panic_loc once that un-gates (needs log.print(&mut [u8])). - Output::panic(format_args!("{}\n{}", fmt, args)); + self.panic_loc(fmt, args, None) } #[cold] @@ -5270,7 +5268,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O args: core::fmt::Arguments, loc: Option, ) -> ! { - // PORT NOTE: Zig used a fixed `std.Io.Writer` over a 32 KiB stack buffer. + // Zig used a fixed `std.Io.Writer` over a 32 KiB stack buffer. // Rust's `Log::print` takes `IntoLogWrite` (`fmt::Write`), so write into a // bump-backed `String` instead — same single contiguous text output. let mut panic_stream = bun_alloc::ArenaString::with_capacity_in(32 * 1024, self.arena); @@ -5383,7 +5381,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let arena = self.arena; let mut opts = PrependTempRefsOpts::default(); let mut part_stmts = bun_alloc::vec_from_iter_in(stmts.iter().copied(), arena); - // PORT NOTE: Zig used ListManaged.fromOwnedSlice; we copy into a bump vec. + // Zig used ListManaged.fromOwnedSlice; we copy into a bump vec. self.visit_stmts_and_prepend_temp_refs(&mut part_stmts, &mut opts)?; @@ -5483,7 +5481,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O Ok(()) } - // PORT NOTE: Zig p.zig:3719 declares `bindingCanBeRemovedIfUnused` (the + // Zig p.zig:3719 declares `bindingCanBeRemovedIfUnused` (the // DCE-gated wrapper) but never calls it — every caller goes through // `stmtsCanBeRemovedIfUnused` which already gates on // `dead_code_elimination` and then invokes the `_without_dce_check` @@ -6372,7 +6370,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } crate::parser::Runtime::ReplaceableExport::Inject { name, value } => { let count = stmts.len(); - // PORT NOTE: Zig kept `with.name` as an arena slice; the Rust + // Zig kept `with.name` as an arena slice; the Rust // `ReplaceableExport::Inject` boxes it, so copy into the bump // arena to satisfy `declare_symbol`'s `&'a [u8]`. let name: &'a [u8] = self.arena.alloc_slice_copy(name); @@ -6624,7 +6622,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O ) }; - // PORT NOTE: `G::Arg` is not `Copy` (contains `Vec`); use + // `G::Arg` is not `Copy` (contains `Vec`); use // `alloc_slice_fill_iter` instead of `alloc_slice_copy`. let func_args = bun_ast::StoreSlice::new_mut(arena.alloc_slice_fill_iter([G::Arg { binding: self.b(B::Identifier { r#ref: arg_ref }, name_loc), @@ -6923,7 +6921,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Standard decorator lowering path (for both JS and TS files) if s_class.class.should_lower_standard_decorators { - // PORT NOTE: Zig `lowerStandardDecoratorsStmt` returns `[]Stmt`; the + // Zig `lowerStandardDecoratorsStmt` returns `[]Stmt`; the // Rust stub takes an out-param Vec instead. Wrap to keep // this function's `[]Stmt` contract. let mut out = BumpVec::::new_in(self.arena); @@ -7026,12 +7024,10 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let mut array = BumpVec::::new_in(self.arena); if self.options.features.emit_decorator_metadata { - // TODO(port): full design:type / design:paramtypes / design:returntype - // metadata emission ported below in condensed form. self.emit_decorator_metadata_for_prop(prop, &mut array, loc); } - // PORT NOTE: reshaped — Zig insertSlice(0, ...) prepends; we prepend then push args. + // reshaped — Zig insertSlice(0, ...) prepends; we prepend then push args. let mut full = BumpVec::::with_capacity_in( prop.ts_decorators.len_u32() as usize + array.len(), self.arena, @@ -7141,7 +7137,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O continue; } - // PORT NOTE: Zig copies `prop.*` by value into the new list; the old + // Zig copies `prop.*` by value into the new list; the old // backing slice is overwritten right after this loop, so `take` is // semantically equivalent (Property: Default). class_properties.push(core::mem::take(prop)); @@ -7171,7 +7167,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } let i = super_index.map(|j| j + 1).unwrap_or(0); - // TODO(port): bumpalo Vec lacks insert_slice; emulate via per-item insert. + // bumpalo Vec lacks insert_slice; emulate via per-item insert. for (off, m) in instance_members.iter().enumerate() { constructor_stmts.insert(i + off, *m); } @@ -7179,7 +7175,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O cf.func.body.stmts = bun_ast::StoreSlice::new_mut(constructor_stmts.into_bump_slice_mut()); } else { - // PORT NOTE: Zig `Property.List.fromList(class.properties)` re-wraps the + // Zig `Property.List.fromList(class.properties)` re-wraps the // freshly-installed slice and inserts at index 0. We rebuild instead // (Property is not Clone in Rust). let old_props: bun_ast::StoreSlice = s_class.class.properties; @@ -7348,9 +7344,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } } - // Helper extracted from lower_class to keep that fn readable. - // TODO(port): this condenses the Zig per-kind metadata switch (lines 5024-5105). - // Diff against Zig to verify exact arg ordering for get/set. + // Helper extracted from lower_class to keep that fn readable; condenses the + // Zig per-kind metadata switch (P.zig lines 5024-5105, same arg ordering). #[cold] #[inline(never)] fn emit_decorator_metadata_for_prop( @@ -7362,7 +7357,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O use js_ast::g::PropertyKind; // Local helper: bump-alloc an arg pair and call __legacyMetadataTS. - // PORT NOTE: pulled out of the per-arm code to cut a ~3x repetition vs Zig. + // pulled out of the per-arm code to cut a ~3x repetition vs Zig. macro_rules! push_metadata { ($label:expr, $value:expr) => {{ let label = self.new_expr(E::EString::from_static($label), bun_ast::Loc::EMPTY); @@ -7685,7 +7680,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O .expect("infallible: in namespace"); self.record_usage(enclosing_ref); - // TODO(port): E::Dot.name is `&'static [u8]` pending crate-wide 'bump + // E::Dot.name is `&'static [u8]` pending crate-wide 'bump // threading. Symbol.original_name is an arena-owned `StoreStr` (lives for // parser 'a, which outlives every Expr). Erase the lifetime to fit the // placeholder field type. @@ -7970,7 +7965,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O if call.args.len_u32() == 0 { return; } - // PORT NOTE: match `data` by value (it is `Copy`) so the `StoreRef<_>` + // match `data` by value (it is `Copy`) so the `StoreRef<_>` // payloads bind owned + `mut`, letting `to_utf8` mutate the EString in // place and `arr.items.slice_mut()` write through `DerefMut` — same // arena slots Zig's `*E.String` / `*E.Array` captures wrote to. @@ -8137,7 +8132,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O ) { debug_assert!(self.options.features.react_fast_refresh); debug_assert!(ReactRefresh::is_hook_name(original_name)); - // PORT NOTE: Zig stores `?*?HookContext` (raw pointer to stack storage in + // Zig stores `?*?HookContext` (raw pointer to stack storage in // the visiting fn frame). `ReactRefresh::hook_ctx_mut` centralises the // raw-pointer deref and returns a borrow detached from `self` (the // storage is on a caller stack frame), so we can call other `&mut self` @@ -8417,7 +8412,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O debug_assert!(!self.options.tree_shaking); debug_assert!(self.options.features.hot_module_reloading); - // PORT NOTE: Zig held `&mut parts[last]` inside `hmr_transform_ctx` + // Zig held `&mut parts[last]` inside `hmr_transform_ctx` // while iterating `parts` — Rust borrowck rejects that aliasing. // Reshaped via `split_last_mut` so the head slice and tail part are // disjoint borrows; `finalize()` takes only the head prefix so the @@ -8481,7 +8476,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Potentially remove some statements, then filter out parts to remove any // with no statements for idx in begin..parts.len() { - // PORT NOTE: Zig `var part = part_;` is a *shallow bitwise copy* + // Zig `var part = part_;` is a *shallow bitwise copy* // that leaves `parts.items[idx]` intact so the outer multi-pass // loop (which restarts at `begin = parts_end`) re-scans real data // on the next iteration. `mem::take` would zero the slot and @@ -8612,7 +8607,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } // leave the first part in there for namespace export when bundling - // PORT NOTE: Zig `parts.items.len = parts_end` does not drop the tail. + // Zig `parts.items.len = parts_end` does not drop the tail. // `truncate` would drop slots that may alias kept parts (the loop // above did `ptr::read` without clearing the source), so use // `set_len` to match Zig's no-destructor semantics. @@ -8720,7 +8715,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O total_stmts_count += usize::from(preserve_strict_mode); - // PORT NOTE: Stmt is not Default; fill with `Stmt::empty()`. + // Stmt is not Default; fill with `Stmt::empty()`. let stmts_to_copy = arena.alloc_slice_fill_with(total_stmts_count, |_| Stmt::empty()); { let mut remaining_stmts = &mut stmts_to_copy[..]; @@ -8752,7 +8747,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O loc: bun_ast::Loc::EMPTY, stmts: bun_ast::StoreSlice::new_mut(stmts_to_copy), }, - // PORT NOTE: Zig `Flags.Function.init(.{ .is_export = false })` → + // Zig `Flags.Function.init(.{ .is_export = false })` → // empty FunctionSet (no flags set). flags: Flags::FUNCTION_NONE, ..Default::default() @@ -8769,7 +8764,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O bun_ast::Loc::EMPTY, )]); - // PORT NOTE: reshaped — Zig wrote `parts.items.len = 1` directly. + // reshaped — Zig wrote `parts.items.len = 1` directly. // BumpVec has no `set_len`-on-grow path; ensure at least one slot then truncate. if parts.is_empty() { parts.push(js_ast::Part::default()); @@ -8780,7 +8775,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // REPL mode transforms if self.options.repl_mode { - // PORT NOTE: Zig `ReplTransforms(@This()).apply` → inherent `apply_repl_transforms` + // Zig `ReplTransforms(@This()).apply` → inherent `apply_repl_transforms` // (declared in ast::repl_transforms as an `impl P` mixin). self.apply_repl_transforms(parts, arena)?; } @@ -8789,7 +8784,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O if self.options.bundle { // Each part tracks the other parts it depends on within this file - // PORT NOTE: closure captures (top_level, symbols) via the `ctx` arg of + // closure captures (top_level, symbols) via the `ctx` arg of // `for_each_top_level_symbol`, since the iterator borrows `parts` while the // closure mutates `top_level_symbols_to_parts` (disjoint from `self.symbols`). struct Ctx<'s> { @@ -8878,7 +8873,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let char_freq: Option = self.compute_character_frequency(); let module_scope_strict = self.module_scope().strict_mode; - // PORT NOTE: Zig shallow-copies `p.module_scope.*` into Ast; Scope is not + // Zig shallow-copies `p.module_scope.*` into Ast; Scope is not // `Clone` in Rust (Vec/HashMap members), so move it out and leave // a default in `*self.module_scope`. `to_ast` is terminal — the parser // does not touch `module_scope` afterwards. @@ -9097,8 +9092,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } // The Binding2ExprWrapper / ExpressionTransposer self-referential helpers are -// seeded with arena-unit placeholders inside the struct literal. -// TODO(refactor): wire the real `*P` back-pointer. +// seeded with arena-unit placeholders inside the struct literal; the real `*P` +// back-pointer is wired lazily by the call sites. impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_ONLY> { /// Construct a `P` in place at `out` (matching Zig's `init(..., this: *P) !void`). /// @@ -9177,7 +9172,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // `bun_paths::fs::Path` view over the same `text`. `pretty` // is irrelevant once `node_modules/` is found in `text`; when // it isn't, the result won't match any `unwrap_commonjs_packages` - // entry anyway. // TODO(port): unify bun_paths::fs::Path<'static> → bun_paths::fs::Path + // entry anyway. (Goes away if `bun_paths::fs::Path<'static>` is + // ever unified with the resolver `bun_paths::fs::Path`.) let path_view = fs::Path { text: source.path.text, pretty: source.path.text, @@ -9366,7 +9362,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O options: opts, }); - // PORT NOTE: Zig wires `ImportTransposer.init(this)` etc. here. In Rust + // Zig wires `ImportTransposer.init(this)` etc. here. In Rust // the recursion lives as inherent `P::maybe_transpose_if_*` methods // called directly (no stored `*mut P`), and `Binding2ExprWrapper` // receives its `*mut P` per-call from the live `&mut P` at the call @@ -9415,7 +9411,7 @@ impl LowerUsingDeclarationsContext { stmts: &mut [Stmt], ) { for stmt in stmts.iter_mut() { - // PORT NOTE: Zig `switch (stmt.data) { .s_local => |local| ... }` — + // Zig `switch (stmt.data) { .s_local => |local| ... }` — // `local` is a `*S.Local`. Match the `StoreRef` by value (Copy ptr) // so DerefMut writes through to the arena slot. let stmt_loc = stmt.loc; @@ -9509,7 +9505,7 @@ impl LowerUsingDeclarationsContext { } js_ast::StmtData::SExportClause(data) => { // Merge export clauses together. - // PORT NOTE: ClauseItem isn't `Clone` (POD-only fields, no derive); + // ClauseItem isn't `Clone` (POD-only fields, no derive); // shallow-copy via ptr::read to mirror Zig `appendSlice`. // arena-owned `StoreSlice` valid for 'a; the source // slot is never read again (this whole stmt is dropped via the @@ -9534,7 +9530,7 @@ impl LowerUsingDeclarationsContext { // If any of these are exported, turn it into a "var" and add export clauses if local.is_export { local.is_export = false; - // PORT NOTE: Zig wrote `local.kind = .k_var` inside the + // Zig wrote `local.kind = .k_var` inside the // decls loop; borrowck rejects that aliasing through // StoreRef DerefMut. Hoist the kind write below. let mut any_ident = false; @@ -9799,8 +9795,9 @@ impl LowerUsingDeclarationsContext { } // ─── Helper trait for generate_import_stmt's `symbols: anytype` param ─── -// TODO(port): two call shapes exist (RuntimeImports and a string→Ref map). Impl -// this for both and verify the alias_name() RuntimeImports special case. +// Models Zig's comptime `if (@TypeOf(symbols) == RuntimeImports) +// RuntimeImports.all[alias] else alias` branch via `alias_name()`; implemented +// for `RuntimeImports` (Key = u8 index) and `JSXImportSymbols` (Key = alias). pub trait GenerateImportSymbols { type Key; fn get(&self, key: &Self::Key) -> Option; @@ -9820,7 +9817,7 @@ pub fn null_stmt_data() -> js_ast::StmtData { } #[inline] pub fn key_expr_data() -> js_ast::ExprData { - // PORT NOTE: Zig's `&Prefill.String.Key` was a `*E.String` to a static. + // Zig's `&Prefill.String.Key` was a `*E.String` to a static. // `ExprData::EString` now wraps a `StoreRef`; allocate a fresh // store node from the prefill constant on each call (callers are JSX-only // and infrequent — see js_ast::expr::IntoExprData for `EString`). diff --git a/src/js_parser/parse/mod.rs b/src/js_parser/parse/mod.rs index ed05fd408a63..f6e7c0741e22 100644 --- a/src/js_parser/parse/mod.rs +++ b/src/js_parser/parse/mod.rs @@ -79,7 +79,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let had_pure_comment_before = self.lexer.has_pure_comment_before && !self.options.ignore_dce_annotations; *expr = self.parse_prefix(level, errors.as_deref_mut(), flags)?; - // PORT NOTE: reshaped for borrowck — `errors` is reborrowed via as_deref_mut + // reshaped for borrowck — `errors` is reborrowed via as_deref_mut // for each call site instead of Zig's single pointer pass-through. // There is no formal spec for "__PURE__" comments but from reverse- @@ -199,7 +199,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O continue; } - // PORT NOTE: Zig hoisted `opts` above the loop; it is fully + // Zig hoisted `opts` above the loop; it is fully // reinitialized here every iteration before any read, so declare // per-iteration. let mut opts = PropertyOpts { @@ -223,7 +223,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O if let Some(property) = p.parse_property(js_ast::g::PropertyKind::Normal, &mut opts, None)? { - // PORT NOTE: read fields before move (G::Property is not Copy). + // read fields before move (G::Property is not Copy). let prop_kind = property.kind; let prop_key = property.key; properties.push(property); @@ -343,12 +343,17 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } pub fn parse_call_args(&mut self) -> Result { - let p = self; - // Allow "in" inside call arguments - let old_allow_in = p.allow_in; - p.allow_in = true; - // TODO(port): errdefer — restore `p.allow_in = old_allow_in` on error path + // Allow "in" inside call arguments; restored on every exit path + // (Zig: `defer p.allow_in = old_allow_in`). + let old_allow_in = self.allow_in; + self.allow_in = true; + let result = self.parse_call_args_inner(); + self.allow_in = old_allow_in; + result + } + fn parse_call_args_inner(&mut self) -> Result { + let p = self; let mut args: smallvec::SmallVec<[Expr; 4]> = smallvec::SmallVec::new(); p.lexer.expect(T::TOpenParen)?; @@ -371,7 +376,6 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } let close_paren_loc = p.lexer.loc(); p.lexer.expect(T::TCloseParen)?; - p.allow_in = old_allow_in; Ok(ExprListLoc { list: ExprNodeList::from_arena_slice(&args), loc: close_paren_loc, @@ -435,7 +439,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O p.allow_in = true; // Forbid "await" and "yield", but only for arrow functions - // PORT NOTE: Zig saved/restored via toBytes/bytesToValue; clone is equivalent. + // Zig saved/restored via toBytes/bytesToValue; clone is equivalent. let old_fn_or_arrow_data = p.fn_or_arrow_data_parse.clone(); p.fn_or_arrow_data_parse.arrow_arg_errors = arrow_arg_errors; p.fn_or_arrow_data_parse.track_arrow_arg_errors = true; @@ -492,7 +496,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O p.lexer.next()?; } let items: &'a mut [Expr] = items_list.into_bump_slice_mut(); - // PORT NOTE: Zig kept `items_list` alive and aliased `.items`; bump_slice is equivalent (arena-owned). + // Zig kept `items_list` alive and aliased `.items`; bump_slice is equivalent (arena-owned). // The parenthetical construct must end with a close parenthesis p.lexer.expect(T::TCloseParen)?; @@ -759,7 +763,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // SAFETY: E::String slices are arena-owned for 'a. return Ok(unsafe { bun_collections::detach_lifetime(estr.slice8()) }); } else { - // PORT NOTE: Zig used toUTF8AllocWithTypeWithoutInvalidSurrogatePairs which + // Zig used toUTF8AllocWithTypeWithoutInvalidSurrogatePairs which // errors on lone surrogates. The Rust port replaces them with U+FFFD; the // surrogate-error diagnostic path is dropped until the strict variant lands. let alias_utf8 = strings::to_utf8_alloc_with_type(estr.slice16()); @@ -969,7 +973,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O )), ..Default::default() }; - // PORT NOTE: reshaped for borrowck — Zig mutated `result.stmt_or_expr.expr` in place. + // reshaped for borrowck — Zig mutated `result.stmt_or_expr.expr` in place. if let js_ast::StmtOrExpr::Expr(ref mut e) = result.stmt_or_expr { p.parse_suffix(e, Level::Lowest, None, EFlags::None)?; } @@ -1369,7 +1373,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let supported_attribute: Option = 'brk: { // Parse the key if p.lexer.is_identifier_or_keyword() { - // PORT NOTE: Zig used `inline for` over enum values + @tagName. + // Zig used `inline for` over enum values + @tagName. if p.lexer.identifier == b"type" { break 'brk Some(SupportedAttribute::Type); } @@ -1615,14 +1619,20 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O js_ast::scope::Kind::FunctionArgs, async_range.loc, )?; - // TODO(port): errdefer — `defer p.popScope()` (borrowck blocks scopeguard over &mut p) let mut data = FnOrArrowDataParse { allow_await: AwaitOrYield::AllowExpr, needs_async_loc: args[0].binding.loc, ..Default::default() }; - let mut arrow_body = p.parse_arrow_body(args, &mut data)?; + // Zig: `defer p.popScope()` — pop on the error path too. + let mut arrow_body = match p.parse_arrow_body(args, &mut data) { + Ok(body) => body, + Err(e) => { + p.pop_scope(); + return Err(e); + } + }; arrow_body.is_async = true; p.pop_scope(); return Ok(p.new_expr(arrow_body, async_range.loc)); diff --git a/src/js_parser/parse/parse_entry.rs b/src/js_parser/parse/parse_entry.rs index 8eadde3d0dda..53a5c1ae695d 100644 --- a/src/js_parser/parse/parse_entry.rs +++ b/src/js_parser/parse/parse_entry.rs @@ -355,7 +355,6 @@ impl<'a> Parser<'a> { impl<'a> Parser<'a> { #[cfg_attr(not(target_arch = "wasm32"), allow(unused_mut))] pub fn parse(mut self) -> Result, Error> { - // TODO(port): narrow error set #[cfg(target_arch = "wasm32")] { self.options.ts = true; @@ -488,7 +487,7 @@ impl<'a> Parser<'a> { .set(ImportRecordFlags::IS_UNUSED, new_unused); } - // PORT NOTE: `scan_pass.used_symbols`/`import_records` are still + // `scan_pass.used_symbols`/`import_records` are still // exclusively borrowed inside `p`; route through `p`'s fields so the // borrow checker sees disjoint field access on the same struct. let import_records = p.import_records.items_mut(); @@ -511,7 +510,7 @@ impl<'a> Parser<'a> { // So we say "did we parse any JSX?" // if yes, just automatically add the import so that .bun knows to include the file. if p.options.jsx.parse && p.needs_jsx_import { - // PORT NOTE: Zig's `string` aliased the long-lived option storage + // Zig's `string` aliased the long-lived option storage // directly. `add_import_record` requires `&'a [u8]`, but borrowing // `p.options` would conflict with `&mut p`, so copy into the arena. let arena = p.arena; @@ -542,7 +541,6 @@ impl<'a> Parser<'a> { runtime_api_call: &'static [u8], symbols: js_ast::symbol::List<'a>, ) -> Result, Error> { - // TODO(port): narrow error set // Zig moves lexer/options by value into `P` (Parser.zig) and only // `defer p.lexer.deinit()` cleans up — Zig has no implicit destructor // on `Parser.lexer`. In Rust we move them out and leave inert @@ -701,11 +699,11 @@ impl<'a> Parser<'a> { if p.log().errors > 0 { #[cfg(target_arch = "wasm32")] { - // If the logger is backed by console.log, every print appends a newline. - // so buffering is kind of mandatory here - // TODO(port): Zig builds a custom GenericWriter wrapping Output::print and a - // buffered writer over it. Provide a `bun_core::Output::buffered()` - // that returns an `impl core::fmt::Write` flushed on drop. + // Divergence from Zig: Zig wraps `Output.writer()` in a + // buffered `GenericWriter` here because a console.log-backed + // logger appends a newline per print; the Rust port currently + // emits each message unbuffered (one console.log line per + // write on wasm32). Diagnostics formatting only. for msg in p.log().msgs.as_slice() { let mut m: bun_ast::Msg = *msg; let _ = m.write_format(Output::writer(), true); @@ -729,12 +727,13 @@ impl<'a> Parser<'a> { } fn _parse(self) -> Result, Error> { - // TODO(port): narrow error set - // TODO(port): bun_crash_handler::current_action — `Action` stores - // `&'static [u8]` but `self.source.path.text` is `'a`; widen - // the lifetime on `Action` (Zig held the same pointer). Once unblocked: - // let _restore = bun_crash_handler::scoped_action(Action::Parse(self.source.path.text)); - // (`ActionGuard` restores the previous action on Drop — no scopeguard.) + // Zig: `const prev_action = bun.crash_handler.current_action; defer + // bun.crash_handler.current_action = prev_action; ... = .{ .parse = ... }` + // (parse_entry.zig:333-335). `Source.path` is `Path<'static>`, so + // `path.text` satisfies `Action::Parse(&'static [u8])` directly. + let _action_guard = bun_crash_handler::scoped_action(bun_crash_handler::Action::Parse( + self.source.path.text, + )); // Zig moves lexer/options by value into `P` (Parser.zig:339) and only // `defer p.lexer.deinit()` cleans up — Zig has no implicit destructor @@ -804,13 +803,10 @@ impl<'a> Parser<'a> { // We must check the cache only after we've consumed the hashbang and leading // @bun pragma // We don't want to ever put files with `// @bun` into this cache, as that would be wasteful. #[cfg(not(target_arch = "wasm32"))] - if true - /* TODO(port): feature_flag */ - { + if bun_core::feature_flags::RUNTIME_TRANSPILER_CACHE { if let Some(cache) = p.options.features.runtime_transpiler_cache_mut() { - // TODO(port): `Path::is_node_module`/`is_jsx_file` live on the - // resolver `fs::Path` (not the logger stub) — inline their - // bodies until the logger Path grows them. + // `Path::is_node_module`/`is_jsx_file` live on the resolver + // `fs::Path` (not the logger stub) — their bodies are inlined here. let path = &p.source.path; #[cfg(windows)] const NM: &[u8] = b"\\node_modules\\"; @@ -872,8 +868,12 @@ impl<'a> Parser<'a> { return Err(err!("SyntaxError")); } - // TODO(port): bun_crash_handler::CURRENT_ACTION.set(Action::Visit(self.source.path.text)) - // — see lifetime note at top of fn. + // Zig sets `current_action = .{ .visit = ... }` here (parse_entry.zig:443) + // and relies on the function-scope `defer` above to restore the previous + // action; a second guard dropped at end of `_parse` is equivalent. + let _visit_action_guard = bun_crash_handler::scoped_action( + bun_crash_handler::Action::Visit(source.path.text), + ); let mut visit_tracer = bun_core::perf::trace("JSParser::visit"); p.prepare_for_visit_pass()?; @@ -961,7 +961,7 @@ impl<'a> Parser<'a> { // The TypeScript compiler itself contains code with this pattern, so // it's important to implement this optimization. - // PORT NOTE: `Loc` lacks `Hash` (logger crate), so the + // `Loc` lacks `Hash` (logger crate), so the // `scopes_in_order_for_enum` lookups linear-scan `keys()` — // matches Zig's ArrayHashMap linear behaviour at small N (one // entry per top-level `enum`). `scope_order_to_visit` is @@ -1001,7 +1001,7 @@ impl<'a> Parser<'a> { js_ast::StmtData::SLocal(local) => { if (local.decls.len_u32() as usize) > 1 { for decl in local.decls.slice() { - // PORT NOTE: `S::Local`/`Decl` are not `Copy`; + // `S::Local`/`Decl` are not `Copy`; // rebuild the struct instead of `**local`. let _local = S::Local { kind: local.kind, @@ -1056,7 +1056,7 @@ impl<'a> Parser<'a> { p.append_part(&mut parts, sliced)?; if should_move { - // PORT NOTE: `Part` isn't `Copy`; pop+push instead of last+truncate. + // `Part` isn't `Copy`; pop+push instead of last+truncate. before.push(parts.pop().expect("unreachable")); } } @@ -1073,7 +1073,7 @@ impl<'a> Parser<'a> { } } js_ast::StmtData::SEnum(_) => { - // PORT NOTE: `Part` isn't `Clone`; move out the + // `Part` isn't `Clone`; move out the // pre-visited parts instead of `appendSlice`. let enum_parts = core::mem::replace( &mut preprocessed_enums[preprocessed_enum_i], @@ -1222,7 +1222,7 @@ impl<'a> Parser<'a> { let mut remaining_stmts: &mut [Stmt] = all_stmts; for i in 0..p.imports_to_convert_from_require.len() { - // PORT NOTE: borrowck — copy out the three Copy fields so the + // borrowck — copy out the three Copy fields so the // immutable borrow of `p.imports_to_convert_from_require` // ends before `p.module_scope_mut()` takes `&mut self`. let (ns_ref, ns_loc, import_record_id) = { @@ -1273,7 +1273,7 @@ impl<'a> Parser<'a> { } if p.commonjs_named_exports.count() > 0 { - // PORT NOTE: borrowck — `deoptimize_commonjs_named_exports` mut-borrows + // borrowck — `deoptimize_commonjs_named_exports` mut-borrows // `self`, so the `values()`/`keys()` slices are read once into locals // (Zig kept slice handles across the call). let export_names_len = p.commonjs_named_exports.keys().len(); @@ -1547,7 +1547,7 @@ impl<'a> Parser<'a> { } } let _ = &mut *part; - // PORT NOTE: Zig had no explicit continue/break here; loop continues + // Zig had no explicit continue/break here; loop continues continue 'outer_part_loop; } } @@ -1563,9 +1563,9 @@ impl<'a> Parser<'a> { // export const foo = 123 // export * as ns from './foo' // - if false - /* TODO(port): feature_flag — Zig gates with comptime FeatureFlags.export_star_redirect (false) */ - { + // Zig gates with `comptime FeatureFlags.export_star_redirect`, which is + // permanently `false` (see the circular-export breakage above). + if false { // If the file only contains "export * from './blah' // we pretend the file never existed in the first place. // the semantic difference here is in export default statements @@ -1916,7 +1916,7 @@ impl<'a> Parser<'a> { } // if they didn't use any of the jest globals, don't inject it, I guess. - // PORT NOTE: Zig used `inline for (comptime std.meta.fieldNames(Jest))` — comptime + // Zig used `inline for (comptime std.meta.fieldNames(Jest))` — comptime // reflection over Jest's Ref fields. Rust iterates the static `Jest::FIELDS` // table (`&[(&'static str, fn(&Jest) -> Ref)]`) instead; declaration order // matches the Zig struct so emitted clause/property order is identical. @@ -2095,7 +2095,7 @@ impl<'a> Parser<'a> { }); if i > 0 { - // PORT NOTE: snapshot to break the `&mut self` ↔ `&self.runtime_imports` + // snapshot to break the `&mut self` ↔ `&self.runtime_imports` // borrow overlap in `generate_import_stmt(symbols: &Sym)`; the callee // never touches `self.runtime_imports`, so the clone is purely a // borrow-checker workaround (Zig passed by value here). @@ -2118,7 +2118,7 @@ impl<'a> Parser<'a> { && p.options.features.auto_import_jsx && p.options.jsx.runtime == options::JSX::Runtime::Automatic { - // PORT NOTE: `generate_import_stmt` takes `&mut self` plus `import_path: &'a [u8]` + // `generate_import_stmt` takes `&mut self` plus `import_path: &'a [u8]` // and `symbols: &Sym`, so the Pragma-owned `Box<[u8]>` paths are copied into the // bump arena (giving them the required `'a` lifetime) and `jsx_imports` is moved // out via `take` (it is `Default`) to avoid an overlapping `&self.jsx_imports` @@ -2222,9 +2222,7 @@ impl<'a> Parser<'a> { // p.popScope(); #[cfg(not(target_arch = "wasm32"))] - if true - /* TODO(port): feature_flag */ - { + if bun_core::feature_flags::RUNTIME_TRANSPILER_CACHE { if let Some(cache) = p.options.features.runtime_transpiler_cache_mut() { if p.macro_call_count != 0 { // disable this for: @@ -2244,7 +2242,7 @@ impl<'a> Parser<'a> { )?)) } - // PORT NOTE: associated fn (was `&self` reading `self.lexer.source.contents`) + // associated fn (was `&self` reading `self.lexer.source.contents`) // because `_parse` consumes `self` by value and destructures it before this // call site; the source contents are passed explicitly. // called from gated `_parse` body above diff --git a/src/js_parser/parse/parse_fn.rs b/src/js_parser/parse/parse_fn.rs index 260d9f8fee1a..8436b3ca4988 100644 --- a/src/js_parser/parse/parse_fn.rs +++ b/src/js_parser/parse/parse_fn.rs @@ -12,15 +12,13 @@ use bun_ast as js_ast; use bun_ast::op::Level; use bun_ast::{E, Expr, Flags, G, S, Stmt}; -// TODO(port): narrow error set type Error = bun_core::Error; // Zig: `pub fn ParseFn(comptime typescript, comptime jsx, comptime scan_only) type { return struct { ... } }` // — file-split mixin pattern. Round-C lowered `const JSX: JSXTransformType` → `J: JsxT`, so this is // a direct `impl P` block. impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_ONLY> { - // Zig: `const is_typescript_enabled = P.is_typescript_enabled;` - // (PORT NOTE: P.rs already defines `IS_TYPESCRIPT_ENABLED`; reuse it.) + // Zig: `const is_typescript_enabled = P.is_typescript_enabled;` — reuse `IS_TYPESCRIPT_ENABLED`. /// This assumes the "function" token has already been parsed pub fn parse_fn_stmt( @@ -198,7 +196,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O p.lexer.expect(T::TOpenParen)?; // Await and yield are not allowed in function arguments - // PORT NOTE: Zig used `std.mem.toBytes` / `bytesToValue` to save/restore by value; + // Zig used `std.mem.toBytes` / `bytesToValue` to save/restore by value; // in Rust `FnOrArrowDataParse` is `Clone`, so a clone is equivalent. let old_fn_or_arrow_data = p.fn_or_arrow_data_parse.clone(); @@ -381,7 +379,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } p.lexer.expect(T::TCloseParen)?; - // PORT NOTE: Zig restored via `std.mem.bytesToValue`; plain copy is equivalent. + // Zig restored via `std.mem.bytesToValue`; plain copy is equivalent. p.fn_or_arrow_data_parse = old_fn_or_arrow_data; p.fn_or_arrow_data_parse.has_argument_decorators = arg_has_decorators; @@ -570,17 +568,16 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } let _ = p.push_scope_for_parse_pass(js_ast::scope::Kind::FunctionBody, arrow_loc)?; - // PORT NOTE: Zig `defer p.popScope();` — moved to explicit call before each return below. - // TODO(port): consider scopeguard if more early-returns are added. + // Zig `defer p.popScope();` — moved to explicit call before each return below. - // PORT NOTE: Zig used `std.mem.toBytes`/`bytesToValue`; clone is equivalent. + // Zig used `std.mem.toBytes`/`bytesToValue`; clone is equivalent. let old_fn_or_arrow_data = p.fn_or_arrow_data_parse.clone(); p.fn_or_arrow_data_parse = data.clone(); let expr = match p.parse_expr(Level::Comma) { Ok(e) => e, Err(err) => { - // PORT NOTE: Zig `try` returns here without restoring fn_or_arrow_data_parse; + // Zig `try` returns here without restoring fn_or_arrow_data_parse; // only the `defer p.popScope()` fires on the error path. p.pop_scope(); return Err(err); diff --git a/src/js_parser/parse/parse_import_export.rs b/src/js_parser/parse/parse_import_export.rs index 757b4f510f4e..863b62e733ea 100644 --- a/src/js_parser/parse/parse_import_export.rs +++ b/src/js_parser/parse/parse_import_export.rs @@ -73,7 +73,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O p.allow_in = old_allow_in; if SCAN_ONLY { - // PORT NOTE: reshaped for borrowck — EString::slice takes &mut self (rope flatten), + // Reshaped for borrowck — EString::slice takes &mut self (rope flatten), // so capture the slice (arena-lifetime) before re-using `value` by-value below. let slice_opt: Option<&'a [u8]> = if let ExprData::EString(e_string) = &mut value.data { if e_string.is_utf8() && e_string.is_present() { @@ -113,12 +113,11 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O pub fn parse_import_clause(&mut self) -> Result, Error> { let p = self; - // TODO(port): narrow error set let mut items = bun_alloc::ArenaVec::::new_in(p.arena); p.lexer.expect(T::TOpenBrace)?; let mut is_single_line = !p.lexer.has_newline_before; // this variable should not exist if we're not in a typescript file - // PORT NOTE: in Zig this var was comptime-gated to only exist when TS is enabled; + // In Zig this var was comptime-gated to only exist when TS is enabled; // in Rust we declare it unconditionally — dead-store elim removes it when !TS. let mut had_type_only_imports = false; @@ -290,7 +289,6 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O pub fn parse_export_clause(&mut self) -> Result, Error> { let p = self; - // TODO(port): narrow error set let mut items = bun_alloc::ArenaVec::::with_capacity_in(1, p.arena); p.lexer.expect(T::TOpenBrace)?; let mut is_single_line = !p.lexer.has_newline_before; diff --git a/src/js_parser/parse/parse_jsx.rs b/src/js_parser/parse/parse_jsx.rs index 15686b1d8351..b764101cf64c 100644 --- a/src/js_parser/parse/parse_jsx.rs +++ b/src/js_parser/parse/parse_jsx.rs @@ -53,7 +53,10 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O 'parse_attributes: loop { match p.lexer.token { T::TIdentifier => { - // PORT NOTE: `defer i += 1` inlined at each exit point of this arm. + // `i` must be incremented whenever this arm pushes a property (Zig used + // `defer i += 1`). The early `continue` for an ignored bare `key` prop + // intentionally skips the increment: no property is pushed there, so + // `key_prop_i`/`first_spread_prop_i` stay valid indices into `props`. // Parse the prop name let key_range = p.lexer.range(); let prop_name_literal = p.lexer.identifier; @@ -104,7 +107,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O i += 1; // defer i += 1 } T::TOpenBrace => { - // PORT NOTE: `defer i += 1` inlined at end of this arm. + // This arm must increment `i` once before exiting (Zig used `defer i += 1`). // Use Next() not ExpectInsideJSXElement() so we can parse "..." p.lexer.next()?; diff --git a/src/js_parser/parse/parse_prefix.rs b/src/js_parser/parse/parse_prefix.rs index ac7d6068e5be..04aa489748a5 100644 --- a/src/js_parser/parse/parse_prefix.rs +++ b/src/js_parser/parse/parse_prefix.rs @@ -14,7 +14,6 @@ use bun_ast::g::{Arg, PropertyKind}; use bun_ast::op::Level; use bun_ast::{self as js_ast, B, E, Expr, ExprData, ExprNodeList, G, OpCode, scope, symbol}; -// TODO(port): narrow error set — Zig used `anyerror!Expr` throughout type PResult = core::result::Result; // Zig: `fn ParsePrefix(comptime ts, comptime jsx, comptime scan_only) type { return struct { ... } }` @@ -257,7 +256,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Handle the start of an arrow expression if p.lexer.token == T::TEqualsGreaterThan && level.lte(Level::Assign) { let ref_ = p.store_name_in_ref(name).expect("unreachable"); - // PORT NOTE: reshaped for borrowck — build binding before borrowing arena. + // reshaped for borrowck — build binding before borrowing arena. // `Arg` is non-Copy (owns Vec) → use fill_iter instead of alloc_slice_copy. let binding = p.b(B::Identifier { r#ref: ref_ }, loc); let args = p.arena.alloc_slice_fill_iter([Arg { @@ -268,7 +267,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let _ = p .push_scope_for_parse_pass(scope::Kind::FunctionArgs, loc) .expect("unreachable"); - // PORT NOTE: Zig `defer p.popScope()` — reshaped so pop_scope runs before `?` propagates + // Zig `defer p.popScope()` — reshaped so pop_scope runs before `?` propagates let mut fn_or_arrow_data = FnOrArrowDataParse { needs_async_loc: loc, ..Default::default() @@ -329,7 +328,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let loc = p.lexer.loc(); p.lexer.scan_reg_exp()?; // always set regex_flags_start to null to make sure we don't accidentally use the wrong value later - // PORT NOTE: Zig `defer p.lexer.regex_flags_start = null` — reset after both success and + // Zig `defer p.lexer.regex_flags_start = null` — reset after both success and // the `next()?` error path. Reshaped: capture, advance, then unconditionally reset before // propagating any error from `next()`. let value = E::Str::new(p.lexer.raw()); @@ -653,7 +652,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O )?; } - // PORT NOTE: spec passes the arena-backed `[]ExprNodeIndex` slice directly into + // spec passes the arena-backed `[]ExprNodeIndex` slice directly into // `ParseClassOptions{.ts_decorators = ts_decorators}`. `ParseClassOptions::ts_decorators` // is currently typed `&'a [Expr]` (parser.rs), so until that field is widened to // `ExprNodeList` we copy into the arena (Expr is `Copy`) and let `ts_decorators` drop @@ -697,7 +696,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } // This will become the new expr - // PORT NOTE: Zig allocates E::New with undefined fields then fills via the arena + // Zig allocates E::New with undefined fields then fills via the arena // pointer. Reshaped: parse target into a local, then construct E::New once. let mut target = Expr::EMPTY; p.parse_expr_with_flags(Level::Member, flags, &mut target)?; @@ -755,7 +754,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let dots_loc = p.lexer.loc(); p.lexer.next()?; - // PORT NOTE: reshaped for borrowck — Zig wrote into unusedCapacitySlice()[0] + // reshaped for borrowck — Zig wrote into unusedCapacitySlice()[0] // then bumped len; here we parse into a local then push. let mut value = Expr::EMPTY; p.parse_expr_or_bindings(Level::Comma, Some(&mut self_errors), &mut value)?; @@ -767,7 +766,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } } _ => { - // PORT NOTE: reshaped for borrowck — Zig wrote into unusedCapacitySlice()[0] + // reshaped for borrowck — Zig wrote into unusedCapacitySlice()[0] let mut item = Expr::EMPTY; p.parse_expr_or_bindings(Level::Comma, Some(&mut self_errors), &mut item)?; items.push(item); @@ -836,7 +835,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O while p.lexer.token != T::TCloseBrace { if p.lexer.token == T::TDotDotDot { p.lexer.next()?; - // PORT NOTE: reshaped for borrowck — Zig wrote into unusedCapacitySlice()[0] + // reshaped for borrowck — Zig wrote into unusedCapacitySlice()[0] // with `value: Expr.empty` then parsed into &property.value.? let mut value = Expr::EMPTY; p.parse_expr_or_bindings(Level::Comma, Some(&mut self_errors), &mut value)?; @@ -899,7 +898,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O self_errors.merge_into(errors.unwrap()); } - // PORT NOTE: BumpVec → Vec via arena slice; see pfx_t_open_bracket. + // BumpVec → Vec via arena slice; see pfx_t_open_bracket. let properties_list = G::PropertyList::from_bump_vec(properties); Ok(p.new_expr( E::Object { diff --git a/src/js_parser/parse/parse_property.rs b/src/js_parser/parse/parse_property.rs index 53b79617d0de..228c68689403 100644 --- a/src/js_parser/parse/parse_property.rs +++ b/src/js_parser/parse/parse_property.rs @@ -132,8 +132,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } p.pop_scope(); - // PORT NOTE: G::Fn is not Copy (FnBody/TS-metadata aren't), so mutate in place via the - // E::Function payload after boxing rather than copying `func`. + // G::Fn is not Copy (FnBody/TS-metadata aren't), so mutate `func` in place + // rather than copying it as the Zig original did. let mut func = func; func.flags.insert(flags::Function::IsUniqueFormalParameters); let args = func.args.slice(); @@ -801,7 +801,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O ..Default::default() }; - // PORT NOTE: reshaped for borrowck — `errors` is Option<&mut _>, reborrow via as_deref_mut + // `errors` is Option<&mut _>; reborrow via as_deref_mut so the caller's binding stays usable p.parse_expr_or_bindings( Level::Comma, errors.as_deref_mut(), diff --git a/src/js_parser/parse/parse_skip_typescript.rs b/src/js_parser/parse/parse_skip_typescript.rs index 8a42000f4c1b..71cf9430b9ce 100644 --- a/src/js_parser/parse/parse_skip_typescript.rs +++ b/src/js_parser/parse/parse_skip_typescript.rs @@ -10,12 +10,11 @@ use bun_ast::ts::Metadata; use bun_core::{self, Error, err}; // Zig: `fn SkipTypescript(comptime ts, comptime jsx, comptime scan_only) type { return struct {...} }` -// — file-split mixin pattern. Round-C lowered `const JSX: JSXTransformType` → `J: JsxT`, so this is -// a direct `impl P` block. +// — file-split mixin pattern, lowered to a direct `impl P` block on the const-generic parser. -// PORT NOTE: Zig nested `Bitset` inside `SkipTypeOptions`; Rust hoists it to a module-level -// alias. Re-export here so the parser-side type alias used in this file matches the -// canonical definition in `TypeScript.rs`. +// Re-export so the parser-side type alias used in this file matches the +// canonical definition in `TypeScript.rs` (the Zig original nested this +// bitset inside `SkipTypeOptions`). pub(crate) type SkipTypeOptionsBitset = typescript::SkipTypeOptionsBitset; impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_ONLY> { @@ -229,7 +228,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O Ok(()) } - // PORT NOTE: Zig signature is `result: if (get_metadata) *TypeScript.Metadata else void`. + // The Zig signature is `result: if (get_metadata) *TypeScript.Metadata else void`. // Rust cannot express a const-generic-dependent param type on stable; we use // `Option<&mut Metadata>` and require callers to pass `Some` iff `GET_METADATA == true`. // The const generic is kept so `if GET_METADATA { ... }` branches monomorphize away. @@ -884,8 +883,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } if GET_METADATA { - // PORT NOTE: reshaped for borrowck — `find_symbol` borrows `&mut self`; - // `result` is a disjoint fn parameter so the borrows do not conflict. + // `find_symbol` borrows `&mut self`; `result` is a disjoint fn + // parameter so the borrows do not conflict. let ident = self.lexer.identifier; let r = result .as_deref_mut() @@ -1421,8 +1420,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O F: FnOnce(&mut Self) -> Result, { self.mark_type_script_only(); - // PORT NOTE: Zig copies the lexer by value; Rust Lexer holds `&mut Log` so we use a - // POD `LexerSnapshot` and `restore()`. + // The Zig original copies the lexer by value to backtrack; the Rust Lexer + // holds `&mut Log`, so backtracking goes through a POD `LexerSnapshot` + `restore()`. let old_lexer = self.lexer.snapshot(); let old_log_disabled = self.lexer.is_log_disabled; self.lexer.is_log_disabled = true; diff --git a/src/js_parser/parse/parse_stmt.rs b/src/js_parser/parse/parse_stmt.rs index f564c83b4374..666f28444f6d 100644 --- a/src/js_parser/parse/parse_stmt.rs +++ b/src/js_parser/parse/parse_stmt.rs @@ -20,7 +20,6 @@ use crate::typescript; use bun_ast::{ImportKind, ImportRecordFlags, ImportRecordTag}; use js_ast::expr::EFlags; -// TODO(port): narrow error set type Result = core::result::Result; // Zig: `pub fn ParseStmt(comptime ts, comptime jsx, comptime scan_only) type { return struct {...} }` @@ -30,7 +29,7 @@ type Result = core::result::Result; // `t_export`/`t_import`/fallthrough bodies inline (the `_draft_heavy` staging mod is gone). impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_ONLY> { - // PORT NOTE on `#[inline]` / `#[inline(never)]` / `#[cold]` annotations across the `t_*` arms: + // Note on `#[inline]` / `#[inline(never)]` / `#[cold]` annotations across the `t_*` arms: // `parse_stmt` is invoked once per leading statement token; profiling showed its // stack-adjust prologue/epilogue dominating because LLVM was hoisting the larger // (and rarely-taken) `t_*` bodies inline, ballooning `parse_stmt`'s frame. Keep the @@ -95,7 +94,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // "@decorator export declare class Foo {}" // "@decorator export declare abstract class Foo {}" // - // PORT NOTE: spec stores the Vec directly into `opts.ts_decorators.values`. + // spec stores the Vec directly into `opts.ts_decorators.values`. // `DeferredTsDecorators::values` is currently typed `&'a [Expr]` (parser.rs), so until // that field is widened to `ExprNodeList` we copy into the arena (Expr is `Copy`) and // let `ts_decorators` drop normally — no `mem::forget` / `from_raw_parts` lifetime @@ -204,7 +203,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O fn t_if(p: &mut Self, _: &mut ParseStatementOptions, loc: bun_ast::Loc) -> Result { let mut current_loc = loc; let mut root_if: Option = None; - // PORT NOTE: `StoreRef` (arena back-pointer with safe `Deref`/`DerefMut`) + // `StoreRef` (arena back-pointer with safe `Deref`/`DerefMut`) // into the previous iteration's `S::If` allocation — borrowck cannot // express the cross-iteration back-reference, but the arena keeps every // node alive for `'a`. @@ -351,7 +350,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let mut found_default = false; while p.lexer.token != T::TCloseBrace { let mut body = StmtList::new_in(p.arena); - // PORT NOTE: Zig hoisted `value`/`stmt_opts` above the loop; + // Zig hoisted `value`/`stmt_opts` above the loop; // both are reinitialized every iteration before any read, so // declare per-iteration. let mut value: Option = None; @@ -1895,8 +1894,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O pub fn parse_stmt(&mut self, opts: &mut ParseStatementOptions<'a>) -> Result { if !self.stack_check.is_safe_to_recurse() { - // TODO(port): bun_core::throw_stack_overflow() not yet exported; map to a SyntaxError - // until the StackOverflow error variant lands. + // Sentinel error; mapped to a "Maximum call stack size exceeded" + // syntax error at the catch site in parse_entry.rs. return Err(err!("StackOverflow")); } diff --git a/src/js_parser/parse/parse_suffix.rs b/src/js_parser/parse/parse_suffix.rs index 7a000d8b23b0..5d5067d53ae8 100644 --- a/src/js_parser/parse/parse_suffix.rs +++ b/src/js_parser/parse/parse_suffix.rs @@ -100,8 +100,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O p.lexer.expect(T::TIdentifier)?; } - // TODO(port): `E::Dot::name` is `&'static [u8]` (arena-owned slice - // placeholder); lexer hands back `&'a [u8]`. + // `E::Dot::name` is spelled `&'static [u8]` but actually holds an + // arena-owned slice; the lexer hands back `&'a [u8]`. let name = E::Str::new(p.lexer.identifier); let name_loc = p.lexer.loc(); p.lexer.next()?; @@ -431,7 +431,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let loc = left.loc; let prev = *left; - // PORT NOTE: Zig allocates an E::If with `undefined` yes/no then writes through the + // Zig allocates an E::If with `undefined` yes/no then writes through the // arena pointer (`ternary.data.e_if.yes`). The `Data::EIf(StoreRef)` payload is a // boxed arena slot, so we mirror that: allocate first, then fill via DerefMut on StoreRef. let ternary = p.new_expr( @@ -552,7 +552,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Zig used `inline` @field/@tagName comptime dispatch for the 30+ simple binary // operators below. Rust has no struct-field-name reflection; each is written out. - // PORT NOTE: bodies are uniform — `if level.gte(L) {Done}; next; new Binary{op,left,right}`. + // bodies are uniform — `if level.gte(L) {Done}; next; new Binary{op,left,right}`. fn sfx_t_plus(p: &mut Self, level: Level, left: &mut Expr) -> CResult { if level.gte(Level::Add) { @@ -580,7 +580,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O p.lexer.next()?; let loc = left.loc; let prev = *left; - // PORT NOTE: Zig wrote `@enumFromInt(@intFromEnum(Op.Level.assign) - 1)`; equivalent to `Level::Assign.sub(1)`. + // Zig wrote `@enumFromInt(@intFromEnum(Op.Level.assign) - 1)`; equivalent to `Level::Assign.sub(1)`. let right = p.parse_expr(Level::Assign.sub(1))?; *left = p.new_expr( E::Binary { @@ -1442,7 +1442,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O flags: EFlags, ) -> Result<(), Error> { let p = self; - // PORT NOTE: Zig kept a separate `left_value` local + `left = &left_value` + // Zig kept a separate `left_value` local + `left = &left_value` // to work around a Zig codegen bug ("creates a new address to stack locals // each & usage"). Rust has no such bug, so we mutate `left` directly and // drop the trailing/deferred `left_and_out.* = left_value` writebacks. @@ -1450,7 +1450,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let mut optional_chain: Option = None; loop { if p.lexer.loc().start == p.after_arrow_body_loc.start { - // PORT NOTE: Zig labeled-switch `next_token: switch (...) { continue :next_token ... }` + // Zig labeled-switch `next_token: switch (...) { continue :next_token ... }` // becomes a plain loop re-reading `p.lexer.token` each iteration. loop { match p.lexer.token { @@ -1501,7 +1501,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // function with many scopes and local variables consumes // enormous amounts of stack space. // - // PORT NOTE: Zig used `inline ... => |tag| @field(@This(), @tagName(tag))(p, level, left)` + // Zig used `inline ... => |tag| @field(@This(), @tagName(tag))(p, level, left)` // for comptime name-based dispatch. Rust has no @field/@tagName reflection, so each // arm is written out explicitly. let continuation = match p.lexer.token { diff --git a/src/js_parser/parse/parse_typescript.rs b/src/js_parser/parse/parse_typescript.rs index a766177d7c44..9df3f7d4df27 100644 --- a/src/js_parser/parse/parse_typescript.rs +++ b/src/js_parser/parse/parse_typescript.rs @@ -36,7 +36,6 @@ fn clone_ts_member_data(d: &TSNamespaceMemberData) -> TSNamespaceMemberData { // a direct `impl P` block. impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_ONLY> { - // TODO(port): narrow error set pub fn parse_type_script_decorators(&mut self) -> Result { let p = self; if !Self::IS_TYPESCRIPT_ENABLED && !p.options.features.standard_decorators { @@ -65,7 +64,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // // This matches the behavior of the TypeScript compiler. // PERF(port): was ensureUnusedCapacity + unusedCapacitySlice — profile if it shows up on a hot path - // PORT NOTE: Zig `parseExprWithFlags` takes an out-param slot; preserved here. + // Zig `parseExprWithFlags` takes an out-param slot; preserved here. let mut expr = Expr::EMPTY; p.parse_expr_with_flags(Level::New, EFlags::TsDecorator, &mut expr)?; decorators.push(expr); @@ -82,7 +81,6 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O /// @ DecoratorMemberExpression /// @ DecoratorCallExpression /// @ DecoratorParenthesizedExpression - // TODO(port): narrow error set pub fn parse_standard_decorator(&mut self) -> Result { let p = self; let loc = p.lexer.loc(); @@ -431,7 +429,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O .insert(name.ref_.expect("infallible: ref bound"), ns_member_data); } - // PORT NOTE: S::Namespace.stmts is `StoreSlice` (arena slice). BumpVec → bump slice. + // S::Namespace.stmts is `StoreSlice` (arena slice). BumpVec → bump slice. let stmts_slice: &'a mut [Stmt] = stmts.into_bump_slice_mut(); Ok(p.s( S::Namespace { @@ -592,7 +590,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Parse the name if p.lexer.token == T::TStringLiteral { - // PORT NOTE: `slice8()` is currently duplicated in E.rs (two impl blocks); + // `slice8()` is currently duplicated in E.rs (two impl blocks); // read `.data` directly — `to_utf8_e_string` guarantees `is_utf16 == false`. let estr = p.lexer.to_utf8_e_string()?; debug_assert!(!estr.is_utf16); @@ -673,7 +671,6 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // run the renamer. For external-facing things the renamer will avoid // collisions automatically so this isn't important for correctness. // PERF(port): strings::cat heap-allocates; Zig allocated into p.arena. - // TODO(perf): route through bump arena. let prefixed = strings::cat(b"_", name_text).expect("unreachable"); let prefixed: &'a [u8] = p.arena.alloc_slice_copy(&prefixed); arg_ref = p diff --git a/src/js_parser/parser.rs b/src/js_parser/parser.rs index f6b33a185f43..3ba5d5e255e2 100644 --- a/src/js_parser/parser.rs +++ b/src/js_parser/parser.rs @@ -30,8 +30,6 @@ pub mod options { // re-exported here so the `options::Loader`/`options::Target` spelling used // throughout `P.rs`/`Parser.rs` keeps resolving without per-site churn. pub(crate) use bun_ast::Loader; - // TODO(port): bun_options_types::{ServerComponents, OutputFormat, - // AllowUnresolved, Format, Framework} — missing from lower-tier surface. pub use bun_options_types::bundle_enums::ModuleType; // D042: canonical `JSX::{Pragma, Runtime, ImportSource, Defaults, ...}` // lives in `bun_options_types::jsx`. The glob above already brings in @@ -275,7 +273,7 @@ pub mod Runtime { /// This is used for `--print` entry points so we can get the result. pub remove_cjs_module_wrapper: bool, - // PORT NOTE: `?*bun.jsc.RuntimeTranspilerCache` — raw `*mut` (not `&'a mut`) + // `?*bun.jsc.RuntimeTranspilerCache` — raw `*mut` (not `&'a mut`) // so `Features` stays `'static`-bounded inside `Parser::Options` and avoids // the borrowck self-borrow that `&'a mut` would induce while `P` holds // `&mut Options`. @@ -381,7 +379,7 @@ pub mod Runtime { if feature_flags.is_empty() { return None; } - // PORT NOTE: reshaped for borrowck — Zig inserted then sorted via + // reshaped for borrowck — Zig inserted then sorted via // `set.map.sort(...)` with a comparator borrowing `set.map.keys()`. // `StringSet` preserves insertion order and has no in-place key sort, // so sort the inputs first; the resulting `keys()` iteration order @@ -603,7 +601,6 @@ pub(crate) type RuntimeImports = Runtime::Imports; pub use crate::p::{NewParser, P}; -pub use bun_collections::StringHashMap as StringHashMapRe; // TODO(port): name collision with `StringHashMap` re-export // NOTE(b0): `pub use bun_js_printer as js_printer;` removed — js_printer is same-tier mutual // (js_printer depends on js_parser). Downstream callers import bun_js_printer directly. @@ -1144,7 +1141,7 @@ macro_rules! generated_symbol_name { pub struct ExprOrLetStmt { pub stmt_or_expr: js_ast::StmtOrExpr, - // PORT NOTE: Zig writes `.decls = decls.slice()` borrowing the heap buffer + // Zig writes `.decls = decls.slice()` borrowing the heap buffer // that was just moved into `S::Local`. The buffer pointer is stable across // the move, but borrowck can't see that — store as `RawSlice` to record the // outlives-holder invariant without a per-site unsafe cast. Read by the @@ -1474,7 +1471,9 @@ pub struct StringVoidMap { impl StringVoidMap { /// Returns true if the map already contained the given key. pub(crate) fn get_or_put_contains(&mut self, key: &[u8]) -> bool { - // TODO(port): StringHashMap key ownership — Zig stored borrowed source slices. + // Zig stored borrowed source slices as keys; the Rust StringHashMap + // copies keys into owned heap allocations on insert, so the pooled map + // outliving any one source is fine. let entry = self.map.get_or_put(key).expect("unreachable"); entry.found_existing } @@ -1511,10 +1510,12 @@ impl bun_collections::pool::ObjectPoolType for StringVoidMap { bun_collections::object_pool!(pub StringVoidMapPool: StringVoidMap, threadsafe, 32); pub(crate) type StringBoolMap = StringHashMap; -pub(crate) type RefMap = HashMap; // TODO(port): RefCtx hasher + 80% load factor -pub(crate) type RefRefMap = HashMap; // TODO(port): RefCtx hasher + 80% load factor +// Zig used `std.HashMapUnmanaged(Ref, _, RefCtx, 80)`; the default hasher and +// load factor here are close enough that no custom context is warranted. +pub(crate) type RefMap = HashMap; +pub(crate) type RefRefMap = HashMap; -// PORT NOTE: `scope` is `*mut` (not `&'arena`) because the visit pass writes +// `scope` is `*mut` (not `&'arena`) because the visit pass writes // through it (push_scope_for_visit_pass assigns it to `current_scope: *mut`) // and the parse pass needs Copy for the BumpVec> to be // indexable + truncatable. The Scope itself is arena-owned for `'arena`. @@ -1820,7 +1821,7 @@ impl ScanPassResult { self.named_imports.clear_retaining_capacity(); self.import_records.clear(); self.used_symbols.clear_retaining_capacity(); - // PORT NOTE: parser.zig:778-783 does NOT clear import_records_to_keep here; + // parser.zig:778-783 does NOT clear import_records_to_keep here; // matching Zig (the keep-list persists across reset()). self.approximate_newline_count = 0; } @@ -1897,23 +1898,14 @@ impl<'a> ParseStatementOptions<'a> { } } -// TODO(port): `Prefill` holds mutable global AST node singletons (`pub var` in -// Zig). Rust forbids non-`Sync` mutable statics without `unsafe`; several of -// these contain raw pointers (e_string -> &E.String) and one (`ActivateIndex`) -// has an `undefined` field. TODO(refactor): decide between `static mut` + -// `unsafe`, `LazyLock`, or eliminating the globals entirely. The byte-array -// constants are safe and ported as `pub const`. +// Zig's `Prefill` held mutable global AST node singletons (`pub var`); only +// the const-able subset survived the port. Callers needing the rest (missing +// nodes, empty statements, the HMR helper exprs) construct them directly — +// they are cheap value types, so the shared-singleton optimization isn't +// worth `static mut`/`LazyLock` plumbing in Rust. pub mod prefill { use super::*; - pub mod hot_module_reloading { - // TODO(port): mutable static Expr arrays — need `static mut` or `LazyLock`. - // pub static DEBUG_ENABLED_ARGS: [Expr; 1] = [...]; - // pub static DEBUG_DISABLED: [Expr; 1] = [...]; - // pub static ACTIVATE_STRING: E::String = E::String { data: b"activate" }; - // pub static ACTIVATE_INDEX: E::Index = ...; // .target = undefined - } - pub mod string_literal { pub(crate) const CHILDREN: [u8; 8] = *b"children"; } @@ -1931,13 +1923,6 @@ pub mod prefill { pub mod data { use super::*; - // TODO(port): Expr.Data / Stmt.Data / B variant statics — needs final - // shape of `js_ast::ExprData` (Rust enum) before these compile. - // pub static B_MISSING: B = B::Missing(B::Missing {}); - // pub static E_MISSING: ExprData = ExprData::EMissing(E::Missing {}); - // pub static S_EMPTY: StmtData = StmtData::SEmpty(S::Empty {}); - // pub static FILENAME: ExprData = ExprData::EString(&string::FILENAME); - // ... etc. pub const THIS: js_ast::ExprData = js_ast::ExprData::EThis(E::This {}); pub(crate) const ZERO: js_ast::ExprData = js_ast::ExprData::ENumber(value::ZERO); } @@ -1982,9 +1967,9 @@ pub struct MacroState<'a> { } impl<'a> MacroState<'a> { - // TODO(port): Zig initializes `prepend_stmts = undefined`; Rust cannot leave - // a `&mut` field uninitialized. Caller must supply a placeholder list, or - // this field becomes `Option<&'a mut Vec>` set to `None` here. + // Zig initialized `prepend_stmts = undefined` (the field is write-only in + // both ports); Rust cannot leave a `&mut` field uninitialized, so the + // caller supplies a placeholder list. pub fn init(prepend_stmts: &'a mut Vec) -> MacroState<'a> { MacroState { refs: MacroRefs::default(), @@ -2272,7 +2257,7 @@ pub struct ReactRefresh<'a> { /// and then it will insert `var _s = ...`, add the `_s()` call at /// the start of the function, and then add the call to `_s(func, ...)`. /// - /// PORT NOTE: Zig type is `?*?HookContext` — a raw nullable pointer to + /// Zig type is `?*?HookContext` — a raw nullable pointer to /// stack storage on the visiting fn frame. Modeled as `Option>` /// (Copy) so the save/set/restore dance in visitStmt/visitExpr can take a /// stack-local address without the `'a` borrow the visitor cannot satisfy. diff --git a/src/js_parser/repl_transforms.rs b/src/js_parser/repl_transforms.rs index 267f028e932a..d0ea80b1183f 100644 --- a/src/js_parser/repl_transforms.rs +++ b/src/js_parser/repl_transforms.rs @@ -220,7 +220,7 @@ impl<'a, const TS: bool, const SCAN: bool> P<'a, TS, SCAN> { )); // Convert class declaration to assignment: ClassName = class ClassName {} - // PORT NOTE: G::Class is non-Copy (owns a Vec); the original + // G::Class is non-Copy (owns a Vec); the original // S::Class store entry is dead after this rewrite, so move it out. let class_value = core::mem::take(&mut class.class); let class_expr = self.new_expr(class_value, stmt.loc); diff --git a/src/js_parser/scan/scan_imports.rs b/src/js_parser/scan/scan_imports.rs index 29cdebfc5629..43fa944e7ba9 100644 --- a/src/js_parser/scan/scan_imports.rs +++ b/src/js_parser/scan/scan_imports.rs @@ -9,7 +9,7 @@ use bun_collections::VecExt; use bun_core::strings; use bun_crash_handler::handle_oom::handle_oom; -// PORT NOTE: Zig file-level struct → Rust struct. `stmts` is a sub-slice of the +// Zig file-level struct → Rust struct. `stmts` is a sub-slice of the // input `stmts` argument (in-place compacted), so it borrows from the caller. #[derive(Default)] pub(crate) struct ImportScanner<'a> { @@ -26,11 +26,10 @@ fn raw_str(s: &'static [u8]) -> js_ast::StoreStr { } impl<'a> ImportScanner<'a> { - // TODO(port): narrow error set - // PORT NOTE: `

` unbounded generic → concrete `P<'a, TS, SCAN>`. - // TODO(port): the Zig also accepts `bun.bundle_v2.AstBuilder` as P (comptime - // `P != AstBuilder` check). Only the parser P is handled here; the AstBuilder - // path needs a `ParserLike` trait or a separate monomorphization. + // `

` unbounded generic → concrete `P<'a, TS, SCAN>`. The Zig version also + // accepted `bun.bundle_v2.AstBuilder` as P (comptime `P != AstBuilder` check); + // only the parser P is handled here — the bundler scans imports via its own + // path and does not go through this function. pub(crate) fn scan< 'p, const TYPESCRIPT: bool, @@ -40,7 +39,7 @@ impl<'a> ImportScanner<'a> { p: &mut P<'p, TYPESCRIPT, SCAN_ONLY>, stmts: &'a mut [Stmt], will_transform_to_common_js: bool, - // PORT NOTE: Zig used `if (comptime_bool) *T else void` for this param's + // Zig used `if (comptime_bool) *T else void` for this param's // type; Rust const generics can't gate a param type, so use Option and // debug-assert presence matches the const. mut hot_module_reloading_context: Option<&mut ConvertESMExportsForHmr>, @@ -52,22 +51,22 @@ impl<'a> ImportScanner<'a> { let mut scanner = ImportScanner::default(); let mut stmts_end: usize = 0; - // PORT NOTE: `arena` (p.arena) dropped — see §Allocators (AST crate). + // `arena` (p.arena) dropped — see §Allocators (AST crate). // Arena allocs below go through `p.arena` (a &Bump) where they persist. let is_typescript_enabled: bool = TYPESCRIPT; for i in 0..stmts.len() { - // PORT NOTE: Zig iterated by value-copy then wrote back via index at + // Zig iterated by value-copy then wrote back via index at // the bottom; we index directly to allow in-place mutation + reassign. let mut stmt = stmts[i]; // copy match stmt.data { js_ast::StmtData::SImport(mut import_ptr) => { - // PORT NOTE: Zig did `var st = import_ptr.*; defer import_ptr.* = st;` + // Zig did `var st = import_ptr.*; defer import_ptr.* = st;` // (copy + unconditional write-back). Equivalent to mutating in place. let st: &mut S::Import = &mut *import_ptr; let import_record_index = st.import_record_index; - // PORT NOTE: reshaped for borrowck — Zig held `record: *ImportRecord` + // reshaped for borrowck — Zig held `record: *ImportRecord` // for the whole arm; Rust can't keep a long-lived &mut alongside // other `p.*` borrows. We take a raw pointer once and unsafe-deref // at each use site (no operation below grows `p.import_records`, so @@ -83,7 +82,7 @@ impl<'a> ImportScanner<'a> { } if record!().path.namespace == crate::Macro::NAMESPACE { - // PORT NOTE: `Path::isMacro()` inlined (no Rust method yet). + // `Path::isMacro()` inlined (no Rust method yet). record!().flags.insert(import_record::Flags::IS_UNUSED); record!().path.is_disabled = true; continue; @@ -240,7 +239,7 @@ impl<'a> ImportScanner<'a> { // Remove the symbol if it's never used outside a dead code region if symbol.use_count_estimate != 0 { - // PORT NOTE: ClauseItem isn't `Copy`; bitwise-move it + // ClauseItem isn't `Copy`; bitwise-move it // (its fields are all POD; arena-owned, never dropped). if items_end != idx { // SAFETY: items_end < idx < len; non-overlapping. @@ -377,7 +376,7 @@ impl<'a> ImportScanner<'a> { .insert(import_record::Flags::CONTAINS_DEFAULT_ALIAS); } - // PORT NOTE: borrow (not clone) — disjoint-field borrow vs. the + // borrow (not clone) — disjoint-field borrow vs. the // `p.symbols` / `p.named_imports` / `p.declared_symbols` writes // below. `None` stands in for `ImportItemForNamespaceMap.init()`. let existing_items: Option<&ImportItemForNamespaceMap> = @@ -702,14 +701,14 @@ impl<'a> ImportScanner<'a> { } } js_ast::StmtData::SExportDefault(mut st) => { - // PORT NOTE: Zig used `defer` to record the export after the body; + // Zig used `defer` to record the export after the body; // capture default_name now and run the record after the body below. let deferred_default_name = st.default_name; // Rewrite this export to be: // exports.default = // But only if it's anonymous - // PORT NOTE: comptime `P != bun.bundle_v2.AstBuilder` check elided — + // comptime `P != bun.bundle_v2.AstBuilder` check elided — // this monomorphization is the parser `P` only (see fn-level TODO). // blocked_on: P::module_exports gated (reconciler-6 re-gate in P.rs) if !HOT_MODULE_RELOADING_TRANSFORMATIONS && will_transform_to_common_js { diff --git a/src/js_parser/scan/scan_side_effects.rs b/src/js_parser/scan/scan_side_effects.rs index 1dcd76b4d1eb..cf5ee369f6cf 100644 --- a/src/js_parser/scan/scan_side_effects.rs +++ b/src/js_parser/scan/scan_side_effects.rs @@ -6,10 +6,6 @@ use bun_ast::symbol; use bun_ast::{self, Binding, E, Expr, ExprData, G, Op, Stmt, StmtData, StoreRef}; use bun_collections::VecExt; -// PORT NOTE: round-E un-gate. SideEffects in Zig is an enum with associated fns that -// take `p: anytype`. Round-E converts the unbounded `

` generic to concrete -// `P<'a, TS, SCAN>`. Method bodies gated; the `Result` type and enum surface are real. - #[repr(u8)] // Zig: enum(u1) — Rust has no u1 repr; u8 is the smallest #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] pub enum SideEffects { @@ -151,7 +147,7 @@ impl SideEffects { p.report_stack_overflow(expr.loc); return Some(expr); } - // PORT NOTE: `Expr`/`ExprData`/`StoreRef<_>` are all `Copy`. We match on + // `Expr`/`ExprData`/`StoreRef<_>` are all `Copy`. We match on // `expr.data` *by value* so `expr` itself is never borrowed across a // recursive `simplify_unused_expr(p, ..)` call. Mutations to boxed // payloads write through `StoreRef::DerefMut` into the arena, so they @@ -403,9 +399,9 @@ impl SideEffects { } } - // PORT NOTE: G::Property is not Copy (Vec ts_decorators - // field). The Zig spec does an in-place struct copy; here we - // swap so the kept property lands at `end` without cloning. + // G::Property is not Copy (Vec ts_decorators field). The Zig + // original does an in-place struct copy; here we swap so the + // kept property lands at `end` without cloning. e_object.properties.slice_mut().swap(end, j); end += 1; } @@ -536,7 +532,7 @@ impl SideEffects { Op::Code::BinStrictEq | Op::Code::BinStrictNe | Op::Code::BinComma )); - // PORT NOTE: Zig threads `p.binary_expression_simplify_stack` (a reusable + // The Zig original threads `p.binary_expression_simplify_stack` (a reusable // ArrayList on `P`) to avoid per-call allocation. The Rust `P` field is // currently `ListManaged<'a, ()>` (placeholder element type — see P.rs:537), // so until that's reshaped to `BinaryExpressionSimplifyVisitor` we use a diff --git a/src/js_parser/scan/scan_symbols.rs b/src/js_parser/scan/scan_symbols.rs index 9bc6e55fea21..32791b22a546 100644 --- a/src/js_parser/scan/scan_symbols.rs +++ b/src/js_parser/scan/scan_symbols.rs @@ -4,14 +4,6 @@ use crate::parser::FindSymbolResult; use bun_ast as js_ast; use bun_ast::{Ref, Scope}; -// PORT NOTE: Zig's `fn Symbols(comptime ts, comptime jsx, comptime scan_only) type { return struct { ... } }` -// is the file-split mixin pattern: a fieldless namespace whose associated fns all take `*P` as the -// first arg, where `P = js_parser.NewParser_(ts, jsx, scan_only)`. In Rust this is a plain -// `impl P<...> { }` block — multiple impl blocks on the same type across files in one -// crate are allowed. -// -// adt_const_params: lowered `const JSX: JSXTransformType` → `J: JsxT` (sealed trait + ZST). - impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_ONLY> { pub fn find_symbol( &mut self, @@ -79,8 +71,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // If this is an identifier from a sibling TypeScript namespace, then we're // going to have to generate a property access instead of a simple reference. // Lazily-generate an identifier that represents this property access. - // PORT NOTE: reshaped for borrowck — Zig's getOrPut returns key/value - // pointers while we also call self.new_symbol (&mut self). Split into + // The Zig original used getOrPut, which returns key/value pointers + // while we also call self.new_symbol (&mut self). Split into // get-then-insert so the &mut self borrow does not overlap the map borrow. if let Some(existing) = ts.property_accesses.get(name) { break 'brk *existing; @@ -126,8 +118,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O break 'brk existing.ref_; } - // PORT NOTE: reshaped for borrowck — gpe borrows self.module_scope while - // self.new_symbol needs &mut self. Drop gpe, allocate, then re-insert. + // gpe borrows self.module_scope while self.new_symbol needs &mut self. + // Drop gpe, allocate, then re-insert. let new_ref = self .new_symbol(js_ast::symbol::Kind::Unbound, name) .expect("unreachable"); diff --git a/src/js_parser/typescript.rs b/src/js_parser/typescript.rs index 66ce8c582ad9..f49fb147a219 100644 --- a/src/js_parser/typescript.rs +++ b/src/js_parser/typescript.rs @@ -43,10 +43,9 @@ impl<'a, const TS: bool, const SCAN: bool> P<'a, TS, SCAN> { } // end impl P (can_follow_type_arguments_in_expression) impl<'a, const TS: bool, const SCAN: bool> P<'a, TS, SCAN> { - // TODO(port): narrow error set — only `lexer.next()` is fallible here. pub fn is_ts_arrow_fn_jsx(&mut self) -> Result { let p = self; - // PORT NOTE: Zig `const old = p.lexer` (value copy). Rust Lexer holds `&mut Log` + // Zig `const old = p.lexer` (value copy). Rust Lexer holds `&mut Log` // so cannot Clone; use the LexerSnapshot POD via `snapshot()`/`restore()`. let old_lexer = p.lexer.snapshot(); @@ -143,7 +142,7 @@ impl<'a, const TS: bool, const SCAN: bool> P<'a, TS, SCAN> { fn look_ahead_next_token_is_open_paren_or_less_than_or_dot(&mut self) -> bool { let p = self; - // PORT NOTE: Zig value-copied the Lexer; use snapshot()/restore() (see is_ts_arrow_fn_jsx). + // Zig value-copied the Lexer; use snapshot()/restore() (see is_ts_arrow_fn_jsx). let old_lexer = p.lexer.snapshot(); let old_log_disabled = p.lexer.is_log_disabled; p.lexer.is_log_disabled = true; @@ -152,7 +151,7 @@ impl<'a, const TS: bool, const SCAN: bool> P<'a, TS, SCAN> { let result = matches!(p.lexer.token, T::TOpenParen | T::TLessThan | T::TDot); - // PORT NOTE: Zig used `defer` for restore; reshaped to linear since there is + // Zig used `defer` for restore; reshaped to linear since there is // no early return between the defer and end of scope. p.lexer.restore(&old_lexer); p.lexer.is_log_disabled = old_log_disabled; @@ -162,7 +161,7 @@ impl<'a, const TS: bool, const SCAN: bool> P<'a, TS, SCAN> { // This function is taken from the official TypeScript compiler source code: // https://github.com/microsoft/TypeScript/blob/master/src/compiler/parser.ts - // PORT NOTE: renamed `ts_is_identifier` to avoid clash with lexer/P helpers of the same name. + // renamed `ts_is_identifier` to avoid clash with lexer/P helpers of the same name. fn ts_is_identifier(&self) -> bool { use crate::parser::AwaitOrYield::AllowIdent; let p = self; @@ -393,7 +392,7 @@ pub enum SkipTypeOptions { DisallowConditionalTypes, } -// PORT NOTE: Zig nested `Bitset` and `empty` inside `SkipTypeOptions`. Rust +// Zig nested `Bitset` and `empty` inside `SkipTypeOptions`. Rust // inherent associated types (`impl Foo { type Bar = ...; }`) are unstable // (`inherent_associated_types`), so the alias and empty constant are hoisted // to module scope. diff --git a/src/js_parser/visit/mod.rs b/src/js_parser/visit/mod.rs index 66a3d50cda18..13eccbb7d0c3 100644 --- a/src/js_parser/visit/mod.rs +++ b/src/js_parser/visit/mod.rs @@ -27,7 +27,7 @@ use bun_ast::{ Stmt, StmtData, Symbol, }; use bun_collections::VecExt; -// PORT NOTE: `parser::SideEffects` is a stub enum without the assoc fns; the real +// `parser::SideEffects` is a stub enum without the assoc fns; the real // `should_keep_stmt_in_dead_control_flow` lives on `ast::side_effects::SideEffects`. use crate::scan::scan_side_effects::SideEffects; use bun_ast::StrictModeKind; @@ -95,7 +95,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O "only_scan_imports_and_do_not_visit must not run this." ); - // PORT NOTE: FnOnlyDataVisit holds `Option<&'a Cell>`; save/restore via + // FnOnlyDataVisit holds `Option<&'a Cell>`; save/restore via // `take` so the old value is moved out before we overwrite the field. let old_fn_or_arrow_data = self.fn_or_arrow_data_visit; let old_fn_only_data = core::mem::take(&mut self.fn_only_data_visit); @@ -152,7 +152,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O .expect("unreachable"); if self.options.features.react_fast_refresh { - // PORT NOTE: react_refresh.hook_ctx_storage is `Option>>` + // react_refresh.hook_ctx_storage is `Option>>` // pointing at a stack-local on the visitStmt caller frame (Zig: `*?Hook`). // `ReactRefresh::hook_ctx_mut` centralises the raw-pointer deref and returns a // borrow detached from `self` (the storage is on the caller's stack frame), so @@ -221,7 +221,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O self.visit_ts_decorators(&mut arg.ts_decorators); } - // PORT NOTE: reborrow per-iter (Zig passes the same pointer each time). + // reborrow per-iter (Zig passes the same pointer each time). let dup: Option<&mut StringVoidMap> = duplicate_args_check.as_deref_mut(); self.visit_binding(arg.binding, dup); if let Some(default) = arg.default.as_mut() { @@ -230,7 +230,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } } - // PORT NOTE: Zig returned the list by value (`ExprNodeList` is a fat ptr there). + // Zig returned the list by value (`ExprNodeList` is a fat ptr there). // `Vec` is not `Copy` in Rust; mutate in place instead. pub fn visit_ts_decorators(&mut self, decs: &mut ExprNodeList) { for dec in decs.slice_mut() { @@ -244,7 +244,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O was_const: bool, ) -> usize { let mut j: usize = 0; - // PORT NOTE: reshaped for borrowck — Zig aliased `out_decls = decls` and + // reshaped for borrowck — Zig aliased `out_decls = decls` and // iterated `decls` while writing through `out_decls[j]`. We iterate by index. let len = decls.len(); let mut i: usize = 0; @@ -262,7 +262,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let prev_require_to_convert_count = self.imports_to_convert_from_require.len(); let prev_macro_call_count = self.macro_call_count; let orig_dead = self.is_control_flow_dead; - // PORT NOTE: `replacement` is a `BackRef` (Zig `?*ReplaceableExport`) so the + // `replacement` is a `BackRef` (Zig `?*ReplaceableExport`) so the // borrow of `self.options` does not survive across `visit_expr_in_out(&mut self)`. // `BackRef` invariant: `self.options.features.replace_exports` is never mutated // during the visit pass, so the entry strictly outlives this loop body. @@ -323,7 +323,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O if let Some(last_hook) = self.react_refresh.last_hook_seen { if let Some(call) = decl.value.unwrap().data.e_call() { if core::ptr::eq(last_hook, &raw const *call) { - // PORT NOTE: disjoint field borrows — `react_refresh.hook_ctx_storage` + // disjoint field borrows — `react_refresh.hook_ctx_storage` // points at caller-frame stack storage (detached lifetime via // `hook_ctx_mut`), and `symbols` is an independent field of `P`. let hasher = &mut self @@ -710,7 +710,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } } - // PORT NOTE: P::stmts_to_single_stmt is ``-gated (P.rs:6267, blocked on + // P::stmts_to_single_stmt is ``-gated (P.rs:6267, blocked on // S::Block Default). Inline a local copy until that un-gates. fn stmts_to_single_stmt_(&mut self, loc: bun_ast::Loc, stmts: &'a mut [Stmt]) -> Stmt { if stmts.is_empty() { @@ -760,7 +760,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O b.stmts = bun_ast::StoreSlice::new_mut(items); } if self.options.features.minify_syntax { - // PORT NOTE: reshaped for borrowck — `stmts` was consumed above; in Zig + // reshaped for borrowck — `stmts` was consumed above; in Zig // `stmts.items` aliases the slice now stored in `s_block.stmts`. new_stmt = self.stmts_to_single_stmt_(stmt.loc, items); } @@ -819,7 +819,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O self.enclosing_class_keyword = class.class_keyword; self.vis_scope() .recursive_set_strict_mode(StrictModeKind::ImplicitStrictModeClass); - // PORT NOTE: `FnOnlyDataVisit::class_name_ref` is `Option<&'a Cell>`, so the + // `FnOnlyDataVisit::class_name_ref` is `Option<&'a Cell>`, so the // shadow ref must outlive the parser borrow. Allocate it in the bump arena (Zig kept // it on the stack and passed `&shadow_ref` — Rust's `'a` bound on the field forbids // that). `Cell` lets us hand out a shared `&'a Cell` to nested frames while @@ -968,7 +968,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O (value.data, key.data) { if e_str.eql_comptime(b"constructor") { - // PORT NOTE: Zig keeps a `*E.Function` into property.value's + // Zig keeps a `*E.Function` into property.value's // arena slot, then re-reads it after visit_expr overwrites // the value below. `StoreRef` carries the same arena pointer. constructor_function_ = Some(e_func); @@ -1091,7 +1091,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O old_props_len + to_add, self.arena, ); - // PORT NOTE: Zig `fromOwnedSlice` adopts the existing buffer in-place. + // Zig `fromOwnedSlice` adopts the existing buffer in-place. // Rust BumpVec can't adopt a foreign arena slice, so move each element // out by `ptr::read` (G::Property has no Drop; old slice becomes dead // arena bytes). @@ -1105,7 +1105,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let args_len = func_args.len(); for arg_idx in 0..args_len { - // PORT NOTE: reshaped for borrowck — copy the scalars we need + // reshaped for borrowck — copy the scalars we need // (id_ref, bind_loc) out of the arg before calling `&mut self` // helpers, so no live `&Arg` overlaps `self.new_expr`/`declare_symbol`. let (id_ref, bind_loc) = { @@ -1226,7 +1226,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Save the current control-flow liveness. This represents if we are // currently inside an "if (false) { ... }" block. let old_is_control_flow_dead = p.is_control_flow_dead; - // PORT NOTE: Zig `defer` — manually restored at block end. The error + // Zig `defer` — manually restored at block end. The error // path (`?`) skips restore; acceptable because callers `.expect()` or // propagate fatally (parse abort) — no resumption after error. @@ -1249,10 +1249,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // save/restore matches the Zig slice-copy directly. let old_scopes_in_order = p.scope_order_to_visit; - // `Loc` lacks `Hash` (logger crate) so `ArrayHashMap::get` - // is unavailable; linear scan over `keys()`/`values()` - // (enums are rare). - // TODO(port): switch to `.get(&stmt.loc)` once `Loc: Hash`. + // Zig: `p.scopes_in_order_for_enum.get(stmt.loc).?` p.scope_order_to_visit = scopes_for_enum_at(&p.scopes_in_order_for_enum, stmt.loc); @@ -1266,21 +1263,16 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } } - if p.current_scope == p.module_scope { - // TODO(port): `MacroState::prepend_stmts` is `&'a mut Vec` but - // `before` is a `BumpVec<'a, Stmt>` — type-shape divergence in - // parser.rs. Zig: `p.macro.prepend_stmts = &before;`. The macro - // expansion path is gated; this BACKREF is restored when MacroState - // switches to `BumpVec` / `NonNull`. - let _ = &mut before; - } + // Zig set `p.macro.prepend_stmts = &before;` here, but the field is + // write-only in both ports (nothing ever reads it), so the backref + // is intentionally not wired. // visit all statements first let mut visited: ListManaged<'a, Stmt> = ListManaged::with_capacity_in(stmts.len(), p.arena); let prev_nearest_stmt_list = p.nearest_stmt_list; - // PORT NOTE: BACKREF — `before` outlives this block; raw NonNull avoids + // BACKREF — `before` outlives this block; raw NonNull avoids // the `&'a mut` borrow conflict. Derive via `addr_of_mut!` (no intermediate // `&mut`) so the pointer shares the local's base tag and survives the // direct `&mut before` reborrows in the loop below (Stacked Borrows). @@ -1474,7 +1466,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O visited_count = end; } - // PORT NOTE: reshaped — Zig `resize`+slice-walk; `Stmt: Copy` so the + // reshaped — Zig `resize`+slice-walk; `Stmt: Copy` so the // simpler clear+extend is equivalent and avoids a `Stmt::default()` // filler value. stmts.clear(); @@ -1492,7 +1484,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O if kind != StmtsKind::SwitchStmt && p.should_lower_using_declarations(stmts.as_slice()) { let mut ctx = LowerUsingDeclarationsContext::init(p)?; ctx.scan_stmts(p, stmts.as_mut_slice()); - // PORT NOTE: Zig's `stmts.* = ctx.finalize(p, stmts.items, ...)` + // Zig's `stmts.* = ctx.finalize(p, stmts.items, ...)` // overwrote the ArrayList struct without freeing the old buffer. // `finalize` stores a sub-slice of that buffer as the lowered // S.Try `body`, so the buffer must outlive the assignment. Leak @@ -1556,7 +1548,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } } } - // PORT NOTE: `Decl` is field-wise `Copy` but lacks the + // `Decl` is field-wise `Copy` but lacks the // derive; `swap` compacts in place (idx >= end always). decls.swap(end, idx); end += 1; @@ -1630,7 +1622,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // by now since they are scoped to this block which we just finished // visiting. let prev_idx = output.len() - 1; - // PORT NOTE: reshaped for borrowck — Zig held `&mut output[..]` + // reshaped for borrowck — Zig held `&mut output[..]` // across `p.substitute...` (which borrows `&mut p`). We read the // `StoreRef` (Copy) first, then re-borrow `output` only when // truncating. @@ -1711,7 +1703,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let prev_stmt = &mut output[prev_idx]; if let StmtData::SLocal(mut prev_local) = prev_stmt.data { if local.can_merge_with(&prev_local) { - // PORT NOTE: `Vec::append_slice` requires `T: Clone` + // `Vec::append_slice` requires `T: Clone` // but `G::Decl` lacks the derive (its fields are all // `Copy`). Per-element bitwise copy matches Zig // `appendSlice` semantics. @@ -1763,7 +1755,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O && prev_local.kind == LocalKind::KVar { if let ExprData::EIdentifier(left_id) = bin_assign.left.data { - // PORT NOTE: `prev_local` is a `StoreRef` (Copy) so + // `prev_local` is a `StoreRef` (Copy) so // re-slicing here writes through to the arena slot. let mut prev_local = prev_local; let decl = &mut prev_local.decls.slice_mut()[0]; @@ -1882,23 +1874,14 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } } -/// `p.scopes_in_order_for_enum.get(loc)` workaround: `bun_ast::Loc` lacks -/// `Hash`, so `ArrayHashMap::get` (gated on `ArrayHashContext`) is -/// unavailable. Linear scan over the (small) parallel key/value slices. -// TODO(port): replace with `.get(&loc).unwrap()` once `Loc: Hash` lands in -// bun_logger (cross-crate; out of scope here). +/// Zig: `p.scopes_in_order_for_enum.get(loc).?` fn scopes_for_enum_at<'a>( map: &bun_collections::ArrayHashMap]>, loc: bun_ast::Loc, ) -> &'a [ScopeOrder<'a>] { - let keys = map.keys(); - let values = map.values(); - for i in 0..keys.len() { - if keys[i] == loc { - return values[i]; - } - } - unreachable!("scopes_in_order_for_enum miss for enum stmt loc"); + map.get(&loc) + .copied() + .expect("scopes_in_order_for_enum miss for enum stmt loc") } pub(crate) fn fn_body_contains_use_strict(body: &[Stmt]) -> Option { diff --git a/src/js_parser/visit/visit_binary.rs b/src/js_parser/visit/visit_binary.rs index 7591c25a41c6..dd2485c994d3 100644 --- a/src/js_parser/visit/visit_binary.rs +++ b/src/js_parser/visit/visit_binary.rs @@ -11,7 +11,7 @@ use bun_ast::{ expr::{Equality, LooseEql, StrictEql}, }; -// PORT NOTE: The Zig `CreateBinaryExpressionVisitor(comptime ts, comptime jsx, comptime scan_only) type` +// The Zig `CreateBinaryExpressionVisitor(comptime ts, comptime jsx, comptime scan_only) type` // returned an anonymous namespace struct whose only public item was `BinaryExpressionVisitor`. // Round-C lowered `const JSX: JSXTransformType` → `J: JsxT`, so `BinaryExpressionVisitor` carries // the parser generics directly. @@ -80,7 +80,7 @@ fn try_optimize_typeof_undefined<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bo )) } -// PORT NOTE: `Expr.Data.eql(left, right, p, .{loose,strict})` — thin adapter +// `Expr.Data.eql(left, right, p, .{loose,strict})` — thin adapter // from the `const STRICT: bool` shape used at the four call sites below to the // canonical `ExprData::eql` (Expr.rs). Kept as a free fn so // the call sites don't each repeat the `LooseEql`/`StrictEql` type-select. @@ -104,7 +104,7 @@ pub struct BinaryExpressionVisitor { /// round-trip to forge an `'arena` borrow. pub e: StoreRef, pub loc: bun_ast::Loc, - // PORT NOTE: Zig field name `in` is a Rust keyword; renamed to `in_`. + // Zig field name `in` is a Rust keyword; renamed to `in_`. pub in_: ExprIn, /// Input for visiting the left child @@ -514,17 +514,13 @@ impl BinaryExpressionVisitor { if p.should_fold_typescript_constant_expressions { if let Some(vals) = Expr::extract_numeric_values(&e_.left.data, &e_.right.data) { - // TODO(port): move to _sys - unsafe extern "C" { - // libc fmod is pure (value-type args, no errno, no pointers) → no caller preconditions. - safe fn fmod(x: f64, y: f64) -> f64; - } return p.new_expr( - // Use libc fmod here to be consistent with what JavaScriptCore does + // Rust `%` on f64 has libc fmod semantics (LLVM frem), + // which matches what JavaScriptCore does: // https://github.com/oven-sh/WebKit/blob/7a0b13626e5db69aa5a32d037431d381df5dfb61/Source/JavaScriptCore/runtime/MathCommon.cpp#L574-L597 - // PORT NOTE: Zig had a non-native fallback to std.math.mod; Rust targets are always native. + // Zig had a non-native fallback to std.math.mod; Rust targets are always native. E::Number { - value: fmod(vals[0], vals[1]), + value: vals[0] % vals[1], }, v.loc, ); @@ -738,7 +734,7 @@ impl BinaryExpressionVisitor { Op::Code::BinAssign => { // Optionally preserve the name if let ExprData::EIdentifier(ident) = e_.left.data { - // PORT NOTE: reshaped for borrowck — copy the `StoreStr` out of + // reshaped for borrowck — copy the `StoreStr` out of // `p.symbols` before taking `&mut self` for `maybe_keep_expr_symbol_name`. let name = p.symbols[ident.ref_.inner_index() as usize].original_name; e_.right = p.maybe_keep_expr_symbol_name( diff --git a/src/js_parser/visit/visit_expr.rs b/src/js_parser/visit/visit_expr.rs index 90de364837d6..9dd2df752e2e 100644 --- a/src/js_parser/visit/visit_expr.rs +++ b/src/js_parser/visit/visit_expr.rs @@ -367,7 +367,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O break 'tagger _tag; } if p.options.jsx.runtime == options::JSX::Runtime::Classic { - // PORT NOTE: `jsx_strings_to_member_expression` wants `&[&'a [u8]]`. + // `jsx_strings_to_member_expression` wants `&[&'a [u8]]`. // In Zig, `options.jsx.fragment: []const string` borrows from the // long-lived `transpiler.options.jsx`, so the strings outlive the // AST. Here, `options.jsx.fragment: Box<[Box<[u8]>]>` is OWNED by @@ -424,7 +424,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let num_props = e_.properties.len_u32(); if num_props > 0 { - // PORT NOTE: Zig duped the property slice into a fresh arena allocation + // Zig duped the property slice into a fresh arena allocation // before wrapping in E.Object. PropertyList = Vec here is // already arena-backed and the JSX node is consumed; reuse in place. // PERF(port): was arena alloc + bun.copy — profile if it shows up on a hot path @@ -453,7 +453,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } let target: Expr = if runtime == options::JSX::Runtime::Classic { - // PORT NOTE: see fragment note above — `options.jsx.factory` is + // see fragment note above — `options.jsx.factory` is // owned by `P` and freed when the parser drops; dupe each part // into the arena so the symbol/E::Dot names outlive the printer. // Build the parts slice in the AST arena (no global-heap `Vec`). @@ -510,7 +510,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O None }; - // PORT NOTE: Zig reassigns `props` (a `*Vec(G.Property)`) to point inside + // Zig reassigns `props` (a `*Vec(G.Property)`) to point inside // a spread object's properties via raw arena pointer. Track as a // `StoreRef` (safe `Deref`/`DerefMut`) so the spread-collapse walk // and the `push`/`take` calls below stay in safe code. @@ -524,7 +524,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O { let mut last_child: u32 = 0; - // PORT NOTE: Zig wrote `e_.children.ptr[last_child] = p.visitExpr(child)` + // Zig wrote `e_.children.ptr[last_child] = p.visitExpr(child)` // while iterating a slice over the same buffer. Iterate by index to avoid // borrowck on `e_.children`. for i in 0..children_count { @@ -561,7 +561,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O { break; } - // PORT NOTE: reshaped for borrowck — Zig reassigns `props` to point + // reshaped for borrowck — Zig reassigns `props` to point // inside the spread object's properties. Compute the next handle in // a block so the `DerefMut` borrow of `props_handle` ends before // reassignment. @@ -823,7 +823,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // iteration on the heap instead of recursion on the call stack to avoid // stack overflow for deeply-nested ASTs. // - // PORT NOTE: Zig stores `*E.Binary` (arena ptr). `BinaryExpressionVisitor.e` + // Zig stores `*E.Binary` (arena ptr). `BinaryExpressionVisitor.e` // is the `StoreRef` arena handle directly — `Copy` + safe // `Deref`/`DerefMut`, so no raw-pointer detach is needed here. let mut v: BinaryExpressionVisitor = BinaryExpressionVisitor { @@ -1175,7 +1175,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O ); } - // PORT NOTE: `e_` is `StoreRef` — mutations above wrote through + // `e_` is `StoreRef` — mutations above wrote through // DerefMut into the same arena slot `expr.data` already points at. Zig's // `p.newExpr(e_, loc)` re-wraps the same pointer; here `*e` is already that. } @@ -1751,7 +1751,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O && matches!(key.data, Data::EString(..)) { let key_str = key.data.e_string().expect("infallible: variant checked"); - // PORT NOTE: Zig `string(arena)` transcodes UTF-16; while + // Zig `string(arena)` transcodes UTF-16; while // E.rs has duplicate impls (E0034), reach the bytes directly // — class-name keys are parser-produced (UTF-8, no rope). p.decorator_class_name = if !key_str.is_utf16 { @@ -1830,7 +1830,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // // const binding = await import(`./${process.platform}-${process.arch}.node`); // - // PORT NOTE: Zig `defer` restores at scope exit; restored manually before each return. + // Zig `defer` restores at scope exit; restored manually before each return. let prev_should_fold_typescript_constant_expressions = true; p.should_fold_typescript_constant_expressions = true; @@ -1893,7 +1893,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O ); // Copy the call side effect flag over if this is a known target - // PORT NOTE: copy the small inline payloads out first so the `match &e_.target.data` + // copy the small inline payloads out first so the `match &e_.target.data` // borrow doesn't overlap the `e_.can_be_unwrapped_if_unused = …` write below. match e_.target.data { Data::EIdentifier(ident) => { @@ -1928,7 +1928,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O p.record_usage(p.exports_ref); } - // PORT NOTE: `Scope.parent: ?*Scope` in Zig is `Option>` here; + // `Scope.parent: ?*Scope` in Zig is `Option>` here; // walk via the safe arena back-pointer. let mut scope_iter: Option> = Some(p.current_scope); @@ -1970,7 +1970,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O { let old_ce = p.options.ignore_dce_annotations; - // PORT NOTE: Zig `defer` restores at scope exit; do it manually below. + // Zig `defer` restores at scope exit; do it manually below. let old_should_fold_typescript_constant_expressions = p.should_fold_typescript_constant_expressions; let old_is_control_flow_dead = p.is_control_flow_dead; @@ -2265,7 +2265,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // When we see a hook call, we need to hash it, and then mark a flag so that if // it is assigned to a variable, that variable also get's hashed. // - // PORT NOTE: `Runtime::Features.server_components` is a `bool` stub; the + // `Runtime::Features.server_components` is a `bool` stub; the // full Zig type is `enum { off, client, server }` with `.isServerSide()`. Treat // `true` as server-side until the enum lands. if p.options.features.react_fast_refresh @@ -2475,7 +2475,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Zig: `std.mem.toBytes(...)` then `bytesToValue(...)` to save/restore. In Rust the struct // is `Copy`/`Clone`, so just copy it. - // PORT NOTE: reshaped — toBytes/bytesToValue → plain copy. + // reshaped — toBytes/bytesToValue → plain copy. let old_fn_or_arrow_data = p.fn_or_arrow_data_visit; p.fn_or_arrow_data_visit = FnOrArrowDataVisit { is_arrow: true, @@ -2515,10 +2515,9 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let prev_hook_ctx = p.react_refresh.hook_ctx_storage; p.react_refresh.hook_ctx_storage = Some(core::ptr::NonNull::from(&mut react_hook_data)); - // TODO(port): Zig `ListManaged(Stmt).fromOwnedSlice(p.arena, dupe)` takes ownership of - // the arena slice without copying. bumpalo Vec cannot adopt an existing slice; a custom - // arena Vec that can would avoid this copy. Left as a copy with PERF note. - // PERF(port): was fromOwnedSlice (no copy) — profile if it shows up on a hot path + // Zig `ListManaged(Stmt).fromOwnedSlice(p.arena, dupe)` adopted the arena slice + // without copying; bumpalo Vec cannot adopt an existing slice, so this copies. + // PERF: profile if it shows up on a hot path (react-refresh only). let mut stmts_list = bun_alloc::vec_from_iter_in(dupe.iter().copied(), p.arena); let mut temp_opts = PrependTempRefsOpts { kind: crate::parser::StmtsKind::FnBody, diff --git a/src/js_parser/visit/visit_stmt.rs b/src/js_parser/visit/visit_stmt.rs index fc75eed6996d..836ec924e909 100644 --- a/src/js_parser/visit/visit_stmt.rs +++ b/src/js_parser/visit/visit_stmt.rs @@ -66,7 +66,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O p.cur_scope().is_after_const_local_prefix = true; // Zig: `switch (@as(Stmt.Tag, stmt.data))` with `inline else` reflection over @tagName. - // PORT NOTE: reshaped for borrowck — `Stmt::Data` is `Copy` (`StoreRef` is a thin + // reshaped for borrowck — `Stmt::Data` is `Copy` (`StoreRef` is a thin // `NonNull`); take a copy of the enum so the StoreRef payload can be DerefMut'd // without aliasing the `&mut Stmt` we also pass through. The deref'd `&mut S::*` // points into the arena, not into `*stmt`. @@ -165,7 +165,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O ) -> Result<(), Error> { // "module.exports = value" // Zig: p.@"module.exports"(stmt.loc) — mapped to `module_exports` - // PORT NOTE: Zig evaluates lhs before rhs at the call site; preserve that order + // Zig evaluates lhs before rhs at the call site; preserve that order // (`module_exports` builds via `new_expr`, `visit_expr` mutates parser state). let lhs = p.module_exports(stmt.loc); p.visit_expr(&mut data.value); @@ -191,7 +191,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let symbol = p.find_symbol(items[i].alias_loc, name)?; let ref_ = symbol.r#ref; - // PORT NOTE: reshaped for borrowck — get_ptr borrows options; clone the + // reshaped for borrowck — get_ptr borrows options; clone the // small enum payload so `inject_replacement_export(&mut self, ...)` can run. if let Some(entry) = p.options.features.replace_exports.get_ptr(name).cloned() { if !entry.is_replace() { @@ -315,11 +315,9 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Truncate `data.items` to `j` by reslicing the arena view. data.items.truncate(j); - // TODO(port): dead branch in Zig — `data.items.len = j;` runs first, so - // `j == 0 and data.items.len > 0` is always false. Mirrored bug-for-bug. - if j == 0 && data.items.len() > 0 { - return Ok(()); - } + // Zig followed this with `if (j == 0 and data.items.len > 0) return;`, + // but `data.items.len = j;` runs first so the condition is always + // false — the dead branch is intentionally not ported. } else { // This is a re-export and the symbols created here are used to reference for item in items.iter_mut() { @@ -383,7 +381,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O data: &mut S::ExportDefault, ) -> Result<(), Error> { // Zig: defer { if (data.default_name.ref) |ref| p.recordDeclaredSymbol(ref) catch unreachable; } - // PORT NOTE: scopeguard can't borrow `p` across the body; restructured to a tail + // scopeguard can't borrow `p` across the body; restructured to a tail // closure invoked at every return site below. macro_rules! record_on_exit { () => { @@ -586,7 +584,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } js_ast::StmtOrExpr::Stmt(s2) => { - // PORT NOTE: reshaped for borrowck — `s2` borrows from `data.value`; copy + // reshaped for borrowck — `s2` borrows from `data.value`; copy // `s2.loc`/`s2.data` (both Copy) so we can mutate `data.value` below. let s2_loc = s2.loc; let s2_data = s2.data; @@ -874,8 +872,9 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } if p.options.features.server_components.wraps_exports() { - // TODO(port): Zig spec mutates `data.value` *after* pushing `stmt` — - // mirrored bug-for-bug. The class expr wrap likely belongs before push. + // Zig mutates `data.value` *after* pushing `stmt`; mirrored + // deliberately. The pushed stmt still observes the write because + // `data` is an arena backref shared with the pushed copy. let class_expr = p.new_expr(core::mem::take(&mut class.class), stmt.loc); data.value = js_ast::StmtOrExpr::Expr( @@ -1047,7 +1046,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } if p.current_scope == p.module_scope { - // PORT NOTE: defer-vs-drop-scope — restore hook_ctx_storage/is_control_flow_dead + // defer-vs-drop-scope — restore hook_ctx_storage/is_control_flow_dead // before propagating Err so the stack-local `react_hook_data` ptr is never left in // p.react_refresh on the OOM path (Zig defer covers all exits). rr = p.handle_react_refresh_register( @@ -1192,7 +1191,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O .expect("infallible: in namespace"), ); // TODO: is it necessary to lowerAssign? why does esbuild do it _most_ of the time? - // PORT NOTE: ToExprWrapper is Copy; pass by value to avoid borrowing `*p` + // ToExprWrapper is Copy; pass by value to avoid borrowing `*p` // across `p.s(...)`. The `*mut P` ctx is derived from the live `&mut Self` // here so its provenance is a child of the active Unique borrow. let wrapper = p.to_expr_wrapper_namespace; @@ -1427,7 +1426,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O p.visit_expr(&mut data.value); // Zig: defer p.stmt_expr_value = .{ .e_missing = .{} }; - // PORT NOTE: restructured — restored at every return below. + // restructured — restored at every return below. macro_rules! restore_stmt_expr { () => { p.stmt_expr_value = js_ast::ExprData::EMissing(E::Missing {}); @@ -1453,7 +1452,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O if to_convert != u32::MAX { p.commonjs_named_exports_needs_conversion = u32::MAX; 'convert: { - // PORT NOTE: reshaped for borrowck — copy StoreRef so DerefMut + // reshaped for borrowck — copy StoreRef so DerefMut // points into the arena, freeing `&mut data.value`. let js_ast::ExprData::EBinary(mut bin_ref) = data.value.data else { break 'convert; @@ -1537,7 +1536,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } } } else if p.commonjs_replacement_stmts.len() > 0 { - // PORT NOTE: Zig directly swaps backing storage; commonjs_replacement_stmts + // Zig directly swaps backing storage; commonjs_replacement_stmts // is `StmtNodeList = StoreSlice` here, so copy then clear. let repl: &[Stmt] = p.commonjs_replacement_stmts.slice(); if stmts.is_empty() { diff --git a/src/js_parser_jsc/Macro.rs b/src/js_parser_jsc/Macro.rs index 4bd3672d9e50..0b1452f522b3 100644 --- a/src/js_parser_jsc/Macro.rs +++ b/src/js_parser_jsc/Macro.rs @@ -17,7 +17,7 @@ use bun_resolver::package_json::{ MacroImportReplacementMap as MacroRemapEntry, MacroMap as MacroRemap, }; -// PORT NOTE: Zig spec aliases `const js = bun.jsc.C;` (Macro.zig:642) — the +// Zig spec aliases `const js = bun.jsc.C;` (Macro.zig:642) — the // C-API surface is intentionally `#[deprecated]` upstream but is the spec'd // call path for `JSObjectCallAsFunctionReturnValueHoldingAPILock`. use crate::expr_jsc::ExprJsc; @@ -44,7 +44,7 @@ pub(crate) fn is_macro_path(str: &[u8]) -> bool { // MacroContext // ══════════════════════════════════════════════════════════════════════════ -// PORT NOTE: Zig stores `*Resolver` / `*DotEnv.Loader` and copies the +// Zig stores `*Resolver` / `*DotEnv.Loader` and copies the // `MacroRemap` hash-map header by value (which aliases the same backing // storage). Rust models all three as raw pointers because the referents live // inside the owning `Transpiler` and are also reachable through other aliases @@ -58,7 +58,7 @@ pub struct MacroContext { pub macros: MacroMap, pub remap: bun_ptr::BackRef, pub javascript_object: JSValue, - /// PORT NOTE: Zig threads `default_allocator` (mimalloc, process-lifetime) + /// Zig threads `default_allocator` (mimalloc, process-lifetime) /// through `Runner::run` → `Run.allocator`; the slices it backs (property /// keys / UTF-16 string data / `from_blob` JSON sub-parse) are never /// individually freed and outlive the call frame. The Rust AST takes @@ -83,7 +83,7 @@ pub(crate) type MacroMap = ArrayHashMap; impl MacroContext { pub fn get_remap(&self, path: &[u8]) -> Option<&MacroRemapEntry> { // `remap` is a `BackRef` into `Transpiler.options`, which outlives - // every `MacroContext` (see struct PORT NOTE). + // every `MacroContext` (see struct comment). let remap = self.remap.get(); if remap.is_empty() { return None; @@ -125,7 +125,7 @@ impl MacroContext { debug_assert!(!is_macro_path(import_record_path_without_macro_prefix)); - // SAFETY: `resolver` outlives `self` (see struct PORT NOTE); uniquely + // SAFETY: `resolver` outlives `self` (see struct comment); uniquely // accessed for the duration of this resolve call. let resolver = unsafe { &mut *self.resolver }; @@ -171,7 +171,7 @@ impl MacroContext { return Err(e); } }; - // PORT NOTE: Zig captures the resolved primary path's `.text` and + // Zig captures the resolved primary path's `.text` and // hands the slice to `Macro.init`/`loadMacroEntryPoint`, which // copies into a buffer before the borrow ends. The Rust resolver's // `Result` owns its path strings via the global `DirnameStore` @@ -211,7 +211,7 @@ impl MacroContext { } let _flush_guard = Output::flush_guard(); - // PORT NOTE: reshaped for borrowck — Zig copies the Macro by value out + // reshaped for borrowck — Zig copies the Macro by value out // of the map. We snapshot the small POD fields we need (`disabled`, // `vm`) so the macro_entry borrow can be released. let macro_disabled = macro_entry.value_ptr.disabled; @@ -220,7 +220,7 @@ impl MacroContext { if macro_disabled { return Ok(caller); } - // SAFETY: `Some` for every non-disabled Macro; see `Macro` PORT NOTE. + // SAFETY: `Some` for every non-disabled Macro; see `Macro` struct comment. let vm = macro_vm .expect("Macro.vm accessed on disabled sentinel") .as_ptr(); @@ -230,12 +230,12 @@ impl MacroContext { // SAFETY: `event_loop()` returns a self-pointer into `*vm`. unsafe { (*(*vm).event_loop()).ensure_waker() }; - // PORT NOTE: Zig builds `Wrapper { args: ArgsTuple, ret }` and calls + // Zig builds `Wrapper { args: ArgsTuple, ret }` and calls // `vm.runWithAPILock(Wrapper, &wrapper, Wrapper.call)` which is just // `holdAPILock(ctx, fn(ctx))`. The Rust `run_with_api_lock` already // takes a closure, so the wrapper struct collapses into captures. let javascript_object = self.javascript_object; - // PORT NOTE: reshaped for borrowck — `self.bump` is shared-borrowed for + // reshaped for borrowck — `self.bump` is shared-borrowed for // the closure while `self.macros` was already released above; capture // as a raw pointer so the closure does not extend `&mut self`. // Lazy-init the backing arena now that a macro is actually being @@ -374,13 +374,13 @@ pub(crate) fn __bun_macro_context_get_remap( // Macro // ══════════════════════════════════════════════════════════════════════════ -// PORT NOTE: Zig stores `*Resolver` / `*VirtualMachine` and leaves them `undefined` +// Zig stores `*Resolver` / `*VirtualMachine` and leaves them `undefined` // for the disabled sentinel (`Macro{ .resolver = undefined, .disabled = true }`). // Rust references cannot be uninitialised, so both are carried as `Option>`; // they are `Some` for every live macro and `None` only when `disabled == true`, which // is checked before any access (see `MacroContext::call`). pub struct Macro { - // PORT NOTE: `Resolver<'a>` carries a borrow lifetime, but `Macro` is stored + // `Resolver<'a>` carries a borrow lifetime, but `Macro` is stored // by value in a `MacroMap` keyed by hash and outlives any single call frame. // The Zig original stores a raw `*Resolver`; `NonNull` already erases borrow // tracking, so `'static` here is the lifetime-erased moral equivalent. @@ -416,7 +416,7 @@ impl Macro { #[inline] pub fn vm(&self) -> *mut VirtualMachine { debug_assert!(!self.disabled); - // SAFETY-adjacent: `Some` for every non-disabled Macro; see struct PORT NOTE. + // SAFETY-adjacent: `Some` for every non-disabled Macro; see struct comment. self.vm .expect("Macro.vm accessed on disabled sentinel") .as_ptr() @@ -432,11 +432,10 @@ impl Macro { specifier: &[u8], hash: i32, ) -> Result { - // TODO(port): narrow error set let (vm, is_new_vm): (*mut VirtualMachine, bool) = if VirtualMachine::is_loaded() { (VirtualMachine::get_mut_ptr(), false) } else { - // PORT NOTE: Zig saved/restored `resolver.opts.transform_options` + // Zig saved/restored `resolver.opts.transform_options` // across this block because `VirtualMachine.init` (via // `Config.configureTransformOptionsForBunVM`) mutates the *passed* // `args`. In the Rust port the resolver's forward-decl @@ -522,7 +521,7 @@ pub enum MacroError { OutOfMemory, #[error(transparent)] ToJs(#[from] ToJSError), - // PORT NOTE: `JsError` does not impl `std::error::Error` in the stub surface, + // `JsError` does not impl `std::error::Error` in the stub surface, // so `#[error(transparent)]` / `#[from]` (which generates a `source()` requiring // `Error`) are unavailable; format via Debug + manual `From` instead. #[error("{0:?}")] @@ -555,7 +554,7 @@ pub struct Run<'a> { pub function_name: &'a [u8], pub macro_: &'a Macro, pub global: &'a JSGlobalObject, - // PORT NOTE: Zig carried `std.mem.Allocator param` (always + // Zig carried `std.mem.Allocator param` (always // `default_allocator`, mimalloc, process-lifetime — slices backing // `E.String` data / property keys are never freed). The Rust AST uses // arena-owned slices (`EString::init` lifetime-erases its borrow), so @@ -623,7 +622,7 @@ impl<'a> Run<'a> { pub fn run(&mut self, value: JSValue) -> Result { use ConsoleObject::formatter::Tag as T; - // PORT NOTE: `Tag::get` returns `TagResult { tag: TagPayload, .. }`; + // `Tag::get` returns `TagResult { tag: TagPayload, .. }`; // collapse the payload to its discriminant via `.tag()`. match T::get(value, self.global)?.tag.tag() { T::Error => self.coerce(T::Error, value), @@ -644,7 +643,7 @@ impl<'a> Run<'a> { self.log.add_error_fmt( Some(self.source), self.caller.loc, - // PORT NOTE: `JSType` derives `Debug` (not `IntoStaticStr`); + // `JSType` derives `Debug` (not `IntoStaticStr`); // Zig's `@tagName` ≈ `{:?}` here. format_args!( "cannot coerce {} ({:?}) to Bun's AST. Please return a simpler type", @@ -657,7 +656,7 @@ impl<'a> Run<'a> { } } - // PORT NOTE: Zig dispatched on `comptime tag`; that requires + // Zig dispatched on `comptime tag`; that requires // `Tag: core::marker::ConstParamTy`, which the upstream enum does not // derive. Reshaped to a runtime `tag` param — every call site in `run` // already matches once, so the comptime monomorphization was redundant. @@ -767,7 +766,7 @@ impl<'a> Run<'a> { i += 1; } - // PORT NOTE: reshaped for borrowck — `Expr.data.e_array` is a + // reshaped for borrowck — `Expr.data.e_array` is a // `StoreRef` (raw arena ptr) so re-borrow it after the `run` // recursion releases `self`. if let ExprData::EArray(mut e_array) = expr.data { @@ -814,7 +813,7 @@ impl<'a> Run<'a> { while let Some(prop) = object_iter.next()? { let object_value = self.run(object_iter.value)?; - // PORT NOTE: `EString::init` lifetime-erases its borrow + // `EString::init` lifetime-erases its borrow // (arena-owned per the parser's `Str` convention). Copy the // key into the `MacroContext` bump arena so it outlives the // temporary `to_owned_slice()` Vec and the returned `Expr`. @@ -886,7 +885,7 @@ impl<'a> Run<'a> { } else { bun_str.latin1().iter().map(|&b| b as u16).collect() }; - // PORT NOTE: `E::EString::init_utf16` lifetime-erases the slice + // `E::EString::init_utf16` lifetime-erases the slice // (arena-owned per the parser's `Str` convention). Copy into // the `MacroContext` bump arena — Zig used `this.allocator` // (`default_allocator`, process-lifetime). @@ -918,7 +917,7 @@ impl<'a> Run<'a> { if rejected || promise_result.is_error() || promise_result.is_aggregate_error(self.global) - // PORT NOTE: `JSGlobalObject::vm()` returns `&VM`; + // `JSGlobalObject::vm()` returns `&VM`; // `is_exception` takes `*mut VM` (FFI passthrough). The // C++ side never writes through it. || promise_result @@ -943,7 +942,7 @@ impl<'a> Run<'a> { self.log.add_error_fmt( Some(self.source), self.caller.loc, - // PORT NOTE: `JSType` derives `Debug` (not `IntoStaticStr`). + // `JSType` derives `Debug` (not `IntoStaticStr`). format_args!( "cannot coerce {:?} to Bun's AST. Please return a simpler type", value.js_type(), @@ -971,12 +970,12 @@ impl Runner { ); } - // PORT NOTE: Zig `exception_holder = jsc.ZigException.Holder.init();` — + // Zig `exception_holder = jsc.ZigException.Holder.init();` — // the holder is never read in this body (legacy from an earlier // exception-reporting path); a thread-local sentinel suffices. EXCEPTION_HOLDER.with(|h| h.set(true)); - // PORT NOTE: Zig's `defer { for js_args[..n] |a| a.unprotect(); + // Zig's `defer { for js_args[..n] |a| a.unprotect(); // allocator.free(js_args); }` becomes an RAII guard that *owns* the // `Vec` + processed count. All mutation goes through the // guard's fields so there is no aliasing of a raw pointer with later @@ -1053,7 +1052,7 @@ impl Runner { js_args.args[last] = javascript_object; } - // PORT NOTE: Zig stashes the call args + result in threadlocals so the + // Zig stashes the call args + result in threadlocals so the // `extern "C" fn()` trampoline (no userdata) can reach them, then calls // `Bun__startMacro(&call, global)`. Rust round-trips through a // threadlocal `*mut c_void` to a stack `CallData` instead — `CallArgs` diff --git a/src/js_parser_jsc/expr_jsc.rs b/src/js_parser_jsc/expr_jsc.rs index f869958bcf21..d9fea9941a51 100644 --- a/src/js_parser_jsc/expr_jsc.rs +++ b/src/js_parser_jsc/expr_jsc.rs @@ -143,6 +143,22 @@ pub(crate) fn object_to_js( Ok(obj) } +/// Serialize UTF-8 bytes to a JS string, transcoding to UTF-16 only when the +/// bytes are not pure ASCII (`to_utf16_alloc` returns `Ok(None)` for +/// pure-ASCII, in which case the 8-bit Latin-1 form is kept). +fn utf8_bytes_to_js(bytes: &[u8], global: &JSGlobalObject) -> Result { + let utf16 = strings::to_utf16_alloc(bytes, false, false).map_err(|_| ToJSError::OutOfMemory)?; + if let Some(utf16) = utf16 { + let (mut out, chars) = BunString::create_uninitialized_utf16(utf16.len()); + chars.copy_from_slice(&utf16); + bun_string_jsc::transfer_to_js(&mut out, global).map_err(js_err) + } else { + let (mut out, chars) = BunString::create_uninitialized_latin1(bytes.len()); + chars.copy_from_slice(bytes); + bun_string_jsc::transfer_to_js(&mut out, global).map_err(js_err) + } +} + /// `E.String.toJS` (src/js_parser_jsc/expr_jsc.zig:79). /// /// Stamps the body for both `EString` nominal types: the full T4 @@ -154,34 +170,30 @@ pub(crate) fn object_to_js( macro_rules! impl_string_to_js { ($name:ident, $ty:ty) => { pub fn $name(s: &$ty, global: &JSGlobalObject) -> Result { - // TODO(port): Zig mutates `s` via `resolveRopeIfNeeded(allocator)`; - // callers only have `&` and there is no bump arena in scope here. - // Either thread a bump arena + interior-mut rope or resolve ropes - // before reaching here. For now, assert non-rope (current callers - // feed resolved literals). - debug_assert!( - s.next.is_none(), - "string_to_js: rope EString reached without resolveRopeIfNeeded; thread a bump arena" - ); + // Zig resolves ropes in place via `resolveRopeIfNeeded(allocator)`; + // callers here only have `&s` and no bump arena, so flatten the + // rope into a temporary heap buffer and serialize from that + // instead. Ropes are only ever built from UTF-8 parts + // (`resolve_rope_if_needed` is a no-op for UTF-16). + if s.next.is_some() && s.is_utf8() { + let mut bytes: Vec = Vec::with_capacity(s.rope_len as usize); + bytes.extend_from_slice(s.slice8()); + let mut next = s.next; + while let Some(part) = next { + let part = part.get(); + bytes.extend_from_slice(&part.data); + next = part.next; + } + return utf8_bytes_to_js(&bytes, global); + } + if !s.is_present() { let emp = BunString::EMPTY; return bun_string_jsc::to_js(&emp, global).map_err(js_err); } if s.is_utf8() { - // `to_utf16_alloc` returns `Ok(None)` for pure-ASCII (keep 8-bit form). - let utf16 = strings::to_utf16_alloc(s.slice8(), false, false) - .map_err(|_| ToJSError::OutOfMemory)?; - if let Some(utf16) = utf16 { - let (mut out, chars) = BunString::create_uninitialized_utf16(utf16.len()); - chars.copy_from_slice(&utf16); - bun_string_jsc::transfer_to_js(&mut out, global).map_err(js_err) - } else { - let bytes = s.slice8(); - let (mut out, chars) = BunString::create_uninitialized_latin1(bytes.len()); - chars.copy_from_slice(bytes); - bun_string_jsc::transfer_to_js(&mut out, global).map_err(js_err) - } + utf8_bytes_to_js(s.slice8(), global) } else { let utf16 = s.slice16(); let (mut out, chars) = BunString::create_uninitialized_utf16(utf16.len()); diff --git a/src/js_printer/lib.rs b/src/js_printer/lib.rs index e1dc61ab68d8..8367b94c333d 100644 --- a/src/js_printer/lib.rs +++ b/src/js_printer/lib.rs @@ -5,10 +5,7 @@ //! (`print_expr`, `print_stmt`, `print_binding`, `print_property`, …) //! compile against `bun_ast::{e,s,b,g,op,expr,stmt}`. The top-level //! `print` / `print_with_writer{,_and_platform}` / `print_common_js` / -//! `get_source_map_builder` driver fns live at crate root. Remaining gaps -//! (see TODO(port) markers below): the template-inlining fold, the -//! ESM-to-CJS __export emission path, `print_dev_server_module`, the source-map -//! self-borrow in `init`, and the `print_ast` minify-renamer driver / `print_json`. +//! `print_json` / `get_source_map_builder` driver fns live at crate root. #![warn(unused_must_use)] #![feature(adt_const_params)] @@ -64,7 +61,8 @@ pub use bun_paths::fs::Path as FsPath; // renamer — defined in `renamer.rs`. The five former leak sites // have been replaced with `bumpalo::Bump`-backed allocation (PORTING.md §Forbidden); // renamed-name strings are arena-owned and typed `*const [u8]` (PORTING.md §Allocators). -// TODO(refactor): thread the AST `'bump` lifetime through Renamer to replace the raw pointers. +// A future refactor could thread the AST `'bump` lifetime through Renamer to +// replace the raw pointers. // ────────────────────────────────────────────────────────────────────────── #[path = "renamer.rs"] pub mod renamer; @@ -791,12 +789,6 @@ use bun_core::printer::{ /// For support JavaScriptCore const ASCII_ONLY_ALWAYS_ON_UNLESS_MINIFYING: bool = true; -pub fn write_module_id(writer: &mut impl core::fmt::Write, module_id: u32) { - debug_assert!(module_id != 0); // either module_id is forgotten or it should be disabled - writer.write_str("$").expect("unreachable"); - write!(writer, "{:x}", module_id).expect("unreachable"); -} - // PERF(port): was comptime monomorphization (`comptime CodePointType: type`) — Zig // instantiated per code-unit type; Rust callers widen to i32 at the boundary. // PERF(port): `ascii_only` is a *runtime* arg (was `const ASCII_ONLY`) so the large @@ -953,7 +945,7 @@ pub fn estimate_length_for_utf8(input: &[u8], ascii_only: bool, quote_char: u8) } // Zig's `else` on `while` runs when the condition fails (i.e. `None`). if remaining.as_ptr() == input.as_ptr() { - // PORT NOTE: reshaped — Zig returns `remaining.len + 2` when *no* escape was ever found. + // reshaped — Zig returns `remaining.len + 2` when *no* escape was ever found. // The branch above already handled the loop body; falling out of the loop with no // iterations means "no escapes anywhere". } @@ -1005,7 +997,7 @@ where "for json, quote_char must be '\"'" ); - // PORT NOTE: this is a large hot-path function; logic is ported 1:1 but the + // this is a large hot-path function; logic is ported 1:1 but the // utf16 path needs &[u16] handling. let text = text_in; let mut i: usize = 0; @@ -1288,7 +1280,6 @@ pub struct Options<'a> { // allocator dropped — global mimalloc (this is an AST crate but Options.allocator is the global default) pub source_map_handler: Option>, pub source_map_builder: Option<&'a mut SourceMap::chunk::Builder>, - // TODO(port): bun_options_types::schema::api::CssInJsBehavior — local stand-in. pub css_import_behavior: CssInJsBehavior, pub target: bun_ast::Target, @@ -1529,7 +1520,7 @@ impl ExprFlag { pub fn forbid_call() -> ExprFlagSet { ExprFlag::ForbidCall.into() } - // PORT NOTE: Zig had `ForbidAnd` referencing `.forbid_and` which doesn't exist in the enum — dead code. + // Zig had `ForbidAnd` referencing `.forbid_and` which doesn't exist in the enum — dead code. #[inline] pub fn has_non_optional_chain_parent() -> ExprFlagSet { ExprFlag::HasNonOptionalChainParent.into() @@ -1544,84 +1535,6 @@ impl ExprFlag { } } -#[derive(Clone, Copy, PartialEq, Eq)] -enum ImportVariant { - PathOnly, - ImportStar, - ImportDefault, - ImportStarAndImportDefault, - ImportItems, - ImportItemsAndDefault, - ImportItemsAndStar, - ImportItemsAndDefaultAndStar, -} - -impl ImportVariant { - #[inline] - pub(crate) fn has_items(self) -> Self { - match self { - Self::ImportDefault => Self::ImportItemsAndDefault, - Self::ImportStar => Self::ImportItemsAndStar, - Self::ImportStarAndImportDefault => Self::ImportItemsAndDefaultAndStar, - _ => Self::ImportItems, - } - } - - // We always check star first so don't need to be exhaustive here - #[inline] - pub(crate) fn has_star(self) -> Self { - match self { - Self::PathOnly => Self::ImportStar, - _ => self, - } - } - - // We check default after star - #[inline] - pub(crate) fn has_default(self) -> Self { - match self { - Self::PathOnly => Self::ImportDefault, - Self::ImportStar => Self::ImportStarAndImportDefault, - _ => self, - } - } - - pub(crate) fn determine(record: &ImportRecord, s_import: &js_ast::S::Import) -> ImportVariant { - let mut variant = ImportVariant::PathOnly; - - if record - .flags - .contains(ImportRecordFlags::CONTAINS_IMPORT_STAR) - { - variant = variant.has_star(); - } - - if !record - .flags - .contains(ImportRecordFlags::WAS_ORIGINALLY_BARE_IMPORT) - { - if !record - .flags - .contains(ImportRecordFlags::CONTAINS_DEFAULT_ALIAS) - { - if let Some(default_name) = &s_import.default_name { - if default_name.ref_.is_some() { - variant = variant.has_default(); - } - } - } else { - variant = variant.has_default(); - } - } - - if !s_import.items.is_empty() { - variant = variant.has_items(); - } - - variant - } -} - #[derive(Clone, Copy, PartialEq, Eq)] enum ClauseItemAs { Import, @@ -1637,14 +1550,7 @@ pub enum IsTopLevel { No, } -/// `MAY_HAVE_MODULE_INFO = IS_BUN_PLATFORM && !REWRITE_ESM_TO_CJS` -// TODO(port): const-generic associated const — written as a free fn until adt_const_params lands. -#[inline(always)] -pub const fn may_have_module_info(is_bun_platform: bool, rewrite_esm_to_cjs: bool) -> bool { - is_bun_platform && !rewrite_esm_to_cjs -} - -// PORT NOTE: Zig defined `TopLevelAndIsExport`/`TopLevel` as conditional zero-size structs when +// Zig defined `TopLevelAndIsExport`/`TopLevel` as conditional zero-size structs when // !may_have_module_info. In Rust we use one shape; dead-code elimination removes the unused // fields when MAY_HAVE_MODULE_INFO is false. #[derive(Clone, Copy, Default)] @@ -1758,7 +1664,7 @@ pub mod __gated_printer { pub stack_overflowed: bool, pub was_lazy_export: bool, - // PORT NOTE: Zig used `if (!may_have_module_info) void else ?*ModuleInfo` — in Rust we always + // Zig used `if (!may_have_module_info) void else ?*ModuleInfo` — in Rust we always // carry the Option and gate at call sites with MAY_HAVE_MODULE_INFO. pub module_info: Option<&'a mut analyze_transpiled_module::ModuleInfo>, @@ -1773,7 +1679,7 @@ pub mod __gated_printer { /// code in the JavaScript parser for details. pub struct BinaryExpressionVisitor<'ast> { // Inputs - // PORT NOTE: Zig stored `*const E.Binary`; we keep a StoreRef so the + // Zig stored `*const E.Binary`; we keep a StoreRef so the // visitor stack can outlive the by-value `Expr` argument to `print_expr`. pub e: js_ast::StoreRef, _phantom: core::marker::PhantomData<&'ast ()>, @@ -1790,14 +1696,8 @@ pub mod __gated_printer { pub right_level: Level, } - impl<'ast> Default for BinaryExpressionVisitor<'ast> { - #[cold] - #[inline(never)] - fn default() -> Self { - // TODO(port): `entry` defaulted to `undefined` in Zig; we need a sentinel &'static OpInfo. - unreachable!("construct via fields") - } - } + // No `Default` impl: `entry` was `undefined` in Zig; every construction + // site builds the visitor with all fields explicit. // ─────────────────────────────────────────────────────────────────────────── // Printer methods @@ -1997,11 +1897,36 @@ pub mod __gated_printer { } fn fmt(&mut self, args: core::fmt::Arguments<'_>) -> Result<(), bun_core::Error> { - // PERF(port): Zig used std.fmt.count + bufPrint into reserved space (no heap). - // TODO(port): implement `count` over fmt::Arguments to match. - let mut buf: Vec = Vec::new(); - Write::write_fmt(&mut buf, format_args!("{}", args)).expect("unreachable"); - self.writer.write_reserved(&buf) + // Zig used `std.fmt.count` + `bufPrint` into reserved space (no + // heap). Rust can't pre-count `fmt::Arguments` without formatting, + // so stream each formatted chunk straight into the writer's + // reserved space instead — same zero-heap property without + // formatting twice. + struct FmtAdapter<'w, W: WriterTrait> { + writer: &'w mut W, + err: Option, + } + impl core::fmt::Write for FmtAdapter<'_, W> { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + match self.writer.write_reserved(s.as_bytes()) { + Ok(()) => Ok(()), + Err(e) => { + self.err = Some(e); + Err(core::fmt::Error) + } + } + } + } + let mut adapter = FmtAdapter { + writer: &mut self.writer, + err: None, + }; + if core::fmt::write(&mut adapter, args).is_err() { + return Err(adapter + .err + .unwrap_or_else(|| bun_core::err!("WriteFailed"))); + } + Ok(()) } pub fn print_buffer(&mut self, str: &[u8]) { @@ -2202,7 +2127,7 @@ pub mod __gated_printer { // Record var declarations for module_info. printGlobalBunImportStatement // bypasses printDeclStmt/printBinding, so we must record vars explicitly. - // PORT NOTE: reshaped for borrowck — compute names before borrowing module_info. + // reshaped for borrowck — compute names before borrowing module_info. if Self::MAY_HAVE_MODULE_INFO && self.module_info.is_some() { if import.star_name_loc.is_some() { let name = self.name_for_symbol(import.namespace_ref); @@ -2493,7 +2418,7 @@ pub mod __gated_printer { properties: js_ast::StoreSlice::new_mut(temp_bindings.as_mut_slice()), is_single_line: true, }; - // PORT NOTE: `Binding::init(*B.Object, loc)` is gated upstream; + // `Binding::init(*B.Object, loc)` is gated upstream; // inline its body — it just tags the union and copies `loc`. // `from_bump` wraps a `&mut T` as a non-null arena ref; here the // pointee is a stack local but `print_binding` only reads it and @@ -2714,7 +2639,7 @@ pub mod __gated_printer { pub fn print_non_negative_float(&mut self, float: f64) { // Is this actually an integer? - // PORT NOTE: @setRuntimeSafety(false) / @setFloatMode(.optimized) have no Rust equivalent. + // @setRuntimeSafety(false) / @setFloatMode(.optimized) have no Rust equivalent. let floored = float.floor(); let remainder = float - floored; let is_integer = remainder == 0.0; @@ -2733,7 +2658,10 @@ pub mod __gated_printer { return; } - // TODO(port): Zig "{d}" on f64 — need shortest-round-trip formatter (ryu) to match output exactly. + // Zig formatted with "{d}" (shortest round-trip decimal, no + // exponent). Rust's `Display` for f64 has the same contract — + // shortest digit string that round-trips, never scientific + // notation — so the emitted text matches. let _ = self.fmt(format_args!("{}", float)); } @@ -2791,7 +2719,7 @@ pub mod __gated_printer { /// `Source::contents` buffer — both are kept alive for `'a` by the /// caller of `Printer::init`. Detach the borrow to a raw ptr per the /// parser's ARENA convention (matching `slice_of` for AST fields). - /// PORT NOTE: reshaped for borrowck — TODO(refactor): thread `'bump` through Renamer. + /// Reshaped for borrowck — a future refactor could thread `'bump` through Renamer. #[inline] fn name_for_symbol(&mut self, ref_: Ref) -> &'a [u8] { let p = std::ptr::from_ref::<[u8]>(self.renamer.name_for_symbol(ref_)); @@ -2811,7 +2739,7 @@ pub mod __gated_printer { #[inline] pub fn import_record(&self, import_record_index: usize) -> &'a ImportRecord { - // PORT NOTE: detached from `&self` so callers can interleave `&mut self` printing. + // detached from `&self` so callers can interleave `&mut self` printing. &self.import_records[import_record_index] } @@ -2842,7 +2770,7 @@ pub mod __gated_printer { if wrap { self.print(b"("); } - // PORT NOTE: Zig used `defer if (wrap) p.print(")")`. We close at every `return` below. + // Zig used `defer if (wrap) p.print(")")`. We close at every `return` below. debug_assert!(self.import_records.len() > import_record_index as usize); let record = self.import_record(import_record_index as usize); @@ -3122,7 +3050,37 @@ pub mod __gated_printer { } pub fn print_string_literal_utf8(&mut self, str: &[u8], allow_backtick: bool) { - // TODO(port): bun_core::wtf8_validate_slice — debug-only assert dropped. + // Zig: `if (Environment.allow_assert) std.debug.assert(std.unicode.wtf8ValidateSlice(str))`. + // WTF-8 = UTF-8 plus surrogate code points (U+D800..U+DFFF as + // `ED A0 80`..`ED BF BF`), so validate UTF-8 shape minus the + // surrogate exclusion. + fn is_valid_wtf8(mut s: &[u8]) -> bool { + while let Some(&b0) = s.first() { + let len = match b0 { + 0x00..=0x7F => 1, + 0xC2..=0xDF => 2, + 0xE0..=0xEF => 3, + 0xF0..=0xF4 => 4, + _ => return false, + }; + if s.len() < len { + return false; + } + let cont_ok = s[1..len].iter().all(|&b| b & 0xC0 == 0x80); + if !cont_ok { + return false; + } + match (len, b0) { + (3, 0xE0) if s[1] < 0xA0 => return false, // overlong + (4, 0xF0) if s[1] < 0x90 => return false, // overlong + (4, 0xF4) if s[1] > 0x8F => return false, // > U+10FFFF + _ => {} + } + s = &s[len..]; + } + true + } + debug_assert!(is_valid_wtf8(str)); let quote = if !IS_JSON { best_quote_char_for_string(str, allow_backtick) @@ -3453,7 +3411,7 @@ pub mod __gated_printer { self.print_space_before_identifier(); self.add_source_mapping(expr.loc); - // PORT NOTE: reshaped for borrowck — find the matching index first, + // reshaped for borrowck — find the matching index first, // then drop the immutable iter borrow before printing. let mut found: Option = None; if let Some(exports) = self.options.commonjs_named_exports { @@ -4098,6 +4056,25 @@ pub mod __gated_printer { self.print_string_literal_e_string(&e, true); } ExprData::ETemplate(e) => { + // Print from a thread-local shallow copy of the template + // node. The fold below must NOT be persisted into the + // shared AST: during bundling the same module's parts can + // be printed concurrently into multiple chunks + // (multi-entry, non-split builds), so writing the folded + // template back through the arena pointer (Zig did + // `e.* = e2.data.e_template.*`, which has the same race) + // would be a cross-thread data race. Re-prints recompute + // the identical fold, so emitted output is unchanged. + let mut e = E::Template { + tag: e.tag, + parts: e.parts, + head: match &e.head { + E::TemplateContents::Cooked(c) => { + E::TemplateContents::Cooked(c.shallow_clone()) + } + E::TemplateContents::Raw(r) => E::TemplateContents::Raw(*r), + }, + }; if e.tag.is_none() && (self.options.minify_syntax || self.was_lazy_export) { // Zig: `var part = part.*` — `TemplatePart` is structurally // `Copy` but `EString` doesn't derive it; field-wise copy. @@ -4115,7 +4092,12 @@ pub mod __gated_printer { } } - let mut replaced: Vec = Vec::new(); + // Bump-allocated (printer arena) so the folded + // template's parts outlive the inner block: the + // thread-local `e` above keeps the parts slice alive + // through the print loop at the end of this arm. + let mut replaced = + bun_alloc::ArenaVec::::new_in(self.bump); for (i, _part) in e.parts().iter().enumerate() { let mut part = part_clone(_part); let inlined_value: Option = match &part.value.data { @@ -4145,9 +4127,9 @@ pub mod __gated_printer { // Zig: `var copy = e.*; copy.parts = &replaced;` — build a // local `Template` (not a StoreRef alias) so `fold`'s // `mem::take(self.head)` doesn't clobber the AST node. - // `replaced` outlives `copy`/`fold()`; wrap as a StoreSlice - // over the local Vec to match `Template.parts`. - let parts_slice = js_ast::StoreSlice::new_mut(replaced.as_mut_slice()); + // `replaced` is leaked into the bump arena, so the + // parts slice stays valid past this block. + let parts_slice = js_ast::StoreSlice::from_bump(replaced); let mut copy = E::Template { tag: e.tag, parts: parts_slice, @@ -4167,9 +4149,28 @@ pub mod __gated_printer { return; } ExprData::ETemplate(t) => { - // SAFETY: e is &mut behind the AST arena pointer - // TODO(port): Zig mutated `e.* = e2.data.e_template.*` — needs &mut access through arena. - let _ = t; + // Use the folded template for the print path + // below — assigned to the thread-local `e`, + // never written back into the shared AST node + // (see the comment at the top of this arm). + // Field values are arena/store-backed in both + // fold paths (the bail-out path aliases the + // stack `copy`, but its `parts`/`head` payloads + // are bump-allocated above), so copying the + // fields out is safe even though `t` itself may + // point at `copy`. + e = E::Template { + tag: t.tag, + parts: t.parts, + head: match &t.head { + E::TemplateContents::Cooked(c) => { + E::TemplateContents::Cooked(c.shallow_clone()) + } + E::TemplateContents::Raw(r) => { + E::TemplateContents::Raw(*r) + } + }, + }; } _ => {} } @@ -4186,7 +4187,7 @@ pub mod __gated_printer { if let Some(tag) = &e.tag { self.add_source_mapping(expr.loc); // Optional chains are forbidden in template tags - // PORT NOTE: `Expr::is_optional_chain` is gated upstream; inline its body. + // `Expr::is_optional_chain` is gated upstream; inline its body. let is_optional_chain = match &expr.data { ExprData::EDot(d) => d.optional_chain.is_some(), ExprData::EIndex(i) => i.optional_chain.is_some(), @@ -4205,7 +4206,6 @@ pub mod __gated_printer { } self.print(b"`"); - let mut e = *e; match &mut e.head { E::TemplateContents::Raw(raw) => self.print_raw_template_literal(raw), E::TemplateContents::Cooked(cooked) => { @@ -4224,7 +4224,7 @@ pub mod __gated_printer { E::TemplateContents::Raw(raw) => self.print_raw_template_literal(raw), E::TemplateContents::Cooked(cooked) => { if cooked.is_present() { - // PORT NOTE: `parts` is `*mut [TemplatePart]` but accessed `&[T]` + // `parts` is `*mut [TemplatePart]` but accessed `&[T]` // here. Zig mutates in place; Rust resolves a local copy of the // EString header (the rope chain is StoreRef-linked and Copy) and // prints from that — the arena node stays roped. @@ -4281,7 +4281,7 @@ pub mod __gated_printer { } else { e.ref_ }; - // PORT NOTE: reshaped for borrowck — `get_const` borrows self; + // reshaped for borrowck — `get_const` borrows self; // capture as `BackRef` so the `&self` borrow is dropped before the // `&mut self` print calls below. Symbol table is arena-backed and // outlives the print pass (BackRef invariant). @@ -4540,10 +4540,10 @@ pub mod __gated_printer { } ExprData::EJsxElement(_) | ExprData::EPrivateIdentifier(_) => { if cfg!(debug_assertions) { - // TODO(port): @tagName(expr.data) — ExprData lacks IntoStaticStr. + // Zig: `@tagName(expr.data)`. Output::panic(format_args!( - "Unexpected expression of type {:?}", - core::mem::discriminant(&expr.data) + "Unexpected expression of type .{}", + expr.data.tag_name() )); } } @@ -4677,7 +4677,7 @@ pub mod __gated_printer { } pub fn print_property(&mut self, item_in: &G::Property) { - // PORT NOTE: Zig took G.Property by value (Copy in Zig). Rust's + // Zig took G.Property by value (Copy in Zig). Rust's // G::Property isn't `Copy`, so take a borrow and shallow-copy the // mutable bits we may rewrite (key + flags). let mut item = G::Property { @@ -5000,7 +5000,7 @@ pub mod __gated_printer { self.add_source_mapping(binding.loc); self.print_symbol(b.r#ref); if Self::MAY_HAVE_MODULE_INFO { - // PORT NOTE: reshaped for borrowck — fetch name before borrowing module_info. + // reshaped for borrowck — fetch name before borrowing module_info. let local_name = self.name_for_symbol(b.r#ref); if let Some(mi) = self.module_info() { let name_id = mi.str(local_name); @@ -5156,7 +5156,7 @@ pub mod __gated_printer { self.name_for_symbol(id.r#ref), ) { if Self::MAY_HAVE_MODULE_INFO { - // PORT NOTE: reshaped for borrowck — bump access first. + // reshaped for borrowck — bump access first. let str8 = str.slice(self.bump); if let Some(mi) = self.module_info() { let name_id = mi.str(str8); @@ -5231,7 +5231,7 @@ pub mod __gated_printer { let prev_stmt_tag = self.prev_stmt_tag; // Zig: `defer { p.prev_stmt_tag = std.meta.activeTag(stmt.data); }` - // PORT NOTE: reshaped for borrowck — scopeguard would hold `&mut self.prev_stmt_tag` + // reshaped for borrowck — scopeguard would hold `&mut self.prev_stmt_tag` // across the whole match body and conflict with every `&mut self` call below. Instead // we assign `self.prev_stmt_tag = new_tag` at every return point (early + tail). let new_tag = stmt.data.tag(); @@ -5547,7 +5547,7 @@ pub mod __gated_printer { self.print_space(); let last = slice_of(s.items).len() - 1; for (i, item) in slice_of(s.items).iter().enumerate() { - // PORT NOTE: reshaped for borrowck — detach symbol from + // reshaped for borrowck — detach symbol from // `&self` via `BackRef` (arena-backed table outlives print). let symbol = BackRef::::new( self.symbols() @@ -5610,10 +5610,13 @@ pub mod __gated_printer { return Ok(()); } - // PORT NOTE: Zig wraps `s.items` in an ArrayListUnmanaged and uses swapRemove - // in-place. `ClauseItem` isn't `Clone`, so build a Vec of arena borrows - // instead and swap-remove the borrows. - // TODO(port): lifetime — Zig mutates `s.items` in place; consider writing back. + // Zig wraps `s.items` in an ArrayListUnmanaged and uses swapRemove + // in-place (incidentally persisting the filtered list into the + // AST). `ClauseItem` isn't `Clone`, so build a Vec of arena + // borrows instead and swap-remove the borrows; the printer is + // the only consumer of the filtered list, and a re-print + // recomputes the identical filtering, so the AST write-back + // is unnecessary. let mut array: Vec<&js_ast::ClauseItem> = slice_of(s.items).iter().collect(); { let mut i: usize = 0; @@ -5621,7 +5624,7 @@ pub mod __gated_printer { let item = array[i]; if !item.original_name.slice().is_empty() { - // PORT NOTE: reshaped for borrowck — detach symbol from + // reshaped for borrowck — detach symbol from // `&self` via `BackRef` (arena-backed; outlives the print pass). let symbol = self .symbols() @@ -5662,7 +5665,8 @@ pub mod __gated_printer { self.prev_stmt_tag = new_tag; return Ok(()); } - // s.items = array.items; — TODO(port): write back into AST + // Zig: `s.items = array.items` — see the note above; the + // write-back is intentionally skipped. } self.print(b"{"); @@ -5753,7 +5757,7 @@ pub mod __gated_printer { self.print_semicolon_after_statement(); if Self::MAY_HAVE_MODULE_INFO && self.module_info.is_some() { - // PORT NOTE: reshaped for borrowck — re-borrow module_info per item so + // reshaped for borrowck — re-borrow module_info per item so // `name_for_symbol` (which needs `&mut self`) can run between uses. let irp_id = { let mi = self.module_info().expect("infallible: module_info enabled"); @@ -6279,7 +6283,7 @@ pub mod __gated_printer { self.print_semicolon_after_statement(); if Self::MAY_HAVE_MODULE_INFO && self.module_info.is_some() { - // PORT NOTE: reshaped for borrowck — `module_info()` borrows `&mut self`, + // reshaped for borrowck — `module_info()` borrows `&mut self`, // so we re-borrow it between `name_for_symbol` calls instead of holding // a single long-lived `mi` across the whole block. `irp_id` is Copy. let import_record_path = &record.path.text; @@ -6479,150 +6483,11 @@ pub mod __gated_printer { } } - pub fn print_bundled_import(&mut self, record: &ImportRecord, s: &S::Import) { - if record.flags.contains(ImportRecordFlags::IS_INTERNAL) { - return; - } - - let import_record = self.import_record(s.import_record_index as usize); - let is_disabled = import_record.path.is_disabled; - let module_id = import_record.module_id; - - // If the bundled import was disabled and only imported for side effects we can skip it - if record.path.is_disabled { - if self.symbols().get_const(s.namespace_ref).is_none() { - return; - } - } - - match ImportVariant::determine(record, s) { - ImportVariant::PathOnly => { - if !is_disabled { - self.print_call_module_id(module_id); - self.print_semicolon_after_statement(); - } - } - ImportVariant::ImportItemsAndDefault | ImportVariant::ImportDefault => { - if !is_disabled { - self.print(b"var $"); - self.print_module_id(module_id); - self.print_equals(); - self.print_load_from_bundle(s.import_record_index); - - if let Some(default_name) = &s.default_name { - self.print(b", "); - self.print_symbol(default_name.ref_.expect("infallible: ref bound")); - self.print(b" = (($"); - self.print_module_id(module_id); - self.print(b" && \"default\" in $"); - self.print_module_id(module_id); - self.print(b") ? $"); - self.print_module_id(module_id); - self.print(b".default : $"); - self.print_module_id(module_id); - self.print(b")"); - } - } else { - if let Some(default_name) = &s.default_name { - self.print(b"var "); - self.print_symbol(default_name.ref_.expect("infallible: ref bound")); - self.print_equals(); - self.print_disabled_import(); - } - } - self.print_semicolon_after_statement(); - } - ImportVariant::ImportStarAndImportDefault => { - self.print(b"var "); - self.print_symbol(s.namespace_ref); - self.print_equals(); - self.print_load_from_bundle(s.import_record_index); - - if let Some(default_name) = &s.default_name { - self.print(b","); - self.print_space(); - self.print_symbol(default_name.ref_.expect("infallible: ref bound")); - self.print_equals(); - - if !IS_BUN_PLATFORM { - self.print(b"("); - self.print_symbol(s.namespace_ref); - self.print_whitespacer(ws!(b" && \"default\" in ")); - self.print_symbol(s.namespace_ref); - self.print_whitespacer(ws!(b" ? ")); - self.print_symbol(s.namespace_ref); - self.print_whitespacer(ws!(b".default : ")); - self.print_symbol(s.namespace_ref); - self.print(b")"); - } else { - self.print_symbol(s.namespace_ref); - } - } - self.print_semicolon_after_statement(); - } - ImportVariant::ImportStar => { - self.print(b"var "); - self.print_symbol(s.namespace_ref); - self.print_equals(); - self.print_load_from_bundle(s.import_record_index); - self.print_semicolon_after_statement(); - } - _ => { - self.print(b"var $"); - self.print_module_id_assume_enabled(module_id); - self.print_equals(); - self.print_load_from_bundle(s.import_record_index); - self.print_semicolon_after_statement(); - } - } - } - - pub fn print_load_from_bundle(&mut self, import_record_index: u32) { - self.print_load_from_bundle_without_call(import_record_index); - self.print(b"()"); - } - #[inline] fn print_disabled_import(&mut self) { self.print_whitespacer(ws!(b"(() => ({}))")); } - pub fn print_load_from_bundle_without_call(&mut self, import_record_index: u32) { - let record = self.import_record(import_record_index as usize); - if record.path.is_disabled { - self.print_disabled_import(); - return; - } - self.print_module_id(self.import_record(import_record_index as usize).module_id); - } - - pub fn print_call_module_id(&mut self, module_id: u32) { - self.print_module_id(module_id); - self.print(b"()"); - } - - #[inline] - fn print_module_id(&mut self, module_id: u32) { - debug_assert!(module_id != 0); // either module_id is forgotten or it should be disabled - self.print_module_id_assume_enabled(module_id); - } - - #[inline] - fn print_module_id_assume_enabled(&mut self, module_id: u32) { - self.print(b"$"); - let _ = self.fmt(format_args!("{:x}", module_id)); - } - - pub fn print_bundled_rexport(&mut self, name: &[u8], import_record_index: u32) { - self.print(b"Object.defineProperty("); - self.print_module_export_symbol(); - self.print(b","); - self.print_string_literal_utf8(name, true); - self.print_whitespacer(ws!(b",{get: () => (")); - self.print_load_from_bundle(import_record_index); - self.print_whitespacer(ws!(b"), enumerable: true, configurable: true})")); - } - // We must use Object.defineProperty() to handle re-exports from ESM -> CJS pub fn print_bundled_export(&mut self, name: &[u8], identifier: &[u8]) { self.print(b"Object.defineProperty("); @@ -6883,7 +6748,7 @@ pub mod __gated_printer { self.print_semicolon_after_statement(); if REWRITE_ESM_TO_CJS && is_export && !decls.is_empty() { - // PORT NOTE: Zig stored `?GeneratedSymbol`; the Rust `runtime::Imports` + // Zig stored `?GeneratedSymbol`; the Rust `runtime::Imports` // flattens this to `Option`, so no `.ref_` projection. let export_ref = self.options.runtime_imports.__export.unwrap(); for decl in decls { @@ -7340,7 +7205,7 @@ impl PrintArg for u8 { w.print_byte(self); } } -// PORT NOTE: Zig `print(str: anytype)` matched `comptime_int, u16, u8` and narrowed via +// Zig `print(str: anytype)` matched `comptime_int, u16, u8` and narrowed via // `@as(u8, @intCast(str))` before `writeByte`. Mirror that for `u16` so wide-int char callers // (e.g. UTF-16 iteration) compile and emit one byte identically. impl PrintArg for u16 { @@ -7406,7 +7271,8 @@ pub trait WriterContext { fn done(&mut self) -> Result<(), bun_core::Error> { Ok(()) } - // TODO(port): copyFileRange optional method (`@hasDecl` check in Zig) + // Zig's NewWriter probed `@hasDecl(ContextType, "copyFileRange")`, but no + // context ever declared it, so the optional method is omitted here. } /// Abstracted writer interface used by `Printer` (the methods Printer calls on `p.writer`). @@ -7440,7 +7306,9 @@ pub trait WriterTrait { StdWriterAdapter(self) } fn take_buffer(&mut self) -> MutableString; - // TODO(port): get_mutable_buffer / ctx access for source-map chunk generation + // No `ctx`/`get_mutable_buffer` accessor: source-map chunk generation reads + // the written bytes through `slice()` (same buffer Zig reached via + // `writer.ctx.getWritten()`). } pub struct StdWriterAdapter<'a, W: ?Sized>(&'a mut W); @@ -7478,21 +7346,9 @@ impl Writer { Ok(bytes.len()) } - pub fn is_copy_file_range_supported() -> bool { - // TODO(port): @hasDecl(ContextType, "copyFileRange") - false - } - - pub fn copy_file_range( - ctx: C, - in_file: Fd, - start: usize, - end: usize, - ) -> Result<(), bun_core::Error> { - // TODO(port): ctx.sendfile(in_file, start, end) - let _ = (ctx, in_file, start, end); - Ok(()) - } + // Zig's `isCopyFileRangeSupported`/`copyFileRange` are not ported: they + // keyed off `@hasDecl(ContextType, "copyFileRange")`, which no context + // (Zig or Rust) ever declared, so both were dead code. pub fn get_mutable_buffer(&mut self) -> &mut MutableString { self.ctx.get_mutable_buffer() @@ -7708,7 +7564,10 @@ pub struct BufferWriter { /// reslice on read (`written()` / `written_without_trailing_zero()`). Avoids the O(n) /// `to_vec().into_boxed_slice()` copy the previous port did on every `done()`. pub written_len: usize, - pub sentinel: &'static bun_core::ZStr, // TODO(port): lifetime — Zig stored a sentinel slice into `buffer` + // Zig also stored `sentinel: [:0]const u8` (a self-borrow into `buffer` set + // by `done()` when `append_null_byte` is true). Nothing ever read it, and + // Rust can't hold a self-borrowing field, so it is dropped; `done()` still + // appends the NUL terminator itself. pub append_null_byte: bool, pub append_newline: bool, } @@ -7735,7 +7594,6 @@ impl BufferWriter { BufferWriter { buffer: MutableString::init_empty(), written_len: 0, - sentinel: bun_core::ZStr::EMPTY, append_null_byte: false, append_newline: false, } @@ -7750,7 +7608,6 @@ impl BufferWriter { BufferWriter { buffer: MutableString::init(capacity).unwrap_or_else(|_| MutableString::init_empty()), written_len: 0, - sentinel: bun_core::ZStr::EMPTY, append_null_byte: false, append_newline: false, } @@ -7834,11 +7691,21 @@ impl BufferWriter { } if self.append_null_byte { - // TODO(port): self.sentinel = self.buffer.slice_with_sentinel() — borrows buffer - self.written_len = self.buffer.list.len(); - } else { - self.written_len = self.buffer.list.len(); + // Zig: `ctx.sentinel = ctx.buffer.sliceWithSentinel()` — which + // appends a NUL unless the buffer already ends with one, then + // `ctx.written = ctx.buffer.slice()` (the NUL is *included* in + // `written`; consumers strip it via + // `written_without_trailing_zero`). + // + // Deliberate divergence: for an *empty* buffer we still append + // the NUL. Zig's `sliceWithSentinel` skipped the append when + // `len == 0` and then underflowed its `len - 1` sentinel slice — + // a latent bug; appending here is strictly safer. + if self.buffer.list.last().copied() != Some(0) { + self.buffer.append_char(0)?; + } } + self.written_len = self.buffer.list.len(); Ok(()) } @@ -7943,13 +7810,12 @@ impl GenerateSourceMap { // former `__gated_entry_points` wrapper has been flattened away). // `print_ast` is live (borrowck reshape: `opts` re-reads routed through // `printer.options`, `*mut Symbol` for `must_not_be_renamed`, raw-ptr -// `Scope.parent` backref). `print_json` remains individually re-gated on -// lower-tier surface (see TODO(port) markers inline). +// `Scope.parent` backref). `print_json` is live as well. // ─────────────────────────────────────────────────────────────────────────── use self::__gated_printer::{Printer, slice_of}; use js_ast::Ast; -// PORT NOTE: Zig had `comptime generate_source_map`; Rust's `generic_const_exprs` +// Zig had `comptime generate_source_map`; Rust's `generic_const_exprs` // can't compute a non-`bool` const-generic from a `bool` const-generic without // viral `where` clauses, and the body only does runtime branches anyway. The // `IS_BUN_PLATFORM` axis stays const so `prepend_count` is still a compile-time @@ -7973,7 +7839,7 @@ pub fn get_source_map_builder( cover_lines_without_mappings: true, approximate_input_line_count: tree.approximate_newline_count, prepend_count: IS_BUN_PLATFORM && generate_source_map == GenerateSourceMap::Lazy, - // PORT NOTE: Zig copied `opts.line_offset_tables orelse generate(...)` + // Zig copied `opts.line_offset_tables orelse generate(...)` // by value (shallow copy of the unmanaged `MultiArrayList` header). // `Options.line_offset_tables` is now a borrow into shared linker // state; mirror Zig's bitwise copy via `ptr::read` into a @@ -8032,17 +7898,17 @@ pub fn print_ast<'a, W: WriterTrait, const ASCII_ONLY: bool, const GENERATE_SOUR let _restore = bun_crash_handler::scoped_action(bun_crash_handler::Action::Print(source.path.text)); - // PORT NOTE: Zig declared `renamer`/`no_op_renamer` undefined and assigned per + // Zig declared `renamer`/`no_op_renamer` undefined and assigned per // branch. `Renamer<'r,'src>` is invariant in `'src` (it holds `&'r mut // NoOpRenamer<'src>`), so the two arms must agree on `'src`; constructing the // `MinifyRenamer` variant inline (rather than via `to_renamer() -> // Renamer<'static,'static>`) lets inference unify it with the no-op arm. let mut no_op_renamer; - // PORT NOTE: hoisted out of the `minify_identifiers` arm so the + // hoisted out of the `minify_identifiers` arm so the // `&'r mut MinifyRenamer` borrow stored in `renamer` outlives the branch. let mut minify_renamer; let renamer: rename::Renamer<'_, '_>; - // PORT NOTE: Zig copied `tree.module_scope` to a stack local and re-pointed + // Zig copied `tree.module_scope` to a stack local and re-pointed // children's `parent` at the local. `Scope` isn't `Copy` here and the only // consumer (`compute_reserved_names_for_scope`) walks `members`/`generated`/ // `children` — never `parent` — so we re-point at the in-place @@ -8074,7 +7940,7 @@ pub fn print_ast<'a, W: WriterTrait, const ASCII_ONLY: bool, const GENERATE_SOUR let module_ref = tree.module_ref; let parts = &tree.parts; - // PORT NOTE: `symbols` was moved into `minify_renamer`; reach it through + // `symbols` was moved into `minify_renamer`; reach it through // the renamer for the post-init `must_not_be_renamed` pass (Zig held a // by-value copy). let dont_break_the_code = [tree.module_ref, tree.exports_ref, tree.require_ref]; @@ -8169,7 +8035,7 @@ pub fn print_ast<'a, W: WriterTrait, const ASCII_ONLY: bool, const GENERATE_SOUR // via `OwnedLineOffsetTables::Drop`). No caller of `print_ast` supplies a // precomputed table. printer.was_lazy_export = tree.has_lazy_export; - // PORT NOTE: borrowck reshape — `opts` was moved into `Printer::init`; mirror + // borrowck reshape — `opts` was moved into `Printer::init`; mirror // Zig's post-init `printer.module_info = opts.module_info` by taking it back // out of `printer.options` (see `print_with_writer_and_platform`). if PrinterType::::MAY_HAVE_MODULE_INFO { @@ -8224,7 +8090,7 @@ pub fn print_ast<'a, W: WriterTrait, const ASCII_ONLY: bool, const GENERATE_SOUR let mut source_maps_chunk: Option = if GENERATE_SOURCE_MAP { if printer.options.source_map_handler.is_some() { - // PORT NOTE: Zig used `printer.writer.ctx.getWritten()`; WriterTrait + // Zig used `printer.writer.ctx.getWritten()`; WriterTrait // exposes the same buffer via `slice()` (cf. print_with_writer_and_platform). Some( printer @@ -8280,7 +8146,7 @@ pub fn print_json( // NewPrinter(ascii_only=false, Writer, rewrite_esm_to_cjs=false, is_bun_platform=false, is_json=true, generate_source_map=false) type PrinterType<'a, W> = Printer<'a, W, false, false, false, true, false>; let writer = _writer; - // PORT NOTE: Zig built a throwaway `Ast.initTest(&parts)` (wrapping `expr` in + // Zig built a throwaway `Ast.initTest(&parts)` (wrapping `expr` in // an `S.SExpr`/Part) solely so the printer could read its default-empty // `import_records` and `symbols` for the no-op renamer; the body then calls // `printExpr(expr, ...)` directly without ever walking those parts. Rust @@ -8421,7 +8287,7 @@ pub fn print_with_writer_and_platform< source_map_builder, ); printer.was_lazy_export = ast.has_lazy_export; - // PORT NOTE: `Printer::init` already moved `opts.module_info` (it's a field of + // `Printer::init` already moved `opts.module_info` (it's a field of // `Options`); re-assigning it post-construction (as Zig does) would be a // use-after-move in Rust. The field already lives on `printer.options.module_info` // and `printer.module_info` was set to `None` by `init`, so mirror Zig by @@ -8466,8 +8332,8 @@ pub fn print_with_writer_and_platform< return PrintResult::Err(err); } - // TODO(port): need ctx accessor on WriterTrait for getWritten() - let written = printer.writer.slice(); // PORT NOTE: Zig used printer.writer.ctx.getWritten() + // Zig used `printer.writer.ctx.getWritten()`; `slice()` exposes the same buffer. + let written = printer.writer.slice(); let source_map: Option = if GENERATE_SOURCE_MAPS { 'brk: { if written.is_empty() || printer.source_map_builder.source_map.should_ignore() { diff --git a/src/js_printer/renamer.rs b/src/js_printer/renamer.rs index b0a22ac298d4..f3a2c53ab4b9 100644 --- a/src/js_printer/renamer.rs +++ b/src/js_printer/renamer.rs @@ -97,7 +97,7 @@ pub(crate) type NameCountMap = bun_collections::hashbrown::HashMap; pub struct NoOpRenamer<'a> { - // PORT NOTE: Zig `Symbol.Map` is a non-owning `BabyList(BabyList(Symbol))` + // Zig `Symbol.Map` is a non-owning `BabyList(BabyList(Symbol))` // slice header passed by value (renamer.zig:2,126,452 — no `deinit` ever // frees it). In the Rust port `symbol::Map` is `Vec>` (owning). // Unlike `MinifyRenamer`/`NumberRenamer` (which the bundler builds over a @@ -135,9 +135,12 @@ impl<'a> NoOpRenamer<'a> { // SAFETY: `original_name` is an AST-arena slice that outlives the renamer. symbol.original_name.slice() } else { - // TODO(port): include `self.source.path.text` once `bun_paths::fs::Path<'static>` - // exposes the text accessor. - Output::panic(format_args!("Invalid symbol {}", ref_)); + // Zig: `Output.panic("Invalid symbol {f} in {s}", .{ ref, renamer.source.path.text })`. + Output::panic(format_args!( + "Invalid symbol {} in {}", + ref_, + String::from_utf8_lossy(self.source.path.text) + )); } } @@ -146,7 +149,7 @@ impl<'a> NoOpRenamer<'a> { } } -// PORT NOTE: two lifetime params — `'r` is the borrow of the underlying renamer, +// Two lifetime params — `'r` is the borrow of the underlying renamer, // `'src` is `NoOpRenamer`'s borrow of the `Source`. The Zig `Renamer` was a // tag+ptr union that erased both; using `&'a mut NoOpRenamer<'a>` would make // `'a` invariant and lock the source borrow to the renamer borrow. @@ -182,7 +185,7 @@ impl<'r, 'src> Renamer<'r, 'src> { } } -// PORT NOTE: Zig `Renamer.deinit` freed NumberRenamer/MinifyRenamer internals. +// Zig `Renamer.deinit` freed NumberRenamer/MinifyRenamer internals. // In Rust all three variants are `&'r mut` (caller-owned, Drop on caller's // storage). No explicit deinit needed. @@ -289,8 +292,8 @@ impl Drop for MinifyRenamer { } } -// TODO(port): Zig used `std.HashMapUnmanaged(Ref, usize, RefCtx, 80)` — -// bun_collections::HashMap should be parameterized with RefCtx hasher. +// Zig used `std.HashMapUnmanaged(Ref, usize, RefCtx, 80)`; the default hasher +// here differs only in hash quality/speed, not behavior. pub(crate) type TopLevelSymbolSlotMap = HashMap; impl MinifyRenamer { @@ -356,7 +359,7 @@ impl MinifyRenamer { symbol_uses: &js_ast::part::SymbolUseMap, stable_source_indices: &[u32], ) -> Result<(), bun_alloc::AllocError> { - // PORT NOTE: ArrayHashMap exposes parallel keys()/values() slices, no .iter(). + // ArrayHashMap exposes parallel keys()/values() slices, no .iter(). for (key, value) in symbol_uses.keys().iter().zip(symbol_uses.values().iter()) { self.accumulate_symbol_use_count( top_level_symbols, @@ -415,7 +418,7 @@ impl MinifyRenamer { ) -> Result<(), bun_alloc::AllocError> { for stable in top_level_symbols { let symbol: &Symbol = self.symbols.get_const(stable.ref_).unwrap(); - // PORT NOTE: reshaped for borrowck — capture symbol fields before mut-borrowing slots + // Reshaped for borrowck — capture symbol fields before mut-borrowing slots let ns = symbol.slot_namespace(); let must_start_with_capital = symbol.must_start_with_capital_letter_for_jsx; let slots = &mut self.slots[ns]; @@ -530,7 +533,7 @@ impl StableSymbolCount { } } -// PORT NOTE: Zig `packed struct(u64)`. Packed layout is not load-bearing here +// Zig `packed struct(u64)`. Packed layout is not load-bearing here // (never bitcast/FFI — only sorted in a local Vec), so two named u32 fields // instead of a #[repr(transparent)] u64 with shift accessors. #[repr(C)] @@ -548,7 +551,7 @@ impl SlotAndCount { } pub struct NumberRenamer { - // PORT NOTE: see `NoOpRenamer.symbols` — non-owning view; Zig + // See `NoOpRenamer.symbols` — non-owning view; Zig // `NumberRenamer.deinit` (renamer.zig:462) never frees `symbols`. pub symbols: ManuallyDrop, pub names: Box<[Vec]>, @@ -637,10 +640,15 @@ impl NumberRenamer { root.name_counts.insert(NameKey(NameStr::new(duped)), value); } - // TODO(b2-blocked): bun_core::env_var::BUN_DUMP_SYMBOLS — typed accessor - // not yet declared upstream; debug-only `symbols.dump()` call elided. + // Zig (renamer.zig:535-538): debug-only, presence-checked symbol dump. + // The Zig gated on `!isWindows` only because it used `std.posix.getenv`; + // the typed accessor works everywhere. + #[cfg(debug_assertions)] + if bun_core::env_var::BUN_DUMP_SYMBOLS.get().is_some() { + symbols.dump(); + } - // PORT NOTE: Zig @memset(sliceAsBytes(names), 0) — Vec::default() is already zeroed. + // Zig @memset(sliceAsBytes(names), 0) — Vec::default() is already zeroed. Ok(Box::new(NumberRenamer { symbols: ManuallyDrop::new(symbols), @@ -668,7 +676,7 @@ impl NumberRenamer { self.assign_names_recursive_with_number_scope(s, scope, source_index, sorted); - // PORT NOTE: Zig `defer { s.deinit(); pool.put(s) }` — fn is infallible, + // Zig `defer { s.deinit(); pool.put(s) }` — fn is infallible, // so no scopeguard needed; cleanup runs unconditionally below. // SAFETY: s came from number_scope_pool.get() and was initialized above; // `put` drops `name_counts` in place before recycling the slot. @@ -756,7 +764,7 @@ impl NumberRenamer { self.assign_names_recursive_with_number_scope(s, child, source_index, sorted); } - // PORT NOTE: Zig (renamer.zig:594-598) only put the final `s` because + // Zig (renamer.zig:594-598) only put the final `s` because // both the pool fallback (`.init(renamer.arena.allocator())`) and // `name_counts` data lived in arenas bulk-freed by `NumberRenamer.deinit // -> arena.deinit()`. The Rust port moved both to the global heap @@ -779,8 +787,10 @@ impl NumberRenamer { } pub fn add_top_level_symbol(&mut self, ref_: Ref) { - // PORT NOTE: reshaped for borrowck — root is a field of self - // TODO(port): self.assign_name needs &mut self AND &mut self.root simultaneously + // Reshaped for borrowck — root is a field of self, but `assign_name` + // needs `&mut self` AND `&mut self.root` simultaneously. Sound only + // while `assign_name` never reaches `self.root` through `self`; keep + // that invariant if `assign_name` changes. let root: *mut NumberScope = &raw mut self.root; // SAFETY: assign_name does not touch self.root through `self` self.assign_name(unsafe { &mut *root }, ref_); @@ -894,7 +904,7 @@ pub enum UnusedName { /// the exact condition under which Zig's `ensureValidIdentifier` returns the /// input slice unchanged (modulo the strict-mode-reserved-word remap, handled /// by the caller). The Rust port of that function currently always allocates -/// a `Box<[u8]>` even on the borrow path — see its `TODO(port)` — so hoisting +/// a `Box<[u8]>` even on the borrow path, so hoisting /// this check into the renamer restores Zig's zero-alloc behaviour for the /// overwhelmingly common case (`symbol.original_name` is parser-produced and /// almost always satisfies this). @@ -917,7 +927,7 @@ fn is_simple_ascii_identifier(s: &[u8]) -> bool { impl NumberScope { /// Caller must use an arena allocator pub fn find_unused_name(&mut self, arena: &Bump, input_name: &[u8]) -> UnusedName { - // PORT NOTE: Zig's `MutableString.ensureValidIdentifier` borrows the + // Zig's `MutableString.ensureValidIdentifier` borrows the // input when it is already a valid ASCII identifier; the Rust port // always heap-allocates (Box<[u8]>). Skip the call entirely for the // common case so this stays alloc-free, matching the .zig fast path. @@ -937,7 +947,7 @@ impl NumberScope { owned_name = MutableString::ensure_valid_identifier(input_name).expect("unreachable"); &owned_name }; - // PORT NOTE: hoisted from inside the match arm so `name` (which may borrow + // Hoisted from inside the match arm so `name` (which may borrow // it) stays valid through the trailing dupe. let mut mutable_name = MutableString::init_empty(); // True iff a "name2"/"name3" suffix was appended below (i.e. `name` was @@ -1098,12 +1108,12 @@ impl ExportRenamer { .expect("unreachable"); tries += 1; let attempt: &[u8] = self.string_buffer.slice(); - // PORT NOTE: reshaped for borrowck — `get_or_put` borrows `self.used` + // Reshaped for borrowck — `get_or_put` borrows `self.used` // mutably, so allocate the arena copy first. let to_use: &[u8] = self.arena.alloc_slice_copy(attempt); let entry = self.used.get_or_put(to_use).expect("unreachable"); if !entry.found_existing { - // PORT NOTE: `StringHashMap` owns a boxed copy of the key on + // `StringHashMap` owns a boxed copy of the key on // insert; the Zig key-ptr write is unnecessary. *entry.value_ptr = tries; @@ -1118,14 +1128,13 @@ impl ExportRenamer { *entry.value_ptr = tries; } - // PORT NOTE: Zig returned `entry.key_ptr.*` (the map's owned copy of `input`). + // Zig returned `entry.key_ptr.*` (the map's owned copy of `input`). // `StringHashMap` does not expose a key pointer; allocate a copy in `self.arena` // so the returned slice is tied to `&self` (sub-borrow of `&mut self`). self.arena.alloc_slice_copy(input) } pub fn next_minified_name(&mut self) -> Result, bun_core::Error> { - // TODO(port): narrow error set let name = js_ast::NameMinifier::default_number_to_minified_name(self.count)?; self.count += 1; Ok(name) @@ -1194,7 +1203,7 @@ pub fn compute_reserved_names_for_scope( symbols: &symbol::Map, names: &mut StringHashMap, ) { - // PORT NOTE: Zig copied `names_.*` to a local and wrote back via defer. + // Zig copied `names_.*` to a local and wrote back via defer. // In Rust we mutate through &mut directly. for member in scope.members.values() { diff --git a/src/jsc/AbortSignal.rs b/src/jsc/AbortSignal.rs index e4f7d1d25b3c..35620d0f7090 100644 --- a/src/jsc/AbortSignal.rs +++ b/src/jsc/AbortSignal.rs @@ -23,8 +23,6 @@ bun_opaque::opaque_ffi! { pub struct AbortSignal; } -// TODO(port): move to jsc_sys -// // `AbortSignal` and `JSGlobalObject` are opaque `UnsafeCell`-backed ZST // handles, so `&AbortSignal` is ABI-identical to a non-null `AbortSignal*` // and C++ mutating through it (refcount, listener list, abort flag) is @@ -158,11 +156,12 @@ impl AbortSignal { self.unref(); } + /// Lifetime: the returned pointer is borrowed from the JS wrapper and is + /// valid only while `value` remains reachable. Use [`AbortSignal::ref_from_js`] + /// to take refcounted ownership instead. pub fn from_js(value: JSValue) -> Option<*mut AbortSignal> { let ptr = WebCore__AbortSignal__fromJS(value); if ptr.is_null() { None } else { Some(ptr) } - // TODO(port): lifetime — returned ptr is borrowed from the JS wrapper; - // valid only while the JSValue is reachable. } pub fn to_js(&self, global: &JSGlobalObject) -> JSValue { @@ -188,8 +187,6 @@ impl AbortSignal { /// to `this` for the duration (e.g., `this.ref_(); defer this.unref();`) and avoid /// caching the pointer across turns. pub fn get_timeout(&self) -> Option<&Timeout> { - // TODO(port): lifetime — callers that run/cancel/deinit need `*mut`; revisit - // whether `&mut Timeout` (or raw ptr) is the right shape once call sites port. let ptr = WebCore__AbortSignal__getTimeout(self); // SAFETY: returned Timeout is owned by `self` and valid while `self` is held // (see doc comment). @@ -256,7 +253,7 @@ impl AbortReason { } } - // PORT NOTE (phase-d): `to_body_value_error` reaches into + // `to_body_value_error` reaches into // `bun_runtime::webcore::body::value::ValueError` (forward dep on // `bun_runtime`). The conversion is trivial and is reconstructed at the // call-site in `bun_runtime` once that tier un-gates. @@ -283,7 +280,7 @@ pub struct Timeout { /// The `Timeout`'s lifetime is owned by the AbortSignal. /// But this does have a ref count increment. - // PORT NOTE: AbortSignal is an opaque C++ type with intrusive WebCore + // AbortSignal is an opaque C++ type with intrusive WebCore // refcounting (ref/unref) that crosses FFI — PORTING.md §Pointers: never // Arc here. Kept as raw `*mut` with manual unref (matches Zig). pub signal: *mut AbortSignal, @@ -304,8 +301,8 @@ impl Timeout { let deadline = bun_core::Timespec::now_allow_mocked_time() .add_ms(i64::try_from(milliseconds).expect("AbortSignal.timeout(ms) overflows i64")); - // PORT NOTE: `bun.TrivialNew` → `heap::alloc(Box::new(...))` (mimalloc - // is the global allocator per PORTING.md §Prereq). + // Zig `bun.TrivialNew` → `heap::into_raw(Box::new(...))` (mimalloc is + // the global allocator). let this: *mut Timeout = bun_core::heap::into_raw(Box::new(Timeout { event_loop_timer: EventLoopTimer { next: ElTimespec { @@ -373,7 +370,7 @@ impl Timeout { } // Dispatching the signal may cause the Timeout to get freed. - // PORT NOTE: capture raw ptr before `this` may dangle. + // Capture the raw ptr before `this` may dangle. let signal_ptr: *mut AbortSignal = (*this).signal; Self::dispatch(vm, signal_ptr); } @@ -382,7 +379,7 @@ impl Timeout { fn dispatch(vm: *mut VirtualMachine, signal_ptr: *mut AbortSignal) { let _ = vm; let vm = VirtualMachine::get(); - // PORT NOTE: `loop.enter(); defer loop.exit();` — RAII guard `exit`s on + // Zig `loop.enter(); defer loop.exit();` — RAII guard `exit`s on // drop even if `signal` unwinds, and holds the raw VM-owned pointer so // borrowck doesn't see two live `&mut EventLoop` across re-entrant JS. let _guard = vm.enter_event_loop_scope(); @@ -394,7 +391,7 @@ impl Timeout { } // This may run inside the "signal" call. - // PORT NOTE: not `impl Drop` — Timeout is constructed/destroyed across FFI + // Deliberately not `impl Drop` — Timeout is constructed/destroyed across FFI // (see export fns below) and `deinit` needs a `vm` parameter. unsafe fn deinit(this: *mut Timeout, vm: *mut VirtualMachine) { // SAFETY: caller guarantees `this` came from `heap::alloc` in `init`. diff --git a/src/jsc/AsyncModule.rs b/src/jsc/AsyncModule.rs index 3c1c472dc593..1ceceba7cd8a 100644 --- a/src/jsc/AsyncModule.rs +++ b/src/jsc/AsyncModule.rs @@ -51,7 +51,7 @@ pub struct AsyncModule { referrer_len: u32, specifier_len: u32, pub fd: Option, - // PORT NOTE: `?*PackageJSON` / `*JSGlobalObject` — both are VM-lifetime + // `?*PackageJSON` / `*JSGlobalObject` — both are VM-lifetime // backrefs (BACKREF/JSC_BORROW class in LIFETIMES.tsv). `package_json` is // stored as a raw ptr so `AsyncModule` is `'static`-embeddable in // `Queue`/`VirtualMachine` without a phantom lifetime; `global_this` uses @@ -157,7 +157,7 @@ impl AsyncModule { jsc::mark_binding(); let mut specifier = specifier_; let mut referrer = referrer_; - // PORT NOTE: Zig `defer { specifier.deref(); referrer.deref(); scope.deinit(); }` — + // Zig `defer { specifier.deref(); referrer.deref(); scope.deinit(); }` — // BunString is `Copy` in the Rust port (no Drop), so deref the held // refcounts explicitly via scopeguard. The `TopExceptionScope` is // omitted: `from_js_host_call_generic` already checks the VM for a @@ -184,7 +184,7 @@ impl AsyncModule { let mut errorable: ErrorableResolvedSource; if let Some(e) = err { - // PORT NOTE: inner Zig `defer { if (needs_deref) { needs_deref = false; + // inner Zig `defer { if (needs_deref) { needs_deref = false; // source_code.deref(); } }` — `OwnedString` derefs on Drop at the end // of this `if` arm; `None` is the no-op path. let _source_code_guard = if resolved_source.source_code_needs_deref { @@ -206,7 +206,7 @@ impl AsyncModule { // Error/AggregateError from the parser log and writes it into // `errorable.result.err.value`. Without this the import promise // would reject with `undefined` (ModuleLoader.cpp:473). - // PORT NOTE: call the `virtual_machine` impl directly (takes + // call the `virtual_machine` impl directly (takes // `&JSGlobalObject`) instead of the `module_loader` shim that // takes `*mut` — avoids a `&T as *const T as *mut T` cast, // which is UB-adjacent under Stacked Borrows even when the @@ -224,9 +224,9 @@ impl AsyncModule { } else { errorable = ErrorableResolvedSource::ok(*resolved_source); } - // TODO(port): Zig calls log.deinit() here explicitly (early), then uses - // specifier after. In Rust, caller owns `log`; we leave it to caller's - // Drop. Verify no behavioral diff. + // Zig calls log.deinit() here explicitly (early), then uses specifier + // after. In Rust the caller owns `log` and its Drop runs at caller scope + // exit — only the free is deferred; nothing observable changes. bun_core::scoped_log!(AsyncModule, "fulfill: {}", specifier); @@ -242,14 +242,12 @@ impl AsyncModule { } } -// PORT NOTE: pub fn deinit → impl Drop. Body only freed owned fields (promise, +// pub fn deinit → impl Drop. Body only freed owned fields (promise, // parse_result, arena, string_buf), all of which now have Drop impls on their // Rust types. No explicit Drop needed; relying on field Drop order. // bun.default_allocator.free(this.stmt_blocks); // bun.default_allocator.free(this.expr_blocks); -// TODO(port): move to _sys -// // safe: `JSGlobalObject` is an opaque `UnsafeCell`-backed ZST handle (`&` is // ABI-identical to non-null `*const`); `ErrorableResolvedSource`/`BunString` // are `#[repr(C)]` payloads whose `&mut` is exclusive for the call. C++ reads @@ -317,7 +315,7 @@ impl Queue { bun_io::AllocatorType::Js, )); - // PORT NOTE: allocator arg dropped (Vec uses global mimalloc). + // allocator arg dropped (Vec uses global mimalloc). self.map.push(module); // PERF(port): was assume_capacity-free append self.vm().package_manager().drain_dependency_list(); @@ -340,11 +338,11 @@ impl Queue { bstr::BStr::new(this.vm().package_manager().lockfile.str(&dependency.name)) ); - // PORT NOTE: reshaped for borrowck — Zig iterated copies and + // reshaped for borrowck — Zig iterated copies and // compacted in place; Rust uses retain_mut and lets Drop free // removed modules. this.map.retain_mut(|module| { - // PORT NOTE: Zig `MultiArrayList.items(.root_dependency_id)` → + // Zig `MultiArrayList.items(.root_dependency_id)` → // `Vec` field walk. for pending in module.parse_result.pending_imports.iter() { if pending.root_dependency_id != root_dependency_id { @@ -355,7 +353,7 @@ impl Queue { // `container_of`-derived `*mut`; provenance is the original // allocation, disjoint from the `&mut module` borrow above. let vm = VirtualMachine::get().as_mut(); - // PORT NOTE: reshaped for borrowck — `lockfile.str()` ties the + // reshaped for borrowck — `lockfile.str()` ties the // returned slice to `&vm`, which conflicts with passing // `&mut vm` to `resolve_error`. The lockfile string buffer is // stable across `resolve_error` (no realloc on the error @@ -403,7 +401,7 @@ impl Queue { } pub fn run_tasks(&mut self) { - // PORT NOTE: reshaped for borrowck — Zig held `pm` across the call + // reshaped for borrowck — Zig held `pm` across the call // while passing `this` (which can recover `pm` via // `@fieldParentPtr`). The Rust `run_tasks` free fn takes both // `&mut PackageManager` and `&mut Queue`; the package manager is a @@ -435,9 +433,9 @@ impl Queue { bstr::BStr::new(name) ); - // PORT NOTE: reshaped for borrowck — compaction loop → retain_mut. + // reshaped for borrowck — compaction loop → retain_mut. self.map.retain_mut(|module| { - // PORT NOTE: Zig `MultiArrayList.items(.tag)` etc. → + // Zig `MultiArrayList.items(.tag)` etc. → // `Vec` field walk. for pending in module.parse_result.pending_imports.iter() { if pending.tag == bun_resolver::PendingResolutionTag::Resolve { @@ -498,9 +496,9 @@ impl Queue { .as_slice(), ); - // PORT NOTE: reshaped for borrowck — compaction loop → retain_mut. + // reshaped for borrowck — compaction loop → retain_mut. self.map.retain_mut(|module| { - // PORT NOTE: Zig `MultiArrayList.items(.import_record_id)` / + // Zig `MultiArrayList.items(.import_record_id)` / // `.items(.root_dependency_id)` → `Vec` field // walk. for pending in module.parse_result.pending_imports.iter() { @@ -537,7 +535,7 @@ impl Queue { return; } - // PORT NOTE: reshaped for borrowck — Zig compacted by index then + // reshaped for borrowck — Zig compacted by index then // truncated `items.len` without running deinit on finished slots. Rust // walks by index and `remove(i)` finished modules by value into // `done(self)`, so each module's owned fields are dropped exactly once @@ -546,7 +544,7 @@ impl Queue { while i < self.map.len() { let (done_count, tags_len) = { let module = &mut self.map[i]; - // PORT NOTE: Zig `MultiArrayList.items(.tag)` / + // Zig `MultiArrayList.items(.tag)` / // `.items(.root_dependency_id)` → `Vec` // field walk via `iter_mut()`. let pending_imports = &mut module.parse_result.pending_imports; @@ -592,7 +590,7 @@ impl Queue { let mut name_and_version_hash: Option = None; let mut patchfile_hash: Option = None; - // PORT NOTE: Zig passed `pm.lockfile` as a separate arg; + // Zig passed `pm.lockfile` as a separate arg; // the Rust port collapsed it onto `&mut self.lockfile` // (PackageManagerLifecycle.rs) to avoid the // `&mut self`/`&self.lockfile` aliasing borrowck rejects. @@ -647,7 +645,7 @@ impl AsyncModule { ) -> Result { // var stmt_blocks = js_ast.Stmt.Data.toOwnedSlice(); // var expr_blocks = js_ast.Expr.Data.toOwnedSlice(); - // PORT NOTE: `JSInternalPromise` aliases `JSPromise` upstream + // `JSInternalPromise` aliases `JSPromise` upstream // (JSInternalPromise.rs), so `JSPromise::create` is the // `createInternalPromise` equivalent. let this_promise = crate::JSPromise::create(global_object).to_js(); @@ -663,7 +661,7 @@ impl AsyncModule { unsafe { *opts.promise_ptr.unwrap() = this_promise.as_promise().unwrap(); } - // PORT NOTE: Zig kept three aliasing slices into `buf` plus + // Zig kept three aliasing slices into `buf` plus // `buf.allocatedSlice()` as the owning storage. Rust can't store // self-referential borrows, so capture lengths and pack // `referrer ++ specifier ++ path.text` into `string_buf`, then expose @@ -698,7 +696,7 @@ impl AsyncModule { } pub fn done(self, jsc_vm: &mut VirtualMachine) { - // PORT NOTE: Zig `allocator.create` + bitwise copy then truncated the + // Zig `allocator.create` + bitwise copy then truncated the // queue without running deinit on the discarded slot — single // ownership transfers to the heap clone. In Rust the caller // (`Queue::poll_modules`) removes the element by value and passes it @@ -710,7 +708,7 @@ impl AsyncModule { // task queue until on_done reclaims it via heap::take; we hold // the only reference here. unsafe { - // PORT NOTE: Zig `AnyTask.New(AsyncModule, onDone).init(clone)` — + // Zig `AnyTask.New(AsyncModule, onDone).init(clone)` — // Rust cannot take a fn as const generic, so hand-write the shim // (option (b) in event_loop/AnyTask.rs). (*clone).any_task = AnyTask::AnyTask { @@ -757,7 +755,7 @@ impl AsyncModule { global_this.take_error(JsError::Thrown), ), Err(err) => { - // PORT NOTE: Zig declared `errorable = undefined` and relied on + // Zig declared `errorable = undefined` and relied on // `processFetchLog` writing the out-param. Rust pre-seeds the // err so the `&mut` borrow is definitely-initialized; // `process_fetch_log` overwrites `result.err.value`. @@ -791,9 +789,9 @@ impl AsyncModule { drop(unsafe { bun_core::heap::take(this) }); } - // TODO(port): narrow error set to bun_alloc::AllocError — Zig body only - // `try`s std.fmt.allocPrint (OOM-only). write! into Vec is - // infallible here; `.ok()` collapses the `fmt::Result`. + // Zig body only `try`s std.fmt.allocPrint (OOM-only). write! into Vec + // is infallible here; `.ok()` collapses the `fmt::Result`, so this never + // actually returns Err — the wide Result is kept for call-site uniformity. fn resolve_error( &mut self, vm: &mut VirtualMachine, @@ -865,7 +863,7 @@ impl AsyncModule { .ok(); } else if e == bun_core::err!("DistTagNotFound") || e == bun_core::err!("NoMatchingVersion") { - // PORT NOTE: Zig peeks at the tagged-union via + // Zig peeks at the tagged-union via // `result.version.tag == .npm and // result.version.value.npm.version.isExact()`. The Rust // `Version::try_npm()` performs the tag guard and yields the @@ -997,7 +995,7 @@ impl AsyncModule { self.poll_ref.unref(bun_io::posix_event_loop::get_vm_ctx( bun_io::AllocatorType::Js, )); - // PORT NOTE: Zig called `this.deinit()` here; in Rust the caller + // Zig called `this.deinit()` here; in Rust the caller // (Queue::retain_mut) returns `false` and Vec drops the element, // running Drop. // `JSInternalPromise` is an `opaque_ffi!` ZST handle; `opaque_mut` is @@ -1218,7 +1216,7 @@ impl AsyncModule { self.poll_ref.unref(bun_io::posix_event_loop::get_vm_ctx( bun_io::AllocatorType::Js, )); - // PORT NOTE: Zig called `this.deinit()` here; caller drops via + // Zig called `this.deinit()` here; caller drops via // retain_mut → false. // `JSInternalPromise` is an `opaque_ffi!` ZST handle; `opaque_mut` is // the centralised non-null deref proof. @@ -1236,7 +1234,7 @@ impl AsyncModule { "resumeLoadingModule: {}", bstr::BStr::new(self.specifier()) ); - // PORT NOTE: Zig copied `parse_result` by value, mutated, wrote + // Zig copied `parse_result` by value, mutated, wrote // back. Rust takes-by-value via `mem::take` then restores below to // satisfy borrowck around `linker.link(&mut parse_result)` while // `self` is also borrowed. @@ -1296,7 +1294,7 @@ impl AsyncModule { )?; } self.parse_result = parse_result; - // PORT NOTE: `print_with_source_map` consumes `ParseResult` by + // `print_with_source_map` consumes `ParseResult` by // value (it moves `ast` into `print_ast`). Hoist the post-print // reads (`is_commonjs_module` / `input_fd`) above the move so we // can `mem::take` instead of cloning. @@ -1306,7 +1304,7 @@ impl AsyncModule { let arena = *self.parse_result.ast.parts.allocator(); let parse_result = core::mem::replace(&mut self.parse_result, ParseResult::empty(arena)); - // PORT NOTE: `VirtualMachine.source_code_printer` is a thread-local + // `VirtualMachine.source_code_printer` is a thread-local // `?*BufferPrinter` (see `SOURCE_CODE_PRINTER`); Zig dereferenced to // copy by value (`var printer = source_code_printer.?.*`), reset, and // wrote back in a `defer`. `BufferPrinter` is `!Clone` in Rust, so @@ -1363,14 +1361,18 @@ impl AsyncModule { }?; } - #[cfg(feature = "dump_source")] - crate::runtime_transpiler_store::dump_source_string( - // SAFETY: `jsc_vm` is the live per-thread `VirtualMachine` (BACKREF, non-null). - unsafe { core::ptr::NonNull::new_unchecked(jsc_vm) }, - specifier, - printer.ctx.get_written(), - ); - // TODO(port): Environment.dump_source mapped to cfg feature; confirm flag name. + // Zig: `if (comptime Environment.dump_source)` — `bun_core::env::DUMP_SOURCE` + // is the Rust equivalent (debug, non-test builds only). The previous + // `cfg(feature = "dump_source")` gate referenced a feature that doesn't + // exist, which silently compiled this call out everywhere. + if bun_core::env::DUMP_SOURCE { + crate::runtime_transpiler_store::dump_source_string( + // SAFETY: `jsc_vm` is the live per-thread `VirtualMachine` (BACKREF, non-null). + unsafe { core::ptr::NonNull::new_unchecked(jsc_vm) }, + specifier, + printer.ctx.get_written(), + ); + } // SAFETY: per-thread VM. if unsafe { (*jsc_vm).is_watcher_enabled() } { @@ -1396,7 +1398,7 @@ impl AsyncModule { .bun_watcher .cast::() }; - // PORT NOTE: `bun_watcher::PackageJSON` is an opaque + // `bun_watcher::PackageJSON` is an opaque // forward-decl of `bun_resolver::PackageJSON`; // the watcher only stores the pointer, so cast through. // SAFETY: `package_json` (when set) is a VM-lifetime diff --git a/src/jsc/BuildMessage.rs b/src/jsc/BuildMessage.rs index 7ad2813c37f3..a3ff0b0edf97 100644 --- a/src/jsc/BuildMessage.rs +++ b/src/jsc/BuildMessage.rs @@ -13,7 +13,6 @@ use crate::{ pub struct BuildMessage { pub msg: bun_ast::Msg, // resolve_result: Resolver.Result, - // PORT NOTE: `std.mem.Allocator param` field dropped — global mimalloc. pub logged: Cell, } diff --git a/src/jsc/BunCPUProfiler.rs b/src/jsc/BunCPUProfiler.rs index a9cebbc9d554..1745789db921 100644 --- a/src/jsc/BunCPUProfiler.rs +++ b/src/jsc/BunCPUProfiler.rs @@ -18,8 +18,8 @@ pub(crate) enum ProfilerError { bun_core::named_error_set!(ProfilerError); pub struct CPUProfilerConfig { - // TODO(port): lifetime — these are borrowed slices in Zig (never freed here); - // using &'static per PORTING.md (no struct lifetime params). + // These are borrowed slices in Zig (never freed here); CLI-arg-backed and + // process-lifetime, so `&'static` is sound (no struct lifetime params). pub name: &'static [u8], pub dir: &'static [u8], pub md_format: bool, @@ -40,7 +40,6 @@ impl Default for CPUProfilerConfig { } // C++ function declarations -// TODO(port): move to jsc_sys unsafe extern "C" { /// `VM` is an opaque `UnsafeCell`-backed ZST handle; `&mut VM` is /// ABI-identical to a non-null `VM*`. @@ -69,7 +68,6 @@ pub(crate) fn stop_and_write_profile( vm: &mut VM, config: &CPUProfilerConfig, ) -> Result<(), ProfilerError> { - // TODO(port): narrow error set let mut json_string = BunString::empty(); let mut text_string = BunString::empty(); @@ -183,7 +181,7 @@ fn build_output_path( // Append directory if specified if !config.dir.is_empty() { - // PORT NOTE: AutoAbsPath uses CheckLength::ASSUME — Err arm is unreachable + // AutoAbsPath uses CheckLength::ASSUME — Err arm is unreachable // (Zig call is infallible). See paths/Path.rs `options::Result` note. path.join(&[config.dir]).expect("unreachable"); } diff --git a/src/jsc/BunHeapProfiler.rs b/src/jsc/BunHeapProfiler.rs index 8579f4ab7ee7..02382fdc4ced 100644 --- a/src/jsc/BunHeapProfiler.rs +++ b/src/jsc/BunHeapProfiler.rs @@ -6,9 +6,9 @@ use bun_sys::{self as sys, E, Fd, FdDirExt}; use crate::VM; pub struct HeapProfilerConfig { - // PORT NOTE: Zig held borrowed `[]const u8` for the process lifetime; the - // config originates from CLI args and lives until exit, so `&'static [u8]` - // matches the ownership exactly. + // The config originates from CLI args and lives until process exit, so + // `&'static [u8]` matches the ownership exactly (the Zig original held + // borrowed `[]const u8` for the process lifetime). pub name: &'static [u8], pub dir: &'static [u8], pub text_format: bool, @@ -55,8 +55,8 @@ pub fn generate_and_write_profile(vm: &mut VM, config: &HeapProfilerConfig) -> R ); // Write the profile to disk using bun.sys.File.writeFile - // PORT NOTE: reshaped for borrowck — `slice_z()` borrows `path_buf` mutably, - // so we re-derive it at each call site instead of holding a single binding. + // `slice_z()` borrows `path_buf` mutably, so we re-derive it at each call + // site instead of holding a single binding. #[cfg(windows)] let result = sys::File::write_file_os_path(Fd::cwd(), output_path_os, profile_slice.slice()); #[cfg(not(windows))] @@ -149,7 +149,6 @@ fn generate_default_filename(buf: &mut PathBuffer, text_format: bool) -> Result< .map_err(|_| err!(NoSpaceLeft))?; let remaining = cursor.len(); let written = total - remaining; - // PORT NOTE: reshaped for borrowck — recompute slice from buf after dropping cursor borrow. Ok(&buf.as_slice()[..written]) } diff --git a/src/jsc/CachedBytecode.rs b/src/jsc/CachedBytecode.rs index 4fef3e002bad..fabceabdee4a 100644 --- a/src/jsc/CachedBytecode.rs +++ b/src/jsc/CachedBytecode.rs @@ -34,7 +34,7 @@ unsafe extern "C" { } impl CachedBytecode { - // PORT NOTE: the returned `&'static [u8]` actually borrows from the + // SAFETY CONTRACT: the returned `&'static [u8]` actually borrows from the // `CachedBytecode` handle and is invalidated when `deref()` is called — // identical to the Zig `[]const u8` + `*CachedBytecode` pair. Callers own // the handle and must call `deref()` (or drop via `allocator()`) to free. @@ -119,7 +119,7 @@ impl CachedBytecode { // bytecode slice alongside an "allocator" whose `.free()` decrements the // CachedBytecode refcount. This is a Zig-specific ownership-tracking idiom. // -// PORT NOTE: the Zig `VTable.free` slot called `CachedBytecode__deref(ctx)` and +// The Zig `VTable.free` slot called `CachedBytecode__deref(ctx)` and // `VTable.alloc` panicked. The Rust `bun_alloc::Allocator` marker trait has no // `alloc`/`free` methods to dispatch through — so the "free → deref" semantics // cannot ride the trait object. Call sites that would have freed through this diff --git a/src/jsc/CallFrame.rs b/src/jsc/CallFrame.rs index 3740f3775cb1..26b768e4bf97 100644 --- a/src/jsc/CallFrame.rs +++ b/src/jsc/CallFrame.rs @@ -421,8 +421,6 @@ impl<'a> Drop for ArgumentsSlice<'a> { } } -// TODO(port): move to jsc_sys -// // `CallFrame`/`VM`/`JSGlobalObject` are opaque `UnsafeCell`-backed ZST handles; // `&T` is ABI-identical to non-null `*const T`. Out-params are exclusive `&mut` // to plain `#[repr(C)]` PODs. `describeFrame` returns a raw C string that the diff --git a/src/jsc/CommonAbortReason.rs b/src/jsc/CommonAbortReason.rs index 60813086cf10..5e0bf20cf85c 100644 --- a/src/jsc/CommonAbortReason.rs +++ b/src/jsc/CommonAbortReason.rs @@ -16,8 +16,6 @@ impl CommonAbortReasonExt for CommonAbortReason { } } -// TODO(port): move to jsc_sys -// // `JSGlobalObject` is an opaque `UnsafeCell`-backed ZST handle; C++ allocating // the JS error value through it is interior mutation invisible to Rust. unsafe extern "C" { diff --git a/src/jsc/CommonStrings.rs b/src/jsc/CommonStrings.rs index b9e6f5cee882..2c58dbf5fae8 100644 --- a/src/jsc/CommonStrings.rs +++ b/src/jsc/CommonStrings.rs @@ -26,7 +26,6 @@ enum CommonStringsForZig { BinaryTypeUint8Array = 12, } -// TODO(port): move to jsc_sys unsafe extern "C" { // `JSGlobalObject` is an opaque `UnsafeCell`-backed FFI handle; `&T` is // ABI-identical to non-null `*const T` and the C++ side's lazy init of its @@ -45,8 +44,8 @@ impl CommonStringsForZig { } impl<'a> CommonStrings<'a> { - // PORT NOTE: Zig had both `IPv4`/`IPv6` and `ipv4`/`ipv6` methods, which - // collide under snake_case. The lowercase Zig methods are renamed to + // Zig had both `IPv4`/`IPv6` and `ipv4`/`ipv6` methods, which collide + // under snake_case. The lowercase Zig methods are renamed to // `ipv4_lower`/`ipv6_lower` here (matching their enum variants). #[inline] pub fn ipv4(self) -> JSValue { @@ -56,14 +55,14 @@ impl<'a> CommonStrings<'a> { pub fn ipv6(self) -> JSValue { CommonStringsForZig::IPv6.to_js(self.global_object) } - // PORT NOTE: Zig `@"127.0.0.1"` — not a valid Rust identifier; renamed to - // match the enum variant. + // Zig `@"127.0.0.1"` — not a valid Rust identifier; renamed to match the + // enum variant. #[inline] pub fn in4_loopback(self) -> JSValue { CommonStringsForZig::IN4Loopback.to_js(self.global_object) } - // PORT NOTE: Zig `@"::"` — not a valid Rust identifier; renamed to match - // the enum variant. + // Zig `@"::"` — not a valid Rust identifier; renamed to match the enum + // variant. #[inline] pub fn in6_any(self) -> JSValue { CommonStringsForZig::IN6Any.to_js(self.global_object) diff --git a/src/jsc/ConcurrentPromiseTask.rs b/src/jsc/ConcurrentPromiseTask.rs index 8fab41a8442f..4931c8965966 100644 --- a/src/jsc/ConcurrentPromiseTask.rs +++ b/src/jsc/ConcurrentPromiseTask.rs @@ -36,13 +36,12 @@ pub struct ConcurrentPromiseTask<'a, Context: ConcurrentPromiseTaskContext> { /// BACKREF — captured from the JS-thread VM at create time; the VM (and its /// `EventLoop`) outlives every task scheduled on it. pub event_loop: BackRef, - // PORT NOTE: `allocator: std.mem.Allocator` field dropped — global mimalloc (non-AST crate) pub promise: JSPromiseStrong, pub global_this: &'a JSGlobalObject, pub concurrent_task: ConcurrentTask, // This is a poll because we want it to enter the uSockets loop - // PORT NOTE: `ref` is a Rust keyword; field renamed to `ref_`. + // (`ref` is a Rust keyword, hence `ref_`) pub ref_: KeepAlive, } diff --git a/src/jsc/ConsoleObject.rs b/src/jsc/ConsoleObject.rs index f43552ed7d71..bbf7123d1f04 100644 --- a/src/jsc/ConsoleObject.rs +++ b/src/jsc/ConsoleObject.rs @@ -116,7 +116,7 @@ impl core::fmt::Display for ConsoleObject { } impl ConsoleObject { - // PORT NOTE: `adapt_to_new_api(&mut self.stderr_buffer)` captures a raw + // `adapt_to_new_api(&mut self.stderr_buffer)` captures a raw // pointer into the buffer field, so the struct is self-referential once // initialized. The previous out-param shape (`&mut MaybeUninit`) // still let the caller move the value afterwards (e.g. @@ -131,9 +131,6 @@ impl ConsoleObject { // `VirtualMachine.console` stores the raw pointer (`*mut c_void`), so // callers leak via `heap::alloc(Pin::into_inner_unchecked(..))` — the // VM owns it for the process lifetime. - // - // TODO(port): make `QuietWriterAdapter` own its 4 KiB buffer so - // the self-reference disappears and this can become `-> Self`. pub fn init( error_writer: Output::StreamType, writer: Output::StreamType, @@ -673,7 +670,7 @@ impl<'a> TablePrinter<'a> { is_iterable: tabular_data.is_iterable(global_object)?, jstype: tabular_data.js_type(), value_formatter: { - // PORT NOTE: `Formatter` has a `Drop` impl, so struct-update + // `Formatter` has a `Drop` impl, so struct-update // from a temporary is rejected (E0509). let mut f = Formatter::new(global_object); f.single_line = true; @@ -781,7 +778,7 @@ impl<'a> TablePrinter<'a> { let column: &mut Column = 'brk: { let col_str = BunString::init(col_key); - // PORT NOTE: reshaped for borrowck — split find/append. + // reshaped for borrowck — split find/append. if let Some(idx) = columns[1..].iter().position(|col| col.name.eql(&col_str)) { @@ -898,7 +895,7 @@ impl<'a> TablePrinter<'a> { )); // `defer` block: release pooled visit map after formatting. - // PORT NOTE: Zig's `defer` body also nulls + // Zig's `defer` body also nulls // `this.value_formatter.map_node`, but `shallow_clone()` // already guarantees the source's `map_node` is `None`, so // only the local clone needs draining. `Formatter::Drop` does @@ -948,7 +945,7 @@ impl<'a> TablePrinter<'a> { col.name.deref(); } }); - // PORT NOTE: reshaped for borrowck — re-borrow through the guard. + // reshaped for borrowck — re-borrow through the guard. let columns: &mut Vec = &mut **_deref_names; // create the first column " " which is always present @@ -1270,7 +1267,7 @@ pub fn write_trace(writer: &mut dyn bun_io::Write, global: &JSGlobalObject) { let exception = holder.zig_exception(); err.to_zig_exception(global, exception); } - // PORT NOTE: reshaped for borrowck — Zig held `exception` and + // reshaped for borrowck — Zig held `exception` and // `&holder.need_to_clear_parser_arena_on_deinit` simultaneously; in Rust // those are two `&mut` into `holder`. Capture the flag in a local and // write it back after. @@ -1485,7 +1482,7 @@ pub fn format2( if len == 1 { // initialized later in this function. - // PORT NOTE: `Formatter` has a `Drop` impl, so struct-update from a + // `Formatter` has a `Drop` impl, so struct-update from a // temporary is rejected (E0509). Construct via `new()` then mutate. let mut fmt = Formatter::new(global); fmt.ordered_properties = options.ordered_properties; @@ -1519,7 +1516,7 @@ pub fn format2( let _ = writer.flush(); } else { - // PORT NOTE: Zig `defer if (options.flush) writer.flush()`. + // Zig `defer if (options.flush) writer.flush()`. // Reborrow through the guard so SB sees body writes as children // of the guard's borrow (see `FlushOnDrop` doc). let mut _flush = FlushOnDrop { @@ -1540,7 +1537,7 @@ pub fn format2( return Ok(()); } - // PORT NOTE: Zig `defer if (options.flush) writer.flush()`. + // Zig `defer if (options.flush) writer.flush()`. // Reborrow through the guard so SB sees body writes as children of the // guard's borrow (see `FlushOnDrop` doc). let mut _flush = FlushOnDrop { @@ -1550,7 +1547,7 @@ pub fn format2( let writer: &mut dyn bun_io::Write = &mut *_flush.writer; let mut this_value: JSValue = vals[0]; - // PORT NOTE: see E0509 note above. + // see E0509 note above. let mut fmt = Formatter::new(global); fmt.remaining_values = bun_ptr::RawSlice::new(&vals[1..]); fmt.ordered_properties = options.ordered_properties; @@ -2558,7 +2555,7 @@ pub mod formatter { break; } - // PORT NOTE: borrowck — `writer` holds `&mut self.estimated_line_length`, + // borrowck — `writer` holds `&mut self.estimated_line_length`, // so route `remaining_values` reads/writes through the `RawSlice` // field directly instead of the `&self` helper methods. if self.remaining_values.is_empty() { @@ -2580,7 +2577,7 @@ pub mod formatter { // then skip the second % so we dont hit it again slice = &slice[slice.len().min((i + 1) as usize)..]; len = slice.len() as u32; - // PORT NOTE: replicates Zig's `while : (i += 1)` continue- + // replicates Zig's `while : (i += 1)` continue- // expression — Zig's `i = 0; continue;` is bumped to 1 by // the continue-expr, so the next iteration inspects // `slice[1]`, not `slice[0]`. (This is itself an upstream @@ -2889,10 +2886,9 @@ pub mod formatter { } } - // TODO(port): Zig computed `length_ignoring_formatted_values` at - // comptime by walking the format string. We'd need a `const fn` / - // proc-macro to recover that; for now the count is a runtime - // argument computed by the `pretty!` macro. + // Zig computed `length_ignoring_formatted_values` at comptime by + // walking the format string; Rust has no equivalent, so the count is a + // runtime argument computed by the `pretty!` macro. pub fn pretty( &mut self, fmt_len: usize, @@ -5079,7 +5075,7 @@ pub mod formatter { } }; - // PORT NOTE: Zig `@tagName(event_type)`. `EventType` is a transparent + // Zig `@tagName(event_type)`. `EventType` is a transparent // u8 newtype (non-exhaustive enum), so there is no derived `From // for &str`; only the two arms above can reach here. let event_tag_name: &'static str = match event_type { @@ -5217,12 +5213,10 @@ pub mod formatter { Ok(()) } - // TODO(port): JSX printing is large (≈230 LOC) and entirely - // self-contained string formatting over `value.get("type"/"key"/"props" - // /"children")`. The logic is reproduced here at the same control-flow - // shape; verify against existing JSX snapshot tests - // (`test/js/bun/util/inspect.test.js`) before trusting the borrow-reseat - // points. + // JSX printing is large (≈230 LOC) and entirely self-contained string + // formatting over `value.get("type"/"key"/"props"/"children")`; the + // logic is reproduced here at the same control-flow shape as the Zig + // original (covered by `test/js/bun/util/inspect.test.js` snapshots). #[inline(never)] fn print_jsx( &mut self, @@ -5247,13 +5241,13 @@ pub mod formatter { writer.write_all(pf!("").as_bytes()); writer.write_all(b"<"); - // PORT NOTE: Zig initialized `needs_space = false` / `tag_name_slice = .empty`, + // Zig initialized `needs_space = false` / `tag_name_slice = .empty`, // but both arms of the `type` if/else below assign them, so deferred init // avoids the dead-store warning while keeping semantics identical. let mut needs_space: bool; let mut tag_name_str = ZigString::init(b""); - // PORT NOTE: Zig spelled this `ZigString.Slice` with an explicit + // Zig spelled this `ZigString.Slice` with an explicit // `defer if (tag_name_slice.isAllocated()) tag_name_slice.deinit()`. // The Rust `ZigStringSlice` enum frees on `Drop`, so the scopeguard // is unnecessary. @@ -5753,7 +5747,7 @@ pub mod formatter { return Ok(()); } - // PORT NOTE: `ArrayBuffer.typed_array_type` is `JSType` in the Rust + // `ArrayBuffer.typed_array_type` is `JSType` in the Rust // port (see array_buffer.rs), not the C-API `TypedArrayType` enum. writer.write_all( if array_buffer.typed_array_type == jsc::JSType::Uint8Array @@ -5813,7 +5807,7 @@ pub mod formatter { Ok(()) } - // PORT NOTE: associated fn (no `&mut self`) so callers can pass a + // associated fn (no `&mut self`) so callers can pass a // `WrappedWriter` that already borrows `&mut self.estimated_line_length` // without tripping E0499. The only `self` use was `print_comma`, which // `WrappedWriter` mirrors. @@ -6089,7 +6083,7 @@ pub extern "C" fn Bun__ConsoleObject__timeLog( Output::flush(); // print the arguments - // PORT NOTE: `Formatter` has a `Drop` impl, so struct-update from a + // `Formatter` has a `Drop` impl, so struct-update from a // temporary is rejected (E0509). Construct via `new()` then mutate. let mut fmt = Formatter::new(global); fmt.max_depth = bun_options_types::context::try_get() diff --git a/src/jsc/Counters.rs b/src/jsc/Counters.rs index 3ab2025a7986..f3526ffcbc7f 100644 --- a/src/jsc/Counters.rs +++ b/src/jsc/Counters.rs @@ -9,9 +9,8 @@ pub struct Counters { impl Counters { pub fn mark(&mut self, tag: Field) { - // PORT NOTE: Zig used `comptime tag` + `@field(this, @tagName(tag))` reflection; - // Rust dispatches via match. Demoted to runtime arg (not used in a type position). - // PERF(port): was comptime monomorphization — profile if it shows up on a hot path. + // Zig used `comptime tag` + `@field(this, @tagName(tag))` reflection; + // Rust dispatches via match on a runtime arg. let slot = match tag { Field::SpawnSyncBlocking => &mut self.spawn_sync_blocking, Field::SpawnMemfd => &mut self.spawn_memfd, @@ -20,7 +19,7 @@ impl Counters { } pub fn to_js(self, global: &JSGlobalObject) -> JsResult { - // PORT NOTE: Zig used `JSObject::create(struct_value, global)` (field reflection + // Zig used `JSObject::create(struct_value, global)` (field reflection // builds an object with one property per struct field). Hand-rolled `put` calls here. let obj = JSValue::create_empty_object(global, 2); obj.put( @@ -37,9 +36,9 @@ impl Counters { } } -// TODO(port): proc-macro — `#[bun_jsc::host_fn]` emits the `extern "sysv64"`/"C" -// trampoline. Until the macro crate exists, expose the Zig-shape signature -// directly; the trampoline is wired by codegen. +// Called through the `$zig(...)` js2native codegen, which emits the extern +// trampoline (`generate-js2native.ts` wraps this in `host_fn::host_fn_static`), +// so no `#[bun_jsc::host_fn]` attribute is needed here. pub fn create_counters_object(global: &JSGlobalObject, _frame: &CallFrame) -> JsResult { // SAFETY: bun_vm() returns the per-thread VirtualMachine singleton; caller is on the JS thread. global.bun_vm().counters.to_js(global) diff --git a/src/jsc/CppTask.rs b/src/jsc/CppTask.rs index 7b940d61067a..b3edfa7c6523 100644 --- a/src/jsc/CppTask.rs +++ b/src/jsc/CppTask.rs @@ -4,7 +4,6 @@ use crate::{JSGlobalObject, JsResult, VirtualMachineRef as VirtualMachine}; use bun_event_loop::{TaskTag, Taskable, task_tag}; use bun_threading::work_pool::{Task as WorkPoolTask, WorkPool}; -// TODO(port): move to jsc_sys #[allow(improper_ctypes)] // VirtualMachine is opaque to C++; passed as `void*` unsafe extern "C" { fn Bun__EventLoopTaskNoContext__performTask(task: *mut EventLoopTaskNoContext); diff --git a/src/jsc/CustomGetterSetter.rs b/src/jsc/CustomGetterSetter.rs index efbc6cb09a64..5a70285b773a 100644 --- a/src/jsc/CustomGetterSetter.rs +++ b/src/jsc/CustomGetterSetter.rs @@ -13,7 +13,6 @@ impl CustomGetterSetter { } } -// TODO(port): move to jsc_sys unsafe extern "C" { safe fn JSC__CustomGetterSetter__isGetterNull(this: &CustomGetterSetter) -> bool; safe fn JSC__CustomGetterSetter__isSetterNull(this: &CustomGetterSetter) -> bool; diff --git a/src/jsc/DOMFormData.rs b/src/jsc/DOMFormData.rs index ce185f221fe5..739f8f10d0c7 100644 --- a/src/jsc/DOMFormData.rs +++ b/src/jsc/DOMFormData.rs @@ -8,7 +8,6 @@ bun_opaque::opaque_ffi! { pub struct DOMFormData; } -// TODO(port): move to jsc_sys unsafe extern "C" { safe fn WebCore__DOMFormData__create(arg0: &JSGlobalObject) -> JSValue; safe fn WebCore__DOMFormData__createFromURLQuery( @@ -62,7 +61,7 @@ impl DOMFormData { }) } - // PORT NOTE: Zig's `comptime Ctx: type, ctx: Ctx, comptime callback: fn(Ctx, ZigString)` + // Zig's `comptime Ctx: type, ctx: Ctx, comptime callback: fn(Ctx, ZigString)` // is Zig's spelling of a monomorphized closure. Reshaped to `FnMut(ZigString)` — the // closure environment IS the ctx, and the generic trampoline below is the `Wrapper.run`. pub fn to_query_string(&mut self, callback: &mut F) @@ -89,8 +88,9 @@ impl DOMFormData { // Returned pointer is valid while `value` is kept alive on the stack // (conservative GC scan). Null → None. `DOMFormData` is an opaque ZST // handle, so `opaque_mut` is the centralised zero-byte deref proof. - // TODO(port): lifetime — unbounded `'a` is a placeholder; caller must keep `value` - // stack-rooted for the lifetime of the returned reference. + // The unbounded `'a` cannot be expressed more tightly: the cell is + // GC-owned, so the caller must keep `value` stack-rooted for the + // lifetime of the returned reference. let p = WebCore__DOMFormData__fromJS(value); (!p.is_null()).then(|| DOMFormData::opaque_mut(p)) } @@ -113,7 +113,7 @@ impl DOMFormData { WebCore__DOMFormData__count(self) } - // PORT NOTE: Zig's `comptime Context: type, ctx: *Context, comptime callback_wrapper` + // Zig's `comptime Context: type, ctx: *Context, comptime callback_wrapper` // reshaped to a Rust closure; the generic `extern "C"` trampoline below is `Wrap.forEachWrapper`. // // LAYERING: `FormDataEntry::File::blob` is a `*mut webcore::Blob`, whose diff --git a/src/jsc/DOMURL.rs b/src/jsc/DOMURL.rs index e765e2f59118..45e449350d45 100644 --- a/src/jsc/DOMURL.rs +++ b/src/jsc/DOMURL.rs @@ -8,8 +8,6 @@ bun_opaque::opaque_ffi! { pub struct DOMURL; } -// TODO(port): move to jsc_sys -// // `DOMURL`/`VM` are opaque `UnsafeCell`-backed ZST handles; `ZigString`/`c_int` // out-params are plain `#[repr(C)]` PODs whose `&mut` is exclusive for the // call → `safe fn`. @@ -35,7 +33,9 @@ bun_core::named_error_set!(ToFileSystemPathError); impl DOMURL { pub fn cast_<'a>(value: JSValue, vm: &'a VM) -> Option<&'a mut DOMURL> { - // TODO(port): lifetime — DOMURL is a GC-owned C++ cell; no Rust-expressible lifetime. + // DOMURL is a GC-owned C++ cell; the returned reference is only valid + // while `value` stays alive (e.g. stack-rooted for the conservative GC + // scan) — the borrow on `vm` does not capture that. // `DOMURL` is an `opaque_ffi!` ZST handle; `opaque_mut` is the // centralised non-null-ZST deref proof (zero-byte `&mut` cannot alias). let p = WebCore__DOMURL__cast_(value, vm); diff --git a/src/jsc/Debugger.rs b/src/jsc/Debugger.rs index b4d2097b9937..d7778439a556 100644 --- a/src/jsc/Debugger.rs +++ b/src/jsc/Debugger.rs @@ -303,9 +303,11 @@ pub enum Mode { } pub struct Debugger { - // TODO(port): lifetime — never freed in Zig; likely borrowed from CLI args / env for process lifetime + // `'static` is genuine: set from `cli::cli_dupe` (process-lifetime CLI + // arena) — see jsc_hooks.rs. Never freed in Zig either. pub path_or_port: Option<&'static [u8]>, - // TODO(port): lifetime — never freed in Zig; default "" + // `'static` is genuine: borrowed from process-lifetime env-var storage; + // default `""`. pub from_environment_variable: &'static [u8], pub script_execution_context_id: u32, pub next_debugger_id: u64, @@ -346,8 +348,6 @@ impl Default for Debugger { } } -// TODO(port): move to jsc_sys -// // SAFETY (safe fn): `JSGlobalObject` is an opaque `UnsafeCell`-backed handle // (`&` is ABI-identical to non-null `*mut`); `BunString` is a `#[repr(C)]` POD // out-param. Remaining args are by-value scalars. @@ -373,7 +373,7 @@ impl Debugger { /// /// Spec `Debugger.zig:31` `waitForDebuggerIfNecessary`. /// - /// PORT NOTE — aliasing: `this.debugger` is read through a raw pointer + /// Aliasing: `this.debugger` is read through a raw pointer /// with fresh short-lived borrows because `event_loop().tick()` / /// `auto_tick_active()` re-enter JS, which calls `VirtualMachine::get()` /// and may form independent `&mut VirtualMachine` borrows. Holding a @@ -404,7 +404,7 @@ impl Debugger { }); bun_core::scoped_log!(debugger, "spin"); - // PORT NOTE: spec `var futex_atomic = .init(0)` and nothing ever + // Spec `var futex_atomic = .init(0)` and nothing ever // stores `1` before this load, so this loop is a no-op on first call // — ported faithfully. while FUTEX_ATOMIC.load(Ordering::Relaxed) > 0 { @@ -487,7 +487,7 @@ impl Debugger { } // Drop the long-lived `&mut Debugger` before re-entering JS — see - // PORT NOTE above. Each loop iteration re-fetches via `debugger_mut()` + // the aliasing note on this fn. Each loop iteration re-fetches via `debugger_mut()` // so re-entrant JS may independently borrow the VM. loop { let wait = match this.debugger.as_deref() { @@ -581,13 +581,13 @@ impl Debugger { if !this_ref.has_started_debugger { this_ref.as_mut().has_started_debugger = true; - // PORT NOTE: `std::thread::spawn` requires `Send`; raw `*mut + // `std::thread::spawn` requires `Send`; raw `*mut // VirtualMachine` is `!Send`. Wrap in a `Send` newtype — the // pointer is only ever dereferenced on the debugger thread under // `holdAPILock` (see `start_js_debugger_thread` doc), and the VM // outlives the process. struct SendVmPtr(*mut VirtualMachine); - // SAFETY: see PORT NOTE above — cross-thread access is mediated + // SAFETY: see comment above — cross-thread access is mediated // by `holdAPILock` / the futex; the VM allocation is `'static`. unsafe impl Send for SendVmPtr {} let send_vm = SendVmPtr(this); @@ -620,14 +620,14 @@ impl Debugger { /// /// Spec `Debugger.zig:143` `startJSDebuggerThread`. /// - /// PORT NOTE: `other_vm` is the *parent thread's* VM. The parent thread + /// `other_vm` is the *parent thread's* VM. The parent thread /// continues executing (and mutating that VM) concurrently with this /// thread (Debugger.zig:131→134-138, then the wait-loop at zig:79-114). /// Taking `&mut VirtualMachine` here would assert exclusive access we do /// not have — UB. Spec uses a raw `*VirtualMachine`; we mirror that and /// never materialize a `&`/`&mut VirtualMachine` to the foreign-thread VM. pub fn start_js_debugger_thread(other_vm: *mut VirtualMachine) { - // PORT NOTE: Zig `MimallocArena` + thread-local `DotEnv.Loader` are + // Zig `MimallocArena` + thread-local `DotEnv.Loader` are // dropped per docs/PORTING.md §Allocators — the global allocator is // mimalloc and `InitOptions` no longer carries `allocator`/`env_loader` // (those are wired by `RuntimeHooks::init_runtime_state`). @@ -653,7 +653,7 @@ impl Debugger { // Spec: `vm.global.vm().holdAPILock(other_vm, OpaqueWrap(VM, start))`. extern "C" fn start_trampoline(ctx: *mut c_void) { - // PORT NOTE: forward the raw pointer unchanged — see fn doc above + // Forward the raw pointer unchanged — see fn doc above // for why we never form `&mut VirtualMachine` to the parent VM. Debugger::start(ctx.cast::()); } @@ -667,7 +667,7 @@ impl Debugger { /// debugger thread. Publishes the inspector URL(s), wakes the futex the /// parent VM is blocked on, then spins this thread's event loop forever. /// - /// PORT NOTE — aliasing: every `VirtualMachine` / `EventLoop` access here + /// Aliasing: every `VirtualMachine` / `EventLoop` access here /// goes through a raw pointer with a fresh short-lived `&mut *p` formed at /// the call site, never bound to a long-lived reference. Reasons: /// @@ -689,7 +689,7 @@ impl Debugger { // `this` is this thread's own VM (created in `start_js_debugger_thread`) // — safe to hold as `&'static`. `other_vm` remains a raw pointer (see - // PORT NOTE above): the parent thread mutates it concurrently after the + // aliasing note above): the parent thread mutates it concurrently after the // futex wake, so forming `&VirtualMachine` to it would be a data race. let this: &VirtualMachine = VirtualMachine::get(); // SAFETY: `other_vm` is the parent-thread VM, live for process @@ -701,7 +701,7 @@ impl Debugger { let other_loop: *mut crate::event_loop::EventLoop = unsafe { (*other_vm).event_loop() }; let global: &JSGlobalObject = this.global(); - // PORT NOTE: copy the four scalars we need from the parent VM's + // Copy the four scalars we need from the parent VM's // debugger before re-entering JS or waking the parent. Spec `.?` would // safety-panic, but we run inside an `extern "C"` trampoline where // unwinding is UB — wake the parent and bail instead (unreachable in @@ -870,8 +870,6 @@ pub enum AsyncCallType { Microtask = 5, } -// TODO(port): move to jsc_sys -// // SAFETY (safe fn): `JSGlobalObject` is an opaque `UnsafeCell`-backed handle // (`&` is ABI-identical to non-null `*const`); remaining args are by-value // scalars / `#[repr(u8)]` enums. @@ -937,8 +935,6 @@ pub enum TestType { bun_opaque::opaque_ffi! { pub struct TestReporterHandle; } -// TODO(port): move to jsc_sys -// // SAFETY (safe fn): `TestReporterHandle` and `CallFrame` are `opaque_ffi!` // ZST handles (`!Freeze` via `UnsafeCell`); `BunString` is a `#[repr(C)]` // in/out-param the C++ side reads/consumes in-place. Remaining args are @@ -1099,8 +1095,6 @@ pub struct LifecycleAgent { bun_opaque::opaque_ffi! { pub struct LifecycleHandle; } -// TODO(port): move to jsc_sys -// // SAFETY (safe fn): `LifecycleHandle` is an `opaque_ffi!` ZST handle (`!Freeze` // via `UnsafeCell`); `ZigException` is a `#[repr(C)]` out-param the C++ side // reads/fills in-place. diff --git a/src/jsc/DeprecatedStrong.rs b/src/jsc/DeprecatedStrong.rs index 4ac598b194e5..e5c77ac7243b 100644 --- a/src/jsc/DeprecatedStrong.rs +++ b/src/jsc/DeprecatedStrong.rs @@ -5,18 +5,17 @@ use core::ptr::NonNull; use crate::JSValue; -// PORT NOTE: This file is a Zig file-level struct. In Zig it is referenced as -// `jsc.Strong.Deprecated` (see bottom-of-file alias `const Strong = jsc.Strong.Deprecated`). -// Ported here as `DeprecatedStrong`. +// In Zig this file-level struct is referenced as `jsc.Strong.Deprecated`; +// ported here as `DeprecatedStrong`. Zig `deinit` → `impl Drop`. // -// PORT NOTE: Zig `deinit` → `impl Drop`. The manual `ref()`/`unref()` path -// overlaps teardown; to avoid Drop double-`unprotect`ing after a final `unref`, -// `unref()` zeroes `raw` and clears `_safety` when it frees (debug builds), so -// Drop becomes a no-op (`unprotect` on ZERO is a no-op; `_safety == None` skips -// the canary free). -// TODO(port): release builds have no ref_count, so a caller that does the final -// `unref()` and then lets Drop fire would double-unprotect — audit call sites -// (Zig contract: ref/unref pairs are balanced, deinit is the release). +// Refcount contract (load-bearing): `ref()`/`unref()` calls must be balanced +// in pairs; Drop is the release for the `init()` protect. In debug builds a +// final `unref()` (ref_count 1 → 0) additionally frees the canary, zeroes +// `raw`, and clears `_safety` so a subsequent Drop is a no-op. Release builds +// have no ref_count, so an unref-used-as-release followed by Drop would +// double-unprotect — callers must never use `unref()` as the release. +// (Audited 2026-06: the only user is test_runner/Collection.rs, which uses +// `init` + Drop and never calls `ref`/`unref`.) // Zig: `enable_safety = bun.Environment.ci_assert`. #[cfg(debug_assertions)] @@ -39,13 +38,13 @@ type Safety = (); #[cfg(debug_assertions)] struct SafetyData { - // PORT NOTE: raw pointer (not Box) — this is a heap canary for UAF detection; + // Raw pointer (not Box) — this is a heap canary for UAF detection; // owning it via Box would change the semantics (Drop would recurse / hide UAF). // Backing allocation is `Box>` (repr(transparent)) // so freeing does NOT run DeprecatedStrong::drop on the sentinel value; the // pointer is stored cast to the inner type for ergonomic field access. ptr: NonNull, - // PORT NOTE: `gpa: std.mem.Allocator` dropped — global mimalloc. + // Zig's `gpa: std.mem.Allocator` field dropped — global mimalloc. ref_count: u32, } @@ -55,7 +54,7 @@ const SAFETY_NONE: Safety = None; const SAFETY_NONE: Safety = (); pub struct DeprecatedStrong { - // PORT NOTE: bare JSValue field is intentional — this *is* the GC-root + // Bare JSValue field is intentional — this *is* the GC-root // wrapper (uses JSValueProtect/Unprotect), so the §JSC "never store bare // JSValue on the heap" rule does not apply here. raw: JSValue, @@ -98,7 +97,7 @@ impl DeprecatedStrong { pub fn swap(&mut self, next: JSValue) -> JSValue { let prev = self.raw; - // PORT NOTE: `*self = ...` drops the old value in place (runs Drop), + // `*self = ...` drops the old value in place (runs Drop), // matching Zig's explicit `this.deinit(); this.* = .init(next);`. *self = Self::init(next); prev @@ -133,7 +132,7 @@ impl DeprecatedStrong { .cast::>(), )); } - // Neutralize so Drop is a no-op (see top-of-file PORT NOTE). + // Neutralize so Drop is a no-op (see top-of-file refcount contract). self._safety = None; self.raw = JSValue::ZERO; return; @@ -171,7 +170,7 @@ pub struct Optional { } impl Optional { - // PORT NOTE: Zig `pub const empty = .initNonCell(null)` — inlined as a struct + // Zig `pub const empty = .initNonCell(null)` — inlined as a struct // literal so it can be `const` (init_non_cell debug_asserts, which is non-const). pub const EMPTY: Optional = Optional { backing: DeprecatedStrong { @@ -192,7 +191,7 @@ impl Optional { } } - // PORT NOTE: Zig `deinit` dropped — `backing: DeprecatedStrong` is dropped + // Zig `deinit` dropped — `backing: DeprecatedStrong` is dropped // automatically (its Drop impl runs `unprotect` + canary free). pub fn get(&self) -> Option { diff --git a/src/jsc/EventType.rs b/src/jsc/EventType.rs index d1b13a33877a..057600200b0f 100644 --- a/src/jsc/EventType.rs +++ b/src/jsc/EventType.rs @@ -1,6 +1,6 @@ use phf::phf_map; -// PORT NOTE: Zig `enum(u8) { ..., _ }` is non-exhaustive — it can hold any u8 +// The Zig original is `enum(u8) { ..., _ }`, which is non-exhaustive — it can hold any u8 // value, not just the named tags. A plain `#[repr(u8)] enum` cannot express // that (Rust enums are exhaustive; transmuting an unnamed discriminant is UB). // Modeled as a transparent u8 newtype with associated consts so the `else` diff --git a/src/jsc/Exception.rs b/src/jsc/Exception.rs index b351582f20e1..49642fc7b7d7 100644 --- a/src/jsc/Exception.rs +++ b/src/jsc/Exception.rs @@ -5,7 +5,6 @@ bun_opaque::opaque_ffi! { pub struct Exception; } -// TODO(port): move to jsc_sys unsafe extern "C" { safe fn JSC__Exception__getStackTrace( this: &Exception, diff --git a/src/jsc/FFI.rs b/src/jsc/FFI.rs index 140c387dba57..6ec352ba3cf8 100644 --- a/src/jsc/FFI.rs +++ b/src/jsc/FFI.rs @@ -2,7 +2,7 @@ // it turns out: FFI.h is faster than our implementation that calls into C++ bindings // so we just use this in some cases // -// PORT NOTE: the original .zig is raw `zig translate-c` output. The meaningful +// The original .zig is raw `zig translate-c` output. The meaningful // surface (EncodedJSValue + tag constants + inline coercion fns) is ported // below; the ~390 lines of compiler-builtin macro noise (`__clang__`, // `__INT_MAX__`, `__ARM_FEATURE_*`, …) emitted by translate-c are dropped — @@ -52,8 +52,6 @@ pub(crate) const TRUE_I64: i64 = ((2 | 4) | 1) as i64; pub(crate) static ValueTrue: bun_core::RacyCell = bun_core::RacyCell::new(EncodedJSValue { as_int64: TRUE_I64 }); -// TODO(port): move to jsc_sys -// // By-value POD union arg; no invariants beyond ABI → `safe fn`. unsafe extern "C" { pub safe fn JSVALUE_TO_UINT64_SLOW(value: EncodedJSValue) -> u64; @@ -65,12 +63,11 @@ pub fn uint64_to_jsvalue_slow(global_object: &JSGlobalObject, val: u64) -> JSVal JSValue::from_uint64_no_truncate(global_object, val) } -// TODO(port): move to jsc_sys unsafe extern "C" { pub fn JSFunctionCall(globalObject: *mut c_void, callFrame: *mut c_void) -> *mut c_void; } -// PORT NOTE: ~390 lines of translate-c compiler-builtin macro definitions +// ~390 lines of translate-c compiler-builtin macro definitions // (`__block`, `__INTMAX_C_SUFFIX__`, `__clang_major__`, `__SIZEOF_*`, // `__ARM_FEATURE_*`, `__APPLE__`, …) from FFI.zig:121-509 intentionally // dropped — they are clang predefined-macro spew with no callers. diff --git a/src/jsc/FetchHeaders.rs b/src/jsc/FetchHeaders.rs index cec2b5d26d69..8ad243ea02e0 100644 --- a/src/jsc/FetchHeaders.rs +++ b/src/jsc/FetchHeaders.rs @@ -6,14 +6,11 @@ use crate::{JSGlobalObject, JSValue, JsResult, VM, host_fn}; use bun_core::{StringPointer, ZigString}; use bun_uws::ResponseKind; -// TODO(port): consider an RAII wrapper that calls `deref` on Drop instead of returning `NonNull` from constructors. bun_opaque::opaque_ffi! { /// Opaque C++ `WebCore::FetchHeaders` handle (ref-counted on the C++ side; see `deref`). pub struct FetchHeaders; } -// TODO(port): move to jsc_sys -// // `FetchHeaders`/`JSGlobalObject`/`VM` are opaque `UnsafeCell`-backed ZST // handles, so `&T` is ABI-identical to a non-null `*const T` and C++ mutating // header storage / VM state through them is interior mutation invisible to @@ -213,7 +210,7 @@ impl FetchHeaders { .expect("WebCore__FetchHeaders__createEmpty returned null") } - // PORT NOTE: reshaped for borrowck — Zig took `pico_headers: anytype` and read + // Reshaped for borrowck — Zig took `pico_headers: anytype` and read // `pico_headers.list.ptr` / `.list.len`. In Rust, callers pass the slice directly. pub fn create_from_pico_headers(pico_headers_list: &[T]) -> NonNull { let out = PicoHeaders { @@ -242,7 +239,6 @@ impl FetchHeaders { value: &[u8], global: &JSGlobalObject, ) -> JsResult<()> { - // TODO(port): bun.jsc.fromJSHostCallGeneric — wraps the FFI call and converts a pending VM exception into JsError host_fn::from_js_host_call_generic(global, || { let zs = ZigString::init(value); WebCore__FetchHeaders__put(self, name_, &zs, global) @@ -257,7 +253,7 @@ impl FetchHeaders { let mut out = ZigString::EMPTY; self.get_(&ZigString::init(name_), &mut out, global); if out.len > 0 { - // PORT NOTE: returns the ZigString view (borrows C++-owned header + // Returns the ZigString view (borrows C++-owned header // storage); caller may `.slice()` it. Returning `&[u8]` directly // would borrow the local `out`, not the underlying buffer. return Some(out); @@ -331,7 +327,6 @@ impl FetchHeaders { &mut self, global: &JSGlobalObject, ) -> JsResult>> { - // TODO(port): bun.jsc.fromJSHostCallGeneric — wraps the FFI call and converts a pending VM exception into JsError host_fn::from_js_host_call_generic(global, || { NonNull::new(WebCore__FetchHeaders__cloneThis(self, global)) }) diff --git a/src/jsc/GarbageCollectionController.rs b/src/jsc/GarbageCollectionController.rs index fc966ca2de0c..42f08d4d39cb 100644 --- a/src/jsc/GarbageCollectionController.rs +++ b/src/jsc/GarbageCollectionController.rs @@ -28,14 +28,15 @@ use crate::VM; use crate::virtual_machine::VirtualMachine; pub struct GarbageCollectionController { - // TODO(port): lifetime — FFI handle created by uws::Timer::create_fallthrough, freed in Drop. - // Stored as `Option>` (None = uninit; Some after `init`). + // Raw FFI handle created by `uws::Timer::create_fallthrough` in `init`, + // freed in Drop. Stored as `Option>` (None = uninit). pub gc_timer: Option>, pub gc_last_heap_size: usize, pub gc_last_heap_size_on_repeating_timer: usize, pub heap_size_didnt_change_for_repeating_timer_ticks_count: u8, pub gc_timer_state: GCTimerState, - // TODO(port): lifetime — FFI handle created by uws::Timer::create_fallthrough, freed in Drop + // Raw FFI handle created by `uws::Timer::create_fallthrough` in `init`, + // freed in Drop. pub gc_repeating_timer: Option>, pub gc_timer_interval: i32, pub gc_repeating_timer_fast: bool, @@ -128,7 +129,7 @@ impl GarbageCollectionController { } } - // PORT NOTE: in the Zig spec `vm.transpiler` is fully constructed + // In the Zig spec `vm.transpiler` is fully constructed // before `JSGlobalObject.create` → `ensureWaker` → this `init`. The // Rust port defers `Transpiler::init` to the high-tier // `init_runtime_state` hook (which runs *after* `ensure_waker`), so diff --git a/src/jsc/GetterSetter.rs b/src/jsc/GetterSetter.rs index b46e8e161a4f..75b273c4cf22 100644 --- a/src/jsc/GetterSetter.rs +++ b/src/jsc/GetterSetter.rs @@ -10,7 +10,6 @@ impl GetterSetter { } } -// TODO(port): move to jsc_sys unsafe extern "C" { safe fn JSC__GetterSetter__isGetterNull(this: &GetterSetter) -> bool; safe fn JSC__GetterSetter__isSetterNull(this: &GetterSetter) -> bool; diff --git a/src/jsc/HTTPServerAgent.rs b/src/jsc/HTTPServerAgent.rs index e396aa4fb9b7..d183f222b400 100644 --- a/src/jsc/HTTPServerAgent.rs +++ b/src/jsc/HTTPServerAgent.rs @@ -40,7 +40,7 @@ impl HTTPServerAgent { // #region Events // - // PORT NOTE (phase-d): `notify_server_started` / `notify_server_stopped` / + // `notify_server_started` / `notify_server_stopped` / // `notify_server_routes_updated` reach into `bun_jsc::api::AnyServer` and // `ServerConfig::RouteDeclaration`, which live in `bun_runtime` (forward // dep). The C++ side only needs `Bun__HTTPServerAgent__setEnabled` for @@ -125,8 +125,6 @@ bun_opaque::opaque_ffi! { pub struct InspectorHTTPServerAgent; } -// TODO(port): move to jsc_sys -// // `safe fn`: `InspectorHTTPServerAgent` is an `opaque_ffi!` ZST handle // (`!Freeze` via `UnsafeCell`); `BunString` is `#[repr(C)]` and read-only // across the call. `&mut`/`&` are ABI-identical to non-null `*mut`/`*const`. diff --git a/src/jsc/JSArray.rs b/src/jsc/JSArray.rs index fb7301461f2d..a033928639aa 100644 --- a/src/jsc/JSArray.rs +++ b/src/jsc/JSArray.rs @@ -5,7 +5,6 @@ bun_opaque::opaque_ffi! { pub struct JSArray; } -// TODO(port): move to jsc_sys unsafe extern "C" { // TODO: this can throw fn JSArray__constructArray( @@ -19,7 +18,7 @@ unsafe extern "C" { impl JSArray { #[track_caller] pub fn create(global: &JSGlobalObject, items: &[JSValue]) -> JsResult { - // PORT NOTE: `fromJSHostCall(global, @src(), fn, .{args})` is a comptime-reflection + // `fromJSHostCall(global, @src(), fn, .{args})` is a comptime-reflection // wrapper that calls `fn(args...)` then checks the VM for a pending exception. // Modeled as a closure-taking helper here. crate::from_js_host_call(global, || unsafe { diff --git a/src/jsc/JSArrayIterator.rs b/src/jsc/JSArrayIterator.rs index ccda7691bbcf..542761ac3217 100644 --- a/src/jsc/JSArrayIterator.rs +++ b/src/jsc/JSArrayIterator.rs @@ -55,7 +55,6 @@ impl<'a> JSArrayIterator<'a> { } } -// TODO(port): move to jsc_sys unsafe extern "C" { safe fn Bun__JSArray__getContiguousVector(value: JSValue, out_len: &mut u32) -> *const JSValue; // safe: by-value `JSValue`/`u32`; `elements` is only pointer-compared against diff --git a/src/jsc/JSBigInt.rs b/src/jsc/JSBigInt.rs index ad293bc5e75e..3c1c73906ad0 100644 --- a/src/jsc/JSBigInt.rs +++ b/src/jsc/JSBigInt.rs @@ -8,7 +8,6 @@ bun_opaque::opaque_ffi! { pub struct JSBigInt; } -// TODO(port): move to jsc_sys unsafe extern "C" { // safe: `JSValue` is a by-value tagged i64; returns a nullable GC-cell // pointer the caller checks before deref. @@ -55,7 +54,6 @@ impl JSBigInt { // (stack-scanned) for as long as the returned ref is used. `JSBigInt` // is an opaque ZST handle so the deref is the centralised `opaque_ref` // proof. - // TODO(port): lifetime — model as `&'a JSBigInt` tied to a stack guard? let p = JSC__JSBigInt__fromJS(value); (!p.is_null()).then(|| JSBigInt::opaque_ref(p)) } diff --git a/src/jsc/JSCell.rs b/src/jsc/JSCell.rs index 31347eb4070d..ce360dc947fe 100644 --- a/src/jsc/JSCell.rs +++ b/src/jsc/JSCell.rs @@ -1,6 +1,6 @@ use crate::custom_getter_setter::CustomGetterSetter; use crate::getter_setter::GetterSetter; -use crate::{JSGlobalObject, JSObject, JSValue}; +use crate::{JSGlobalObject, JSObject, JSType, JSValue}; bun_opaque::opaque_ffi! { /// Opaque FFI handle for `JSC::JSCell`. @@ -10,8 +10,10 @@ bun_opaque::opaque_ffi! { impl JSCell { /// Statically cast a cell to a JSObject. Returns null for non-objects. /// Use `to_object` to mutate non-objects into objects. + #[track_caller] pub fn get_object(&self) -> Option<&JSObject> { - // TODO(port): jsc.markMemberBinding(JSCell, @src()) — comptime binding marker, likely drop + // Zig: `jsc.markMemberBinding(JSCell, @src())`. + crate::mark_member_binding("JSCell", core::panic::Location::caller()); // `JSObject` is an `opaque_ffi!` ZST handle; `opaque_ref` is the // centralised non-null-ZST deref proof. Nullable per the C++ contract // (non-object cells return null). @@ -26,18 +28,22 @@ impl JSCell { /// /// ## References /// - [ECMA-262 §7.1.18 ToObject](https://tc39.es/ecma262/#sec-toobject) + #[track_caller] pub fn to_object<'a>(&'a self, global: &'a JSGlobalObject) -> &'a JSObject { - // TODO(port): jsc.markMemberBinding(JSCell, @src()) — comptime binding marker, likely drop + // Zig: `jsc.markMemberBinding(JSCell, @src())`. + crate::mark_member_binding("JSCell", core::panic::Location::caller()); // `JSObject` is an `opaque_ffi!` ZST handle; `opaque_ref` is the // centralised non-null deref proof (ToObject on a cell never returns null). JSObject::opaque_ref(JSC__JSCell__toObject(self, global)) } - pub fn get_type(&self) -> u8 { - // TODO(port): jsc.markMemberBinding(JSCell, @src()) — comptime binding marker, likely drop - // TODO(port): Zig wraps the extern result in @enumFromInt but the fn return type is `u8`; - // likely intended to return `JSType` — verify. - JSC__JSCell__getType(self) + #[track_caller] + pub fn get_type(&self) -> JSType { + // Zig: `jsc.markMemberBinding(JSCell, @src())`. + crate::mark_member_binding("JSCell", core::panic::Location::caller()); + // `JSType` is a `#[repr(transparent)]` newtype over `u8`, so any byte + // returned by the extern is a valid value (see the extern's NOTE). + JSType(JSC__JSCell__getType(self)) } pub fn to_js(&self) -> JSValue { @@ -45,7 +51,7 @@ impl JSCell { } pub fn get_getter_setter(&self) -> &GetterSetter { - // TODO(port): bun_jsc::JSValue::is_getter_setter (debug_assert dropped while JSValue.rs gated) + debug_assert!(self.get_type() == JSType::GetterSetter); // Caller-asserted invariant — this cell's JSType is GetterSetter. // `GetterSetter` is an `opaque_ffi!` ZST handle; `opaque_ref` is the // centralised non-null-ZST deref proof (`self` is non-null). @@ -53,7 +59,7 @@ impl JSCell { } pub fn get_custom_getter_setter(&self) -> &CustomGetterSetter { - // TODO(port): bun_jsc::JSValue::is_custom_getter_setter (debug_assert dropped while JSValue.rs gated) + debug_assert!(self.get_type() == JSType::CustomGetterSetter); // Caller-asserted invariant — this cell's JSType is CustomGetterSetter. // `CustomGetterSetter` is an `opaque_ffi!` ZST handle; see `get_getter_setter`. CustomGetterSetter::opaque_ref( @@ -66,8 +72,6 @@ impl JSCell { } } -// TODO(port): move to jsc_sys -// // `JSCell`/`JSGlobalObject` are opaque `UnsafeCell`-backed ZST handles, so // `&T` is ABI-identical to a non-null `*const T` and C++ mutating cell state // through it is interior mutation invisible to Rust. diff --git a/src/jsc/JSErrorCode.rs b/src/jsc/JSErrorCode.rs index caff048922e0..87ed6099d054 100644 --- a/src/jsc/JSErrorCode.rs +++ b/src/jsc/JSErrorCode.rs @@ -1,6 +1,6 @@ /// Represents JavaScript error types // -// PORT NOTE: Zig source is `enum(u8) { ..., _ }` (non-exhaustive — any u8 is a +// The Zig original is `enum(u8) { ..., _ }` (non-exhaustive — any u8 is a // valid bit pattern). A Rust `#[repr(u8)] enum` would make non-listed values // UB, so this is ported as a transparent u8 newtype with associated consts. #[repr(transparent)] diff --git a/src/jsc/JSFunction.rs b/src/jsc/JSFunction.rs index d1ea2c0b41aa..56af499d5203 100644 --- a/src/jsc/JSFunction.rs +++ b/src/jsc/JSFunction.rs @@ -41,8 +41,6 @@ pub struct CreateJSFunctionOptions { pub constructor: Option, } -// TODO(port): move to jsc_sys -// // `JSGlobalObject` is an opaque `UnsafeCell`-backed ZST handle; the remaining // params are by-value scalars / `#[repr(C)]` PODs / fn-ptrs, so all three // shims are declared `safe fn`. `getSourceCode` writes a `ZigString` view into diff --git a/src/jsc/JSGlobalObject.rs b/src/jsc/JSGlobalObject.rs index b508beadba07..b480d6a012d2 100644 --- a/src/jsc/JSGlobalObject.rs +++ b/src/jsc/JSGlobalObject.rs @@ -253,7 +253,6 @@ impl JSGlobalObject { #[inline] pub fn throw_missing_arguments_value(&self, arg_names: &[&str]) -> JsError { - // PORT NOTE: Zig version is comptime over `arg_names.len` (0 => @compileError). match arg_names.len() { 0 => unreachable!("requires at least one argument"), 1 => self @@ -291,8 +290,6 @@ impl JSGlobalObject { field: &'static str, typename: &'static str, ) -> JSValue { - // PORT NOTE: Zig used std.fmt.comptimePrint here; const_format::formatcp! - // requires the literals at the macro callsite, so we format at runtime. self.err( JscError::INVALID_ARG_TYPE, format_args!("Expected {} to be a {} for '{}'.", field, typename, name_), @@ -301,8 +298,6 @@ impl JSGlobalObject { } pub fn to_js>(&self, value: T) -> JsResult { - // PORT NOTE: Zig `JSValue.fromAny(this, @TypeOf(value), value)` reflects on the - // type. Rust callers go through `From for JSValue` impls (i32, f64, bool, …). Ok(value.into()) } @@ -655,9 +650,8 @@ impl JSGlobalObject { } pub fn create_error_instance(&self, args: Arguments<'_>) -> JSValue { - // PORT NOTE: Zig branched at comptime on whether `args` is empty. With - // `core::fmt::Arguments`, `as_str()` returns `Some(&'static str)` when - // there are no interpolated args — equivalent fast path. + // `core::fmt::Arguments::as_str()` returns `Some(&'static str)` when + // there are no interpolated args — fast path for constant messages. if let Some(fmt) = args.as_str() { if strings::is_all_ascii(fmt.as_bytes()) { return BunString::static_str(fmt).to_error_instance(self); @@ -795,13 +789,11 @@ impl JSGlobalObject { } pub fn throw_pretty(&self, args: Arguments<'_>) -> JsError { - // PORT NOTE: Zig switched on `Output.enable_ansi_colors_stderr` and - // rewrote the *format string* at comptime (`Output.prettyFmt(fmt, - // enabled)`). Rust can't rewrite the format string of an - // already-captured `Arguments<'_>`, so render first, then run the - // `` → ANSI/strip pass at runtime via `pretty_fmt_rt`. + // The format string of an already-captured `Arguments<'_>` can't be + // rewritten, so render first, then run the `` → ANSI/strip pass + // at runtime via `pretty_fmt_rt`. // - // Zig routed through `createErrorInstance` which catches a mid-format + // The Zig original routed through `createErrorInstance` which catches a mid-format // `WriteFailed` (e.g. user `Symbol.toPrimitive` throws while // stringifying the Received value). `pretty_fmt_rt` would `format!` // into a `String`, and `format!` panics if a `Display` impl returns @@ -1237,11 +1229,6 @@ impl JSGlobalObject { .throw() } - // PORT NOTE: Zig's `validateBigIntRange` / `validateIntegerRange` / `getInteger` - // take `comptime T: type` plus a `comptime range: IntegerRange` with - // `comptime_int` bounds and use @typeInfo for signedness, comptime @max/@min - // clamping, and @compileError on bad ranges. Ported as plain generics over - // `T: bun_core::Integer`; the comptime bounds checks become `debug_assert!`. pub fn validate_big_int_range( &self, value: JSValue, @@ -1306,11 +1293,9 @@ impl JSGlobalObject { let min_t: i128 = range.min.max(T::MIN_I128).max(i128::from(MIN_SAFE_INTEGER)); let max_t: i128 = range.max.min(T::MAX_I128).min(i128::from(MAX_SAFE_INTEGER)); - // PORT NOTE: comptime { if (min_t > max_t) @compileError(...) } — became debug_assert. - debug_assert!(min_t <= max_t, "max must be less than min"); + debug_assert!(min_t <= max_t, "min must be less than or equal to max"); let field_name = range.field_name; - // PORT NOTE: comptime field_name.len == 0 → @compileError. debug_assert!(!field_name.is_empty(), "field_name must not be empty"); let always_allow_zero = range.always_allow_zero; // Zig passes the *unclamped* `range.min`/`range.max` to `throwRangeError` @@ -1401,8 +1386,6 @@ impl JSGlobalObject { /// /// The set of errors accepted by `err()` is defined in `ErrorCode.ts`. pub fn err<'a>(&'a self, code: JscError, args: Arguments<'a>) -> ErrorBuilder<'a, Self> { - // PORT NOTE: Zig `ERR` returns a comptime-monomorphized `ErrorBuilder(code, fmt, @TypeOf(args))`. - // The Rust ErrorBuilder carries the code + Arguments at runtime. ErrorBuilder { global: self, code, @@ -1516,8 +1499,8 @@ pub use crate::GregorianDateTime; /// type — no mirror enum, no transmute. pub use bun_bundler::transpiler::BunPluginTarget; -// PORT NOTE: no `Default` derive — Zig's `code: jsc.Node.ErrorCode` has NO default -// (only `errno`/`name` default to null). Callers must always supply `code`. +// No `Default` derive — `code` has no default (only `errno`/`name` are +// optional). Callers must always supply `code`. pub struct SysErrOptions { pub code: NodeErrorCode, pub errno: Option, @@ -1611,7 +1594,7 @@ pub(crate) extern "C" fn Zig__GlobalObject__onCrash() { panic!("A C++ exception occurred"); } -// PORT NOTE (LAYERING): `getBodyStreamOrBytesForWasmStreaming` deals entirely +// LAYERING: `getBodyStreamOrBytesForWasmStreaming` deals entirely // in `webcore` types (`Response`, `Body.Value`, `Blob`, `ReadableStream`) // which live in `bun_runtime`. The exported `extern "C"` symbol // `Zig__GlobalObject__getBodyStreamOrBytesForWasmStreaming` is therefore diff --git a/src/jsc/JSMap.rs b/src/jsc/JSMap.rs index 0f20a4314d92..388e7142f8a0 100644 --- a/src/jsc/JSMap.rs +++ b/src/jsc/JSMap.rs @@ -66,8 +66,6 @@ impl JSMap { /// GC-heap cell; callers must dereference unsafely at use-site and ensure the /// underlying `JSValue` is kept alive across GC. pub fn from_js(value: JSValue) -> Option> { - // PORT NOTE: Zig used `jsTypeLoose() == .JSMap`; the Rust stub surface - // exposes `is_cell()` + `js_type()` (which together are equivalent). if value.is_cell() && value.js_type() == crate::JSType::Map { // SAFETY: value is a Map cell; its encoded pointer is a valid, // non-null *JSMap on the GC heap. diff --git a/src/jsc/JSModuleLoader.rs b/src/jsc/JSModuleLoader.rs index 3567edfaa9f8..47e91dcda245 100644 --- a/src/jsc/JSModuleLoader.rs +++ b/src/jsc/JSModuleLoader.rs @@ -6,8 +6,6 @@ bun_opaque::opaque_ffi! { pub struct JSModuleLoader; } -// TODO(port): move to jsc_sys -// // `JSGlobalObject` is an opaque ZST handle on the Rust side; Rust never reads or // writes bytes through it. C++ mutates VM state internally, but that is outside // Rust's aliasing model, so these externs take `*const JSGlobalObject` (matching diff --git a/src/jsc/JSObject.rs b/src/jsc/JSObject.rs index 964d48ee15b7..fc5323b691ef 100644 --- a/src/jsc/JSObject.rs +++ b/src/jsc/JSObject.rs @@ -4,7 +4,6 @@ use core::mem::ManuallyDrop; use crate::{JSCell, JSGlobalObject, JSValue, JsError, JsResult}; use bun_core::{String as BunString, ZigString}; -// TODO(port): move to jsc_sys unsafe extern "C" { // safe: read-only `const unsigned` exported by C++ (link-time constant). safe static JSC__JSObject__maxInlineCapacity: c_uint; @@ -92,9 +91,9 @@ impl JSObject { pojo: &T, global: &JSGlobalObject, ) -> JsResult<&'static mut JSObject> { - // TODO(port): Zig used `@typeInfo(T).@"struct"` to enumerate fields at - // comptime. Rust has no field reflection; `PojoFields` is expected to be - // provided by a `#[derive(PojoFields)]` proc-macro that emits an inline + // Zig used `@typeInfo(T).@"struct"` to enumerate fields at comptime. + // Rust has no field reflection; `PojoFields` impls are hand-written + // (see trait docs) and emit an inline // `put(b"name", JSValue::from_any(global, &self.name)?)?;` per field. let val = if NULL_PROTOTYPE { @@ -111,7 +110,7 @@ impl JSObject { let obj = JSObject::opaque_mut(val.0 as *mut JSObject); let cell = obj.to_js(); - // PORT NOTE: Zig used `inline for` — each `fromAny` result is `put()` immediately + // Zig used `inline for` — each `fromAny` result is `put()` immediately // before the next field is encoded. The callback shape preserves that ordering and // keeps every intermediate JSValue on the stack (never collected into a Vec). pojo.put_fields(global, |name, value| { @@ -147,9 +146,9 @@ impl JSObject { global: &JSGlobalObject, properties: &T, ) -> JsResult<()> { - // TODO(port): Zig used `std.meta.fieldNames(@TypeOf(properties))` + - // `@field(properties, field)`. Relies on the `JSValueFields` derive. - // PORT NOTE: Zig's `put` signature forces each field to already be a JSValue — + // Zig used `std.meta.fieldNames(@TypeOf(properties))` + + // `@field(properties, field)`; here types hand-implement `JSValueFields`. + // Zig's `put` signature forces each field to already be a JSValue — // there is NO `fromAny` encoding here (unlike `create`). Hence a separate trait // from `PojoFields` that yields raw JSValues without conversion. properties.put_fields(|name, value| self.put(global, name, value)) @@ -324,16 +323,15 @@ extern "C" fn initializer_call( /// /// Zig used `@typeInfo(T)` to iterate struct fields and called /// `JSValue.fromAny(global, @TypeOf(property), property)` per field. -/// Rust has no built-in reflection, so types opt in via -/// `#[derive(bun_jsc::PojoFields)]`. +/// Rust has no built-in reflection, so types opt in by hand-implementing +/// this trait (no derive macro exists today). /// -/// The derive must emit a sequence of +/// Implementations must emit a sequence of /// `put(b"name", JSValue::from_any(global, &self.name)?)?;` calls — one per /// field, in declaration order — so each encoded JSValue lives only on the /// stack between `from_any` and `put` (matching Zig's `inline for`; never /// collected into a `Vec`, which would sit on the Rust heap and be /// invisible to JSC's conservative stack scan). -// TODO(port): proc-macro — implement `#[derive(PojoFields)]` in bun_jsc. pub trait PojoFields { const FIELD_COUNT: usize; /// Invoke `put(field_name, encoded_value)` once per struct field, encoding @@ -351,7 +349,6 @@ pub trait PojoFields { /// straight to `put()` with no `fromAny` encoding). /// /// Separate from [`PojoFields`] because that trait encodes; this one does not. -// TODO(port): proc-macro — implement `#[derive(JSValueFields)]` in bun_jsc. pub trait JSValueFields { /// Invoke `put(field_name, self.)` once per struct field. Fields are /// `JSValue` and forwarded as-is. diff --git a/src/jsc/JSPromise.rs b/src/jsc/JSPromise.rs index 60276eebdf38..16da108393e7 100644 --- a/src/jsc/JSPromise.rs +++ b/src/jsc/JSPromise.rs @@ -26,8 +26,6 @@ pub enum Status { Rejected = 2, } -// TODO(port): move to jsc_sys -// // `JSPromise` and `JSGlobalObject` are opaque `UnsafeCell`-backed ZST handles // (so `&T` is ABI-identical to non-null `*const T` and C++ mutating through // the pointer is interior mutation invisible to Rust). The shims that take @@ -118,7 +116,7 @@ impl Weak { ref_type: WeakRefType, ctx: &mut T, ) -> Self { - // PORT NOTE: Zig threaded a `comptime finalizer` fn-ptr; the Rust + // Zig threaded a `comptime finalizer` fn-ptr; the Rust // `Weak` encodes that via `WeakRefType` (one variant per finalizer // — see Weak.rs). PERF(port): was comptime monomorphization. Self { @@ -258,7 +256,7 @@ impl Strong { } /// Wrap an existing promise `JSValue` in a fresh Strong handle. - /// PORT NOTE: Zig copies `JSPromise.Strong` by value (HandleSlot ptr is + /// Zig copies `JSPromise.Strong` by value (HandleSlot ptr is /// shared); Rust `Strong` owns its slot, so a literal copy would /// double-free. Callers that need a second owner of the same promise /// (e.g. `bake::DevServer::PromiseEnsureRouteBundledCtx::ensurePromise`) @@ -333,9 +331,12 @@ impl JSPromise { /// and built a `callconv(.c)` trampoline via `jsc.toJSHostCall`. That is the /// host-fn reflection pattern — in Rust it collapses to a monomorphized closure /// + extern-C trampoline. - // TODO(port): proc-macro — the Zig version threads `@src()` and uses - // `jsc.toJSHostCall` for exception-scope plumbing. Verify the - // closure form below is ABI-equivalent or replace with `#[bun_jsc::host_fn]`. + // + // The closure form below is equivalent to Zig's (JSPromise.zig:187-209): + // the trampoline routes through `crate::to_js_host_call` (= `jsc.toJSHostCall`, + // with `#[track_caller]` standing in for `@src()`), and the surrounding + // `top_scope!` + `assert_no_exception_except_termination` match the Zig + // exception-scope plumbing exactly. pub fn wrap(global: &JSGlobalObject, f: F) -> Result where F: FnOnce(&JSGlobalObject) -> JsResult, diff --git a/src/jsc/JSPropertyIterator.rs b/src/jsc/JSPropertyIterator.rs index 814716a310d9..66a4d5522614 100644 --- a/src/jsc/JSPropertyIterator.rs +++ b/src/jsc/JSPropertyIterator.rs @@ -111,7 +111,7 @@ pub struct JSPropertyIterator<'a> { pub object: *mut JSObject, /// Current property value being yielded (only meaningful when /// `options.include_value` is set). - // PORT NOTE: bare JSValue field is sound because this struct is stack-only (`'a` borrow); + // The bare JSValue field is sound because this struct is stack-only (`'a` borrow); // conservative stack scan keeps it alive. Do NOT box this struct. pub value: JSValue, @@ -283,10 +283,9 @@ impl JSPropertyIteratorImpl { property_name: &mut bstr::String, i: usize, ) -> JsResult { - // PORT NOTE: Zig wrapped this in a manual `TopExceptionScope.init/deinit` + - // `returnIfException`; that is exactly `from_js_host_call_generic`'s contract - // (the FFI may return `.zero` without throwing, so the non-generic - // `from_js_host_call` — which treats empty as thrown — is wrong here). + // The FFI may return `.zero` without throwing, so the non-generic + // `from_js_host_call` — which treats empty as thrown — is wrong here; + // `from_js_host_call_generic` checks the exception scope instead. from_js_host_call_generic(global_object, || { Bun__JSPropertyIterator__getNameAndValue(iter, global_object, object, property_name, i) }) diff --git a/src/jsc/JSRef.rs b/src/jsc/JSRef.rs index 29cfdbdacac6..fadf7d9c3ba7 100644 --- a/src/jsc/JSRef.rs +++ b/src/jsc/JSRef.rs @@ -1,8 +1,7 @@ use core::marker::PhantomData; -// PORT NOTE: JSRef.zig stores `jsc.Strong.Optional`, not `jsc.Strong`. The -// methods below (`get() -> Option`, `has()`, `try_swap()`) live on the -// Optional wrapper, so import it under the local name `Strong`. +// The methods used below (`get() -> Option`, `has()`, `try_swap()`) live on +// the Optional wrapper, so import it under the local name `Strong`. use crate::strong::Optional as Strong; use crate::{JSGlobalObject, JSValue}; @@ -152,9 +151,8 @@ impl JsRef { match self { JsRef::Weak(_) => {} JsRef::Strong(_) => { - // PORT NOTE: Zig calls `this.strong.deinit()` here. In Rust, // `Strong`'s `Drop` deallocates the HandleSlot when `*self` is - // overwritten below, so the explicit call is elided. + // overwritten below, so no explicit deinit is needed. } JsRef::Finalized => { return; @@ -192,10 +190,9 @@ impl JsRef { JsRef::Strong(strong) => { let value = strong.try_swap().unwrap_or(JSValue::UNDEFINED); value.ensure_still_alive(); - // PORT NOTE: Zig calls `strong.deinit()` here; in Rust the old - // `Strong` is dropped by the assignment below. - // PORT NOTE: reshaped for borrowck — `strong` borrow ends at - // last use above, permitting reassignment of `*self`. + // The old `Strong` is dropped by the assignment below; the + // `strong` borrow ends at its last use above, permitting + // reassignment of `*self`. *self = JsRef::Weak(value); } JsRef::Finalized => {} @@ -224,9 +221,8 @@ impl JsRef { } pub fn finalize(&mut self) { - // PORT NOTE: Zig calls `self.deinit()` then sets `.finalized`. In Rust, - // overwriting `*self` drops the prior variant (releasing the `Strong` - // HandleSlot via its `Drop`), so the explicit deinit step is elided. + // Overwriting `*self` drops the prior variant (releasing the `Strong` + // HandleSlot via its `Drop`), so no explicit deinit step is needed. // External `jsref.deinit()` callers become `*jsref = JsRef::empty()`. *self = JsRef::Finalized; } diff --git a/src/jsc/JSRuntimeType.rs b/src/jsc/JSRuntimeType.rs index e86619b5142c..94c0fbe9a2bb 100644 --- a/src/jsc/JSRuntimeType.rs +++ b/src/jsc/JSRuntimeType.rs @@ -1,7 +1,7 @@ /// Represents JavaScript runtime value types -// PORT NOTE: Zig `enum(u16) { ... , _ }` is non-exhaustive — any u16 bit-pattern is a -// valid value (and the discriminants are bitflags). A `#[repr(u16)] enum` would be UB -// for unlisted values, so map to a transparent newtype with associated consts instead. +// Any u16 bit-pattern is a valid value (the discriminants are bitflags). A +// `#[repr(u16)] enum` would be UB for unlisted values, so this is a transparent +// newtype with associated consts instead. #[repr(transparent)] #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct JSRuntimeType(pub u16); diff --git a/src/jsc/JSSecrets.rs b/src/jsc/JSSecrets.rs index a777bb697d29..154fa6ea02e5 100644 --- a/src/jsc/JSSecrets.rs +++ b/src/jsc/JSSecrets.rs @@ -3,8 +3,6 @@ use crate::{AnyTaskJob, AnyTaskJobCtx, JSGlobalObject, JSValue, JsResult, Strong // Opaque pointer to C++ SecretsJobOptions struct bun_opaque::opaque_ffi! { pub struct SecretsJobOptions; } -// TODO(port): move to _sys -// // safe fn: `SecretsJobOptions` and `JSGlobalObject` are `opaque_ffi!` ZST // handles (`!Freeze` via `UnsafeCell`); `&mut`/`&` are ABI-identical to // non-null `*mut`/`*const` and C++ mutating job state through them is interior diff --git a/src/jsc/JSString.rs b/src/jsc/JSString.rs index 7351771d6077..1dbc4e37c527 100644 --- a/src/jsc/JSString.rs +++ b/src/jsc/JSString.rs @@ -11,8 +11,6 @@ bun_opaque::opaque_ffi! { pub struct JSString; } -// TODO(port): move to jsc_sys -// // NOTE: Zig declares several of these params as `*JSString` / `*JSGlobalObject` // (mutable), but the C ABI does not distinguish `*const T` from `*mut T`. We // intentionally declare them `*const` here — matching the convention in diff --git a/src/jsc/JSType.rs b/src/jsc/JSType.rs index b53b57d3bf37..2d56415ca9ce 100644 --- a/src/jsc/JSType.rs +++ b/src/jsc/JSType.rs @@ -92,10 +92,10 @@ use crate::array_buffer::TypedArrayType; -// PORT NOTE: Zig's `enum(u8) { ..., _ }` is non-exhaustive — any u8 value is a valid -// JSType (values are read directly from JSCell::m_type via FFI, including embedder- -// defined types). A plain `#[repr(u8)] enum` would be UB for unknown discriminants, -// so this is a transparent newtype with associated consts instead. +// Any u8 value is a valid JSType (values are read directly from JSCell::m_type +// via FFI, including embedder-defined types). A plain `#[repr(u8)] enum` would +// be UB for unknown discriminants, so this is a transparent newtype with +// associated consts instead. #[repr(transparent)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, core::marker::ConstParamTy)] pub struct JSType(pub u8); diff --git a/src/jsc/JSUint8Array.rs b/src/jsc/JSUint8Array.rs index a8bbf5c76fe5..909017d1816a 100644 --- a/src/jsc/JSUint8Array.rs +++ b/src/jsc/JSUint8Array.rs @@ -33,7 +33,7 @@ impl JSUint8Array { } pub fn slice(&mut self) -> &mut [u8] { - // PORT NOTE: detached/empty JSUint8Array has ptr=null, len=0. Zig `ptr[0..0]` is + // Note: detached/empty JSUint8Array has ptr=null, len=0. Zig `ptr[0..0]` is // valid for any ptr; `ffi::slice_mut` tolerates `(null, 0)` so no extra guard. // SAFETY: JSC guarantees `ptr()` is valid for `len()` bytes while the cell is alive. unsafe { bun_core::ffi::slice_mut(self.ptr(), self.len()) } @@ -41,7 +41,7 @@ impl JSUint8Array { /// `bytes` must come from `bun.default_allocator` (the global mimalloc allocator); /// ownership is transferred to the returned JS Uint8Array. - // PORT NOTE: Zig took `[]u8` + a doc comment requiring default_allocator provenance. + // Note: Zig took `[]u8` + a doc comment requiring default_allocator provenance. // In Rust the global allocator IS mimalloc, so `Box<[u8]>` encodes that ownership. pub fn from_bytes(global: &JSGlobalObject, bytes: Box<[u8]>) -> JSValue { let len = bytes.len(); @@ -69,7 +69,6 @@ impl JSUint8Array { } } -// TODO(port): move to jsc_sys unsafe extern "C" { fn JSUint8Array__fromDefaultAllocator( global: *const JSGlobalObject, diff --git a/src/jsc/JSValue.rs b/src/jsc/JSValue.rs index dcad9d619071..d569978d0e8d 100644 --- a/src/jsc/JSValue.rs +++ b/src/jsc/JSValue.rs @@ -24,9 +24,10 @@ use crate::{ /// `PhantomData<*const ()>` enforces `!Send + !Sync` (negative impls are /// nightly-only and not used here for portability of the auto-trait inference). // -// TODO(port): inner type should be `i64` per spec; kept `usize` (same width on -// all supported 64-bit targets) to avoid a cascading bit-twiddle / pointer-cast -// rewrite across already-un-gated leaf modules that pattern-match on `.0`. +// The spec encoding is `i64` (Zig: `enum(i64)`); the inner field is `usize` +// (same width on all supported 64-bit targets — see the size assert below) +// because leaf modules pattern-match on `.0` with pointer/unsigned arithmetic. +// `BackingInt`/`from_raw` are the signed views where sign-correct math matters. #[repr(transparent)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct JSValue(pub usize, PhantomData<*const ()>); @@ -1202,7 +1203,7 @@ impl JSValue { if prop.is_symbol() { // JSValue.zig:1693 — `throwInvalidPropertyTypeValue(property, "string", prop)` // (Node-style ERR_INVALID_ARG_TYPE TypeError including received value's type). - // PORT NOTE: routed via `throw_invalid_arguments` until + // Note: routed via `throw_invalid_arguments` until // `JSGlobalObject::throw_invalid_property_type_value` is ported. return Err(global.throw_invalid_arguments(format_args!( "The \"{}\" property must be of type string. Received a symbol", @@ -1659,7 +1660,7 @@ impl JSValue { this_value: JSValue, args: &[JSValue], ) -> JsResult { - // PORT NOTE: debug-only event-loop bookkeeping (JSValue.zig:251-258) is + // Note: debug-only event-loop bookkeeping (JSValue.zig:251-258) is // omitted while VirtualMachine.rs is gated; restore when it un-gates. host_fn::from_js_host_call(global, || { // SAFETY: `global` is live; `args` is a contiguous slice of valid diff --git a/src/jsc/MarkedArgumentBuffer.rs b/src/jsc/MarkedArgumentBuffer.rs index 0a7dfea2630c..1eeabaa91d53 100644 --- a/src/jsc/MarkedArgumentBuffer.rs +++ b/src/jsc/MarkedArgumentBuffer.rs @@ -8,7 +8,6 @@ bun_opaque::opaque_ffi! { pub struct MarkedArgumentBuffer; } -// TODO(port): move to jsc_sys unsafe extern "C" { safe fn MarkedArgumentBuffer__append(args: &MarkedArgumentBuffer, value: JSValue); // safe: `ctx` is an opaque round-trip pointer C++ only forwards to `f` @@ -76,8 +75,6 @@ impl MarkedArgumentBuffer { /// `fn(*JSGlobalObject, *CallFrame, *MarkedArgumentBuffer) bun.JSError!JSValue` /// and returns a `jsc.JSHostFnZig`. Rust cannot parameterize a `fn` item by a const /// fn-pointer, so this is a macro that expands to a `#[bun_jsc::host_fn]` wrapper. -// TODO(port): consider a proc-macro attribute (`#[bun_jsc::with_marked_argument_buffer]`) -// instead of `macro_rules!` once the host_fn codegen is settled. #[macro_export] macro_rules! marked_argument_buffer_wrap { ($function:path) => {{ @@ -106,7 +103,7 @@ macro_rules! marked_argument_buffer_wrap { let mut ctx = Context { global_this, callframe, - // PORT NOTE: Zig used `undefined`; init with a placeholder since `run` + // Zig used `undefined`; init with a placeholder since `run` // unconditionally overwrites it before we read. result: ::core::result::Result::Ok($crate::JSValue::ZERO), }; diff --git a/src/jsc/ModuleLoader.rs b/src/jsc/ModuleLoader.rs index a1e893b93e22..cb2cc67bd838 100644 --- a/src/jsc/ModuleLoader.rs +++ b/src/jsc/ModuleLoader.rs @@ -53,17 +53,22 @@ pub(crate) fn set_is_allowed_to_use_internal_testing_apis(v: bool) { impl ModuleLoader { /// This must be called after calling transpileSourceCode /// - /// PORT NOTE: takes only `&mut VirtualMachine` (not `&mut self, + /// Takes only `&mut VirtualMachine` (not `&mut self, /// &mut VirtualMachine`) — `ModuleLoader` is a value field of /// `VirtualMachine`, so passing both would alias (PORTING.md §Forbidden). /// Access `module_loader` through `jsc_vm` instead. pub fn reset_arena(jsc_vm: &mut VirtualMachine) { // Spec ModuleLoader.zig:24-29: `if (smol) reset() else - // reset(.{.retain_with_limit = 8M})`. The port collapses both arms to - // `reset()` — `MimallocArena` is not a bump allocator, so there is no - // capacity to retain (see `MimallocArena::reset_retain_with_limit` - // PORT NOTE); mimalloc's per-thread segment cache already provides the - // warm-page reuse Zig's `.retain_with_limit` was after. + // reset(.{.retain_with_limit = 8M})`. The port currently collapses + // both arms to `reset()`. That DIVERGES from the spec: per + // `MimallocArena::reset_retain_with_limit`'s doc comment, the + // "mimalloc's segment cache keeps pages warm anyway" theory behind + // unconditional `reset()` proved wrong (purged pages get re-committed + // and re-zeroed each cycle), which is why the cap-gated retain exists + // and the other call sites use `reset_retain_with_limit(8 MiB)`. + // Restoring the `!smol` arm here is a perf-sensitive change that + // needs benchmarking (transpile arena RSS vs cycle cost), so it is + // tracked as a dedicated work order rather than changed inline. if let Some(arena) = jsc_vm.module_loader.transpile_source_code_arena.as_mut() { arena.reset(); } @@ -149,9 +154,9 @@ pub struct TranspileArgs<'a> { /// `bun_runtime`) so both tiers agree on layout; every field type is already a /// `bun_jsc` dep (`bun_resolver`, `bun_bundler::options`, `bun_js_printer`). /// -/// PORT NOTE: Zig passed these as positional params to `transpileSourceCode` -/// (ModuleLoader.zig:90-96). They're bundled because the §Dispatch fn-ptr -/// signature must be stable across the crate boundary. +/// Bundled into one struct (rather than positional params, as in +/// ModuleLoader.zig:90-96) because the §Dispatch fn-ptr signature must be +/// stable across the crate boundary. #[repr(C)] pub struct TranspileExtra { pub path: bun_resolver::fs::Path<'static>, @@ -362,10 +367,9 @@ pub fn resolve_maybe_needs_trailing_slash( /// ModuleLoader.cpp:473) rejects the import promise with a real Error instead /// of `undefined`. /// -/// PORT NOTE: previously routed through `LoaderHooks` on the assumption the -/// body needed `bun_runtime` types; it doesn't — `BuildMessage` / -/// `ResolveMessage` live in this crate — so the hook slot was dropped and this -/// forwards to the real impl in [`crate::virtual_machine::process_fetch_log`]. +/// No `LoaderHooks` indirection is needed here — `BuildMessage` / +/// `ResolveMessage` live in this crate — so this forwards to the real impl in +/// [`crate::virtual_machine::process_fetch_log`]. pub fn process_fetch_log( global: &JSGlobalObject, specifier: bun_core::String, @@ -440,7 +444,7 @@ pub(crate) unsafe extern "C" fn Bun__fetchBuiltinModule( &mut *ret, ) }; - // PORT NOTE: spec ModuleLoader.zig:861-876 — when `fetchBuiltinModule` + // Spec ModuleLoader.zig:861-876: when `fetchBuiltinModule` // ERRORS, it calls `VirtualMachine.processFetchLog(..., ret, err)` and // returns **true** (so C++ surfaces the error instead of falling through to // filesystem resolution). The hook writes `ret` directly on Found/Errored. @@ -529,15 +533,15 @@ pub(crate) unsafe extern "C" fn ModuleLoader__isBuiltin(data: *const u8, len: us bun_aliases_get(str).is_some() } -// PORT NOTE (spec bundler_jsc/PluginRunner.zig:11-32): the pure byte-string +// Spec bundler_jsc/PluginRunner.zig:11-32: the pure byte-string // `extractNamespace` / `couldBePlugin` helpers live in // `bun_bundler::transpiler::PluginRunner` — `bun_bundler` is already a // `bun_jsc` dep, so `Bun__runVirtualModule` calls them directly rather than // duplicating them here. use bun_bundler::transpiler::PluginRunner; -// PORT NOTE: `ModuleLoader.resolveEmbeddedFile` (spec ModuleLoader.zig:33-71) -// has been MOVED to `bun_runtime::jsc_hooks::resolve_embedded_file_to_buf` +// `ModuleLoader.resolveEmbeddedFile` (spec ModuleLoader.zig:33-71) +// lives in `bun_runtime::jsc_hooks::resolve_embedded_file_to_buf` // per PORTING.md §Forbidden ("dep-cycle: MOVE the code to the right crate") — // the body reaches into `bun_standalone_graph` + `bun_sys::Tmpfile` + // `node::fs`, none of which are `bun_jsc` deps. Three Zig callers live in diff --git a/src/jsc/NodeModuleModule.rs b/src/jsc/NodeModuleModule.rs index 466e65b85879..3505072dfa6b 100644 --- a/src/jsc/NodeModuleModule.rs +++ b/src/jsc/NodeModuleModule.rs @@ -143,8 +143,6 @@ impl Default for CustomLoader { } } -// TODO(port): move to jsc_sys -// // `JSGlobalObject` is an opaque `UnsafeCell`-backed ZST handle; remaining // params are by-value `JSValue`/scalars → `safe fn`. unsafe extern "C" { diff --git a/src/jsc/PluginRunner.rs b/src/jsc/PluginRunner.rs index 0296e40a8f82..b76907fbfe11 100644 --- a/src/jsc/PluginRunner.rs +++ b/src/jsc/PluginRunner.rs @@ -19,8 +19,6 @@ use bun_ptr::BackRef; /// Spec PluginRunner.zig:7. pub struct PluginRunner { pub global_object: BackRef, - // PORT NOTE: Zig stored `allocator: std.mem.Allocator`; dropped per - // PORTING.md (global mimalloc). } // Re-export the JSC-free static helpers so callers in this crate can keep @@ -149,8 +147,6 @@ impl PluginResolver for PluginRunner { // Spec PluginRunner.zig:121 `defer user_namespace.deref()`. let user_namespace = OwnedString::new(user_namespace); - // PORT NOTE: Zig used `std.fmt.allocPrint(this.allocator, …)` and - // returned the allocator-owned slice by value inside `Fs.Path`. // `FsPath<'static>` borrows, so the formatted buffer is leaked to // model the same caller-owns-forever contract (the Zig path also // never frees these — the linker arena owns them for the build). diff --git a/src/jsc/PosixSignalHandle.rs b/src/jsc/PosixSignalHandle.rs index 6569e6330a9a..03fc9458ebb1 100644 --- a/src/jsc/PosixSignalHandle.rs +++ b/src/jsc/PosixSignalHandle.rs @@ -98,7 +98,7 @@ impl PosixSignalHandle { #[allow(dead_code)] pub(crate) fn drain(&self, event_loop: &mut EventLoop) { while let Some(signal) = self.dequeue() { - // PORT NOTE: Zig stamps the discriminant via `Task.init(&stack_marker)` then + // Note: Zig stamps the discriminant via `Task.init(&stack_marker)` then // overwrites the packed `_ptr` bitfield with `setUintptr(signal)`. The Rust // `Task` is a plain `{ tag, ptr }` pair (no bitfield packing), so build it // directly — `bun_runtime::dispatch::run_task` unpacks `task.ptr as usize as u8`. @@ -143,7 +143,6 @@ impl Taskable for PosixSignalTask { const TAG: bun_event_loop::TaskTag = task_tag::PosixSignalTask; } -// TODO(port): move to _sys unsafe extern "C" { safe fn Bun__onSignalForJS(number: i32, global_object: &JSGlobalObject); } diff --git a/src/jsc/RefString.rs b/src/jsc/RefString.rs index c8819f777c77..5f5a9ae745eb 100644 --- a/src/jsc/RefString.rs +++ b/src/jsc/RefString.rs @@ -91,8 +91,6 @@ impl RefString { /// SAFETY: `this` must be the unique live reference to a `RefString` /// previously allocated via `heap::alloc` (or equivalent). After this /// call `this` is dangling. - // TODO(port): revisit ownership — intrusive refcount via - // WTF::StringImpl; may become `impl Drop` if `RefString` ends up `Box`-owned. pub unsafe fn destroy(this: *mut RefString) { // SAFETY: caller contract — `this` is the unique live pointer to a // `Box`-allocated value whose `ptr`/`len` describe a diff --git a/src/jsc/RegularExpression.rs b/src/jsc/RegularExpression.rs index 52abbf2a0e2c..3637049fc782 100644 --- a/src/jsc/RegularExpression.rs +++ b/src/jsc/RegularExpression.rs @@ -28,8 +28,6 @@ pub enum RegularExpressionError { bun_core::named_error_set!(RegularExpressionError); -// TODO(port): move to bun_jsc_sys -// // `RegularExpression` is an opaque `UnsafeCell`-backed ZST handle, so // `&RegularExpression` is ABI-identical to a non-null `*const` and C++ mutating // internal Yarr state through it is interior mutation invisible to Rust. The @@ -61,7 +59,6 @@ impl RegularExpression { unsafe { Self::destroy(regex) }; return Err(RegularExpressionError::InvalidRegExp); } - // TODO(port): consider an owning wrapper with Drop instead of returning a raw *mut. Ok(regex) } diff --git a/src/jsc/ResolveMessage.rs b/src/jsc/ResolveMessage.rs index bd994a3bb38c..c36c5cac6f06 100644 --- a/src/jsc/ResolveMessage.rs +++ b/src/jsc/ResolveMessage.rs @@ -16,10 +16,10 @@ use crate::{ #[crate::JsClass] pub struct ResolveMessage { pub msg: bun_ast::Msg, - // PORT NOTE: Zig stored `allocator: std.mem.Allocator` here; dropped — fields own their + // Note: Zig stored `allocator: std.mem.Allocator` here; dropped — fields own their // allocations and free on Drop / finalize. // - // PORT NOTE: Zig stored `referrer: ?Fs.Path` and only ever read `.text`; + // Note: Zig stored `referrer: ?Fs.Path` and only ever read `.text`; // store the duped text directly so we don't pull in `bun_paths::fs::Path` // (which is lifetime-parameterised over its backing buffer). pub referrer: Option>, @@ -149,7 +149,7 @@ impl ResolveMessage { .ok(); return out; } - // PORT NOTE: matching against interned bun_core::Error consts (Zig: `switch (err)`). + // Note: matching against interned bun_core::Error consts (Zig: `switch (err)`). if err == bun_core::err!("ModuleNotFound") { if referrer == b"bun:main" { write!(&mut out, "Module not found '{}'", BStr::new(specifier)).ok(); @@ -237,8 +237,9 @@ impl ResolveMessage { return out; } - // TODO(port): `toExternalValue` transfers ownership of `text` to JSC; ensure - // `ZigString::to_external_value` consumes the Vec without double-free. + // `to_external_value` transfers ownership of `text` to JSC: the Box is + // leaked here (single transfer via `heap::release`) and freed exactly + // once by JSC's external-string finalizer with the global allocator. let leaked = text.into_boxed_slice(); let mut str = ZigString::init(bun_core::heap::release(leaked)); str.set_output_encoding(); diff --git a/src/jsc/ResolvedSource.rs b/src/jsc/ResolvedSource.rs index 0d64d35f0f6b..0dae7bbed946 100644 --- a/src/jsc/ResolvedSource.rs +++ b/src/jsc/ResolvedSource.rs @@ -8,9 +8,9 @@ use crate::JSValue; // half of the table is code-generated (see `generated_resolved_source_tag.rs`). pub use crate::resolved_source_tag::ResolvedSourceTag as Tag; -// PORT NOTE: `Copy` is required by `Errorable` (the `#[repr(C)]` -// tagged-union it travels through to C++). All fields are POD; `bun.String` is -// a tagged pointer pair and is `Copy` in the Rust port too. +// `Copy` is required by `Errorable` (the `#[repr(C)]` tagged-union it +// travels through to C++). All fields are POD; `BunString` is a tagged pointer +// pair and is `Copy`. #[repr(C)] #[derive(Copy, Clone)] pub struct ResolvedSource { @@ -28,7 +28,7 @@ pub struct ResolvedSource { /// the JSFunction extension. It is kept alive by /// - This structure is stored on the stack /// - There is a JSC::Strong reference to it - // PORT NOTE: bare JSValue field is sound here — ResolvedSource is #[repr(C)] and lives + // The bare JSValue field is sound here — ResolvedSource is #[repr(C)] and lives // on the stack while crossing to C++ (see comment above + headers-handwritten.h). pub cjs_custom_extension_index: JSValue, diff --git a/src/jsc/RuntimeTranspilerCache.rs b/src/jsc/RuntimeTranspilerCache.rs index 47e8006358d4..5bd56ece0e1f 100644 --- a/src/jsc/RuntimeTranspilerCache.rs +++ b/src/jsc/RuntimeTranspilerCache.rs @@ -158,9 +158,8 @@ impl Metadata { Ok(()) } - /// PORT NOTE: Zig took `anytype reader`; both call sites - /// (`from_file_with_cache_file_path`, the debug round-trip in `Entry::save`) - /// drive it from a `fixedBufferStream`, so we accept the concrete + /// Both call sites (`from_file_with_cache_file_path`, the debug round-trip + /// in `Entry::save`) drive this from a fixed buffer, so accept the concrete /// `bun_io::FixedBufferStream` over a borrowed slice. pub fn decode( &mut self, @@ -171,9 +170,9 @@ impl Metadata { return Err(bun_core::err!(StaleCache)); } - // PORT NOTE: reshaped for borrowck/enum-safety — Zig stored raw @enumFromInt then - // validated at the end; here we validate immediately so ModuleType never holds an - // out-of-range discriminant. + // Validate the raw discriminants immediately (rather than at the end, + // as the Zig original did) so `ModuleType` never holds an out-of-range + // value. let module_type_raw = reader.read_int_le::()?; let output_encoding_raw = reader.read_int_le::()?; @@ -252,9 +251,6 @@ pub struct Entry { } impl Entry { - /// PORT NOTE: Zig `deinit` took three allocator params; per §Allocators - /// (non-AST crate) the Rust port owns its buffers via `Box<[u8]>` / - /// `BunString`, so this is just an explicit `deref()` + drop. pub fn deinit(&mut self) { self.output_code.deinit(); self.sourcemap = Box::default(); @@ -309,9 +305,9 @@ impl Entry { }, output_encoding: match output_code { OutputCode::Utf8(_) => Encoding::UTF8, - // PORT NOTE: `bun_core::String` has no `.encoding()` yet; - // derive from the is_* predicates (same discrimination Zig's - // `String.encoding()` performs). + // `bun_core::String` has no `.encoding()`; derive it + // from the `is_*` predicates (the same discrimination + // Zig's `String.encoding()` performs). OutputCode::String(str) => { if str.is_utf16() { Encoding::UTF16 @@ -449,7 +445,7 @@ impl Entry { } else { match self.metadata.output_encoding { Encoding::UTF8 => { - // PORT NOTE / PERF: Zig threaded `output_code_allocator` + // PERF: Zig threaded `output_code_allocator` // (the per-call arena) here so the ~1.2 MB scratch buffer // was bump-freed with the parse arena. The Rust port // dropped that field and `pread_box`'d into a `Box<[u8]>` @@ -600,12 +596,10 @@ pub struct RuntimeTranspilerCache { pub exports_kind: ExportsKind, pub output_code: Option, pub entry: Option, - // PORT NOTE: Zig had sourcemap_allocator / output_code_allocator / esm_record_allocator - // fields. `sourcemap` / `esm_record` were `bun.default_allocator` at every - // call site so `Box<[u8]>` (global mimalloc) is equivalent. - // `output_code_allocator` was the per-call arena; rather than re-thread it, - // the UTF-8 load arm now preads straight into WTF storage (see - // `Entry::load`), so no arena scratch is needed at all. + // `sourcemap` / `esm_record` are owned `Box<[u8]>` (global mimalloc). + // The per-call arena that once backed the output code is gone: the UTF-8 + // load arm preads straight into WTF storage (see `Entry::load`), so no + // arena scratch is needed at all. } impl Default for RuntimeTranspilerCache { @@ -706,10 +700,10 @@ impl RuntimeTranspilerCache { return len; } - // PORT NOTE: Zig used `FileSystem.instance.absBufZ(parts, buf)`. The - // inline `bun_resolver::fs::FileSystem` surface only exposes `abs_buf` - // (no `_z`), so go straight to the underlying joiner with the same - // `top_level_dir` + `Loose` platform Zig's `absBufZ` used. + // The inline `bun_resolver::fs::FileSystem` surface only exposes + // `abs_buf` (no NUL-terminating `_z` variant), so go straight to the + // underlying joiner with the same `top_level_dir` + `Loose` platform + // that `absBufZ` used. let top = FileSystem::instance().top_level_dir; if let Some(dir) = env_var::XDG_CACHE_HOME.get() { @@ -751,10 +745,9 @@ impl RuntimeTranspilerCache { } // Only do this at most once per-thread. - // PORT NOTE: Zig used `bun.ThreadlocalBuffers(struct { buf: bun.PathBuffer })` - // plus a threadlocal `?[:0]const u8` pointing into it. Rust thread_local - // can't easily borrow into itself across calls, so we cache the resolved - // path bytes + length and re-copy into the caller's buffer on each call. + // A Rust thread_local can't easily hand out borrows into itself across + // calls, so cache the resolved path bytes + length and re-copy into the + // caller's buffer on each call. thread_local! { // bun.ThreadlocalBuffers: heap-backed so only a Box pointer lives in TLS. static CACHE_DIR_BUF: RefCell> = RefCell::new(Box::new(PathBuffer::ZEROED)); @@ -869,10 +862,9 @@ impl RuntimeTranspilerCache { let _tracer = bun_core::perf::trace("RuntimeTranspilerCache.toFile"); let mut cache_file_path_buf = PathBuffer::uninit(); - // PORT NOTE: Zig matched on `source_code.encoding()`; derive from - // `is_utf8()` instead. Zig's `.utf8` arm borrowed `source_code.byteSlice()` - // without copying; `OutputCode::Utf8` here owns a `Box<[u8]>`, so we - // copy. PERF(port): add a borrowed `OutputCode` variant to avoid the copy. + // Zig's `.utf8` arm borrowed `source_code.byteSlice()` without + // copying; `OutputCode::Utf8` here owns a `Box<[u8]>`, so we copy. + // PERF: add a borrowed `OutputCode` variant to avoid the copy. // // Zig: `else => .{ .string = source_code }` — by-value copy, **no** // `dupeRef()` and **no** matching `deref()`. `BunString` is `Copy` and @@ -958,8 +950,8 @@ impl RuntimeTranspilerCache { return false; } - // PORT NOTE: `bun_paths::fs::Path<'static>` is the trimmed TYPE_ONLY mirror and - // doesn't carry `is_file()` yet; inline the same check the resolver + // `bun_paths::fs::Path<'static>` is the trimmed TYPE_ONLY mirror and + // doesn't carry `is_file()`; inline the same check the resolver // `Path::is_file` performs (`namespace == "" || namespace == "file"`). if !(source.path.namespace.is_empty() || source.path.namespace == b"file") { return false; diff --git a/src/jsc/RuntimeTranspilerStore.rs b/src/jsc/RuntimeTranspilerStore.rs index 465d44b9bd60..165c232fea33 100644 --- a/src/jsc/RuntimeTranspilerStore.rs +++ b/src/jsc/RuntimeTranspilerStore.rs @@ -57,7 +57,7 @@ bun_core::declare_scope!(RuntimeTranspilerStore, hidden); // Debug source dumping (debug-only helpers; no-ops in release) // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: takes `NonNull` (not `&mut`) — these are called +// Note: takes `NonNull` (not `&mut`) — these are called // from the transpiler worker thread while the JS thread is concurrently live on // the same VM, so a `&mut VirtualMachine` would be a data race AND would alias // the caller's `&mut TranspilerJob` (which is stored inside @@ -84,7 +84,6 @@ pub(crate) fn dump_source_string_failiable( specifier: &[u8], written: &[u8], ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set if !cfg!(debug_assertions) { return Ok(()); } @@ -158,7 +157,7 @@ pub(crate) fn dump_source_string_failiable( use core::fmt::Write as _; let mut out = std::string::String::new(); - // PORT NOTE: closures can't unify input/output lifetimes for the + // Note: closures can't unify input/output lifetimes for the // `JSONFormatterUTF8<'_>` borrow — local fn item works. fn json(s: &[u8]) -> bun_core::fmt::JSONFormatterUTF8<'_> { bun_core::fmt::format_json_string_utf8( @@ -166,7 +165,7 @@ pub(crate) fn dump_source_string_failiable( bun_core::fmt::JSONFormatterUTF8Options::default(), ) } - // PORT NOTE: Zig used a 4 KiB buffered writer streaming to the fd; + // Note: Zig used a 4 KiB buffered writer streaming to the fd; // building the whole document in memory then `write_all` is // observationally identical for this debug-only dump. write!( @@ -190,7 +189,11 @@ pub(crate) fn dump_source_string_failiable( Ok(()) } -pub(crate) fn set_break_point_on_first_line() -> bool { +// `pub`: also consumed cross-crate by bun_runtime's ModuleLoader transpile path +// (runtime/jsc_hooks.rs, spec ModuleLoader.zig:244-247). The one-shot AtomicBool +// must stay shared between that path and this store — whichever transpiles the +// main module first consumes it. +pub fn set_break_point_on_first_line() -> bool { static SET_BREAK_POINT: AtomicBool = AtomicBool::new(true); SET_BREAK_POINT.swap(false, Ordering::SeqCst) } @@ -225,7 +228,7 @@ impl Taskable for RuntimeTranspilerStore { impl RuntimeTranspilerStore { pub fn init() -> RuntimeTranspilerStore { - // PORT NOTE: Zig passed `bun.typedAllocator(TranspilerJob)` to + // Note: Zig passed `bun.typedAllocator(TranspilerJob)` to // `Store.init`; the Rust HiveArrayFallback uses the global mimalloc // (PORTING.md §Allocators), so the allocator arg drops. Self::default() @@ -260,7 +263,7 @@ impl RuntimeTranspilerStore { } } - // PORT NOTE: takes `NonNull` rather than `&mut` for `event_loop`/`vm` + // Note: takes `NonNull` rather than `&mut` for `event_loop`/`vm` // because `&mut self` already aliases `vm.transpiler_store` (this `Self` is // a field of `VirtualMachine`). Field-level derefs only. pub fn run_from_js_thread( @@ -386,7 +389,7 @@ impl RuntimeTranspilerStore { // TranspilerJob // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: bun.heap_breakdown.enabled gate on inline capacity — the Rust +// Note: bun.heap_breakdown.enabled gate on inline capacity — the Rust // `bun_alloc::heap_breakdown` is a no-op outside macOS Instruments builds, so // the 64-slot hive is unconditional here. const TRANSPILER_JOB_HIVE_CAP: usize = 64; @@ -394,7 +397,7 @@ const TRANSPILER_JOB_HIVE_CAP: usize = 64; pub(crate) type TranspilerJobStore = HiveArrayFallback; pub struct TranspilerJob { - // PORT NOTE: stored as the lower-tier `bun_paths::fs::Path<'static>` (the type + // Note: stored as the lower-tier `bun_paths::fs::Path<'static>` (the type // `ParseOptions.path` / `bun_ast::Source.path` use). The slices borrow the // Box'd buffer allocated in `transpile()` and freed in `reset_for_pool()`. pub path: bun_paths::fs::Path<'static>, @@ -404,7 +407,7 @@ pub struct TranspilerJob { pub non_threadsafe_referrer: OwnedString, pub loader: Loader, pub promise: StrongOptional, - // PORT NOTE: struct is stored in a HiveArray and crosses to a worker thread; + // Note: struct is stored in a HiveArray and crosses to a worker thread; // Zig used `*VirtualMachine` / `*JSGlobalObject` (BACKREF — VM owns the // store and outlives every job). pub vm: *mut VirtualMachine, @@ -438,7 +441,7 @@ pub enum Fetcher { File, } -// PORT NOTE: Zig `Fetcher.deinit` called `.deref()` on the contained `bun.String`. +// Note: Zig `Fetcher.deinit` called `.deref()` on the contained `bun.String`. // In the Rust port `bun_core::String` is `Copy` with manual `.deref()`; matching // Zig, decrement explicitly when replacing the enum value. impl Fetcher { @@ -490,8 +493,8 @@ impl TranspilerJob { /// slot is recycled into the HiveArray via `store.put(this)`. Only caller is /// `run_from_js_thread`. /// - /// PORT NOTE: `HiveArrayFallback::put` runs `drop_in_place` on the slot (see - /// hive_array.rs PORT NOTE), so the Drop-carrying fields — `OwnedString` ×2, + /// Note: `HiveArrayFallback::put` runs `drop_in_place` on the slot (see + /// hive_array.rs note), so the Drop-carrying fields — `OwnedString` ×2, /// `OwnedResolvedSource`, `Log`, `StrongOptional` — are torn down *there*, /// not here. This function handles only the teardown that field drop glue /// does **not** cover: the leaked `path.text` Box, `poll_ref.disable()`, @@ -538,7 +541,7 @@ impl TranspilerJob { // before `reset_for_pool`/`put` need `&mut *self` below; deref at the // `fulfill` call site instead. let global_this = self.global_this; - // PORT NOTE: Zig `poll_ref.unref(vm)` — the Rust KeepAlive takes an `EventLoopCtx` + // Note: Zig `poll_ref.unref(vm)` — the Rust KeepAlive takes an `EventLoopCtx` // vtable; resolve it via the `get_vm_ctx` hook (registered by `bun_runtime::init`). self.poll_ref.unref(get_vm_ctx(AllocatorType::Js)); @@ -588,7 +591,7 @@ impl TranspilerJob { } pub(crate) fn schedule(&mut self) { - // PORT NOTE: Zig `poll_ref.ref(this.vm)` — the Rust KeepAlive takes an + // Note: Zig `poll_ref.ref(this.vm)` — the Rust KeepAlive takes an // `EventLoopCtx` vtable; resolve it via the `get_vm_ctx` hook (registered by // `bun_runtime::init`). self.poll_ref.ref_(get_vm_ctx(AllocatorType::Js)); @@ -633,7 +636,7 @@ impl TranspilerJob { } // SAFETY contract: `vm` outlives the job (BACKREF — VM owns the store). - // PORT NOTE: kept as a raw pointer — never form `&mut VirtualMachine` + // Note: kept as a raw pointer — never form `&mut VirtualMachine` // here. (a) the JS thread is concurrently live on the same VM, so a // `&mut` would be a data race; (b) `self` is stored *inside* // `(*vm).transpiler_store.store` (HiveArray inline slot), so a @@ -643,7 +646,7 @@ impl TranspilerJob { // expressions / leaf-field borrows) only. let vm: *mut VirtualMachine = self.vm; - // SAFETY: `vm` is the live owning VM (BACKREF — see PORT NOTE above); + // SAFETY: `vm` is the live owning VM (BACKREF — see note above); // atomic load on a leaf field, no `&VirtualMachine` formed. let store_generation = unsafe { (*vm) @@ -668,7 +671,7 @@ impl TranspilerJob { let loader = self.loader; let this_tag = self.resolved_source.get().tag; - // PORT NOTE: Zig threaded the arena into `output_code_allocator`; the Rust port of + // Note: Zig threaded the arena into `output_code_allocator`; the Rust port of // RuntimeTranspilerCache dropped the per-allocator fields (Box<[u8]> + global mimalloc). // LAYERING: this is the canonical `bun_ast::RuntimeTranspilerCache` // wired with the JSC vtable so the parser's `cache.get()` reaches the @@ -692,7 +695,7 @@ impl TranspilerJob { }, ); - // PORT NOTE: Zig copies the whole Transpiler by value (`transpiler = vm.transpiler`). + // Note: Zig copies the whole Transpiler by value (`transpiler = vm.transpiler`). // `Transpiler<'static>` is not `Clone` (it holds raw self-referential pointers); we do // a bytewise copy mirroring the Zig value-copy. SAFETY: `vm.transpiler` is read via // `addr_of!` (no `&VirtualMachine` formed); every internal raw pointer in the copy @@ -712,11 +715,11 @@ impl TranspilerJob { unsafe { &mut *(&raw mut *transpiler_storage).cast::>() }; transpiler.set_arena(&arena); transpiler.set_log(&raw mut log); - // PORT NOTE: reshaped for borrowck — Zig: transpiler.resolver.opts = transpiler.options + // Note: reshaped for borrowck — Zig: transpiler.resolver.opts = transpiler.options // (BundleOptions value copy). The Rust resolver already shares opts with the parent // Transpiler via raw pointer; set_arena/set_log keep them in sync. transpiler.macro_context = None; - // PORT NOTE: Zig's `MacroContext.init` is a value-type with no heap + // Note: Zig's `MacroContext.init` is a value-type with no heap // allocation, so re-creating it per-iteration (as `parse_maybe` does // when `macro_context.is_none()`) is free. The Rust port boxes a // higher-tier `MacroContext` via `__bun_macro_context_init`; that Box @@ -758,7 +761,7 @@ impl TranspilerJob { // (BACKREF — when non-null it points at the process-lifetime watcher // leaked in `enable_hot_module_reloading`, so the `ParentRef` invariant // holds for this transpile job's duration). Raw `(*vm)` field - // projection avoids forming `&VirtualMachine` per the `vm` PORT NOTE. + // projection avoids forming `&VirtualMachine` per the `vm` note. let import_watcher: Option> = unsafe { bun_ptr::ParentRef::from_nullable_mut((*vm).bun_watcher.cast()) }; if let Some(iw) = import_watcher { @@ -783,14 +786,14 @@ impl TranspilerJob { // this should be a cheap lookup because 24 bytes == 8 * 3 so it's read 3 machine words let is_node_override = strings::has_prefix_comptime(specifier, node_fallbacks::IMPORT_PATH); - // SAFETY: leaf scalar field reads on `*vm`; see `vm` PORT NOTE above. + // SAFETY: leaf scalar field reads on `*vm`; see `vm` note above. let macro_remappings = if unsafe { (*vm).macro_mode } || !unsafe { (*vm).has_any_macro_remappings } || is_node_override { MacroRemap::default() } else { - // PORT NOTE: `MacroRemap` (StringArrayHashMap of StringArrayHashMap) + // Note: `MacroRemap` (StringArrayHashMap of StringArrayHashMap) // has no nested `Clone` impl (the inherent `clone()` requires // `V: Clone`); the Zig copied it by value. Re-key shallowly here // matching the build-command conversion (transpiler.rs:2616). @@ -815,14 +818,14 @@ impl TranspilerJob { // If we're re-using the file descriptor from the fs watcher // Do not close it because that will break the kqueue-based watcher // - // PORT NOTE: stored in a `Cell` so the scopeguard closure can capture + // Note: stored in a `Cell` so the scopeguard closure can capture // `&Cell` and the post-parse writes are visible to it without // raw-pointer laundering (which the unused-assignment lint can't see). let should_close_input_file_fd = Cell::new(fd.is_none()); let mut input_file_fd: Fd = Fd::INVALID; - // SAFETY: leaf scalar field reads on `*vm`; see `vm` PORT NOTE above. + // SAFETY: leaf scalar field reads on `*vm`; see `vm` note above. let (vm_main, vm_main_hash) = unsafe { ((*vm).main(), (*vm).main_hash) }; let is_main = vm_main.len() == path.text.len() && vm_main_hash == hash @@ -855,7 +858,7 @@ impl TranspilerJob { dont_bundle_twice: true, allow_commonjs: true, inject_jest_globals: transpiler.options.rewrite_jest_for_tests, - // SAFETY: leaf-field `&` borrow on `*vm.debugger`; see `vm` PORT NOTE above. + // SAFETY: leaf-field `&` borrow on `*vm.debugger`; see `vm` note above. set_breakpoint_on_first_line: unsafe { &(*vm).debugger } .as_ref() .map(|d| d.set_breakpoint_on_first_line) @@ -870,7 +873,7 @@ impl TranspilerJob { } else { None }, - // SAFETY: leaf-field read on `*vm.module_loader`; see `vm` PORT NOTE above. + // SAFETY: leaf-field read on `*vm.module_loader`; see `vm` note above. remove_cjs_module_wrapper: is_main && unsafe { (*vm).module_loader.eval_source.is_some() }, module_type, @@ -913,7 +916,7 @@ impl TranspilerJob { } // Zig spec: `vm.isWatcherEnabled()` ⇔ `vm.bun_watcher != .none`. The - // Rust field is a type-erased `*mut ImportWatcher`, so a non-null + // Rust field is a nullable `*mut ImportWatcher`, so a non-null // pointer may still hold `ImportWatcher::None`; both must be ruled out // or we'd skip closing `input_file_fd` without a watcher to adopt it. // Discriminant read on the BACKREF captured above; only the JS thread @@ -972,7 +975,7 @@ impl TranspilerJob { } } - // SAFETY: leaf scalar field read; see `vm` PORT NOTE above. Inlined + // SAFETY: leaf scalar field read; see `vm` note above. Inlined // `VirtualMachine::use_isolation_source_provider_cache` to avoid forming // `&VirtualMachine`. let use_isolation_source_provider_cache = unsafe { (*vm).test_isolation_enabled } @@ -995,7 +998,7 @@ impl TranspilerJob { ); if bun_core::env::DUMP_SOURCE { - // SAFETY: `vm` is the live owning VM (BACKREF — see `vm` PORT NOTE above). + // SAFETY: `vm` is the live owning VM (BACKREF — see `vm` note above). let vm = unsafe { NonNull::new_unchecked(vm) }; dump_source_string(vm, specifier, entry.output_code.byte_slice()); } @@ -1085,7 +1088,7 @@ impl TranspilerJob { bp }); - // PORT NOTE: Zig copies BufferPrinter by value here (`var printer = source_code_printer.?.*`) + // Note: Zig copies BufferPrinter by value here (`var printer = source_code_printer.?.*`) // and writes it back later. We swap the buffer out instead and write it back via the // _writeback guard — same observable effect (the thread-local's buffer is reused). let mut printer = core::mem::replace( @@ -1129,7 +1132,7 @@ impl TranspilerJob { if let Some(mi) = module_info.as_deref_mut() { mi.flags.has_tla = !parse_result.ast.top_level_await_keyword.is_empty(); } - // PORT NOTE: derive `*mut` from a `&mut` borrow (not `&x as *const _ as + // Note: derive `*mut` from a `&mut` borrow (not `&x as *const _ as // *mut _`, which is Stacked-Borrows UB). The `&mut` borrow ends when the // closure returns; the raw pointer stays valid until `module_info` is // moved/touched again (after `print_with_source_map`). @@ -1137,7 +1140,7 @@ impl TranspilerJob { module_info.as_deref_mut().map(std::ptr::from_mut); let print_result = { - // SAFETY: see `vm` PORT NOTE above — `from_raw` stores `vm` as a raw + // SAFETY: see `vm` note above — `from_raw` stores `vm` as a raw // pointer and only borrows leaf fields (`source_mappings`, `debugger`) // inside `get()`. No `&mut VirtualMachine` is ever formed. let mut mapper = unsafe { SourceMapHandlerGetter::from_raw(vm, &raw mut printer) }; @@ -1174,7 +1177,7 @@ impl TranspilerJob { } if bun_core::env::DUMP_SOURCE { - // SAFETY: `vm` is the live owning VM (BACKREF — see `vm` PORT NOTE above). + // SAFETY: `vm` is the live owning VM (BACKREF — see `vm` note above). let vm = unsafe { NonNull::new_unchecked(vm) }; dump_source(vm, specifier, source_code_printer); } @@ -1188,7 +1191,7 @@ impl TranspilerJob { debug_assert!(cache.output_code.is_none()); let result = String::clone_latin1(written); - // SAFETY: leaf scalar field read on `*vm`; see `vm` PORT NOTE above. + // SAFETY: leaf scalar field read on `*vm`; see `vm` note above. if written.len() > 1024 * 1024 * 2 || unsafe { (*vm).smol } { // printer.ctx.buffer.deinit() → Drop let writer = BufferWriter::init(); diff --git a/src/jsc/SavedSourceMap.rs b/src/jsc/SavedSourceMap.rs index d7b32fc1d8e6..47365248b32c 100644 --- a/src/jsc/SavedSourceMap.rs +++ b/src/jsc/SavedSourceMap.rs @@ -4,7 +4,7 @@ use core::ffi::c_void; use core::ptr; use std::sync::Arc; -use bun_collections::{HashMap, TaggedPtrUnion}; +use bun_collections::{HashMap, IdentityContext, TaggedPtrUnion}; use bun_core::MutableString; use bun_core::Ordinal; use bun_sourcemap::internal_source_map::FindCache; @@ -41,7 +41,7 @@ impl Default for SavedSourceMap { } impl SavedSourceMap { - // TODO(port): in-place init — `this` is a pre-allocated field on VirtualMachine; `map` is a sibling field backref. + // In-place init — `this` is a pre-allocated field on VirtualMachine; `map` is a sibling field backref. pub unsafe fn init(this: &mut core::mem::MaybeUninit, map: *mut HashTable) { this.write(Self { map, @@ -168,7 +168,7 @@ impl SavedSourceMap { path: &[u8], ) { self.lock(); - // PORT NOTE: reshaped for borrowck — explicit unlock paired manually. + // Note: reshaped for borrowck — explicit unlock paired manually. // Zig `getEntry`/`removeByPtr` collapsed to `get`+`remove(&key)`; the std // backing has no key-slot pointer to hand out, and the key is a u64 hash // we already have in hand. @@ -204,7 +204,7 @@ impl SavedSourceMap { pub fn remove_zig_source_provider(&mut self, opaque_source_provider: *mut c_void, path: &[u8]) { self.lock(); - // PORT NOTE: reshaped for borrowck — explicit unlock paired manually. + // Note: reshaped for borrowck — explicit unlock paired manually. // Zig `getEntry`/`removeByPtr` collapsed to `get`+`remove(&key)`; the std // backing has no key-slot pointer to hand out, and the key is a u64 hash // we already have in hand. @@ -234,8 +234,10 @@ impl SavedSourceMap { } } -// TODO(port): std.HashMap(u64, *anyopaque, bun.IdentityContext(u64), 80) — needs identity (passthrough) hasher and 80% max load. -pub type HashTable = HashMap; +// Zig: std.HashMap(u64, *anyopaque, bun.IdentityContext(u64), 80). Keys are +// already wyhash u64s, so use the passthrough hasher; `bun_collections`' +// zig_hash_map uses an 80% max load factor. +pub type HashTable = HashMap>; impl bun_js_printer::OnSourceMapChunk for SavedSourceMap { fn on_source_map_chunk( @@ -280,7 +282,10 @@ impl Drop for SavedSourceMap { } self.map_mut().unlock_pointers(); - // TODO(port): deinit() on a backref-owned HashMap — ownership lives on VirtualMachine; verify. + // The HashTable storage is owned by the sibling `saved_source_map_table` + // field on VirtualMachine; `deinit()` resets it to an empty default in + // place, so the VM's later (or absent) drop of that field is a no-op — + // no double free. self.map_mut().deinit(); } } @@ -291,7 +296,6 @@ impl SavedSourceMap { source: &bun_ast::Source, mut mappings: MutableString, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set // --hot can re-read a file mid-rewrite (truncate + write) and transpile // a comment-only prefix into a 0-mapping map. Overwriting a real map // with that would make any still-unreported error from the previous @@ -309,12 +313,12 @@ impl SavedSourceMap { if contains { return Ok(()); } - // PORT NOTE: reshaped for borrowck — Zig held the lock across the early return; here we + // Note: reshaped for borrowck — Zig held the lock across the early return; here we // release before returning since no further table access follows. } } - // PORT NOTE: Zig `default_allocator.dupe(u8, mappings.list.items)` — + // Note: Zig `default_allocator.dupe(u8, mappings.list.items)` — // Zig dups because the printer's `MutableString` is backed by a // recycled buffer it does not own. In Rust every caller MOVES an owned // `Vec` here (printer chunk by value, cache hit via `mem::take`), @@ -342,9 +346,8 @@ impl SavedSourceMap { pub fn put_value(&mut self, path: &[u8], value: Value) -> Result<(), bun_core::Error> { use bun_collections::zig_hash_map::MapEntry as Entry; - // TODO(port): narrow error set self.lock(); - // PORT NOTE: reshaped for borrowck — explicit unlock paired manually. + // Note: reshaped for borrowck — explicit unlock paired manually. self.find_cache.invalidate_all(); self.last_ism = None; @@ -434,7 +437,7 @@ impl SavedSourceMap { // Do not lock the mutex while we're parsing JSON! // SAFETY: SourceProviderMap is kept alive by JSC; we did not hold a ref. if let Some(parse) = unsafe { (*ptr).get_source_map(path, Default::default(), hint) } { - // TODO(port): `.none` enum literal for second arg — verify SourceMap load-hint default. + // `SourceMapLoadHint::default()` is `None`, matching Zig's `.none`. if let Some(ref parsed_map) = parse.map { // The mutex is not locked. We have to check the hash table again. // Leak one strong ref into the table (mirrors Zig `map.ref()`). diff --git a/src/jsc/SourceProvider.rs b/src/jsc/SourceProvider.rs index 70c467ce7826..9ddc79a5e2e3 100644 --- a/src/jsc/SourceProvider.rs +++ b/src/jsc/SourceProvider.rs @@ -9,7 +9,6 @@ impl SourceProvider { } } -// TODO(port): move to jsc_sys unsafe extern "C" { // safe: `SourceProvider` is an opaque `UnsafeCell`-backed ZST handle; `&mut` is // ABI-identical to a non-null pointer and C++ refcount mutation is interior. diff --git a/src/jsc/StringBuilder.rs b/src/jsc/StringBuilder.rs index f774e59880a1..461af325b1c4 100644 --- a/src/jsc/StringBuilder.rs +++ b/src/jsc/StringBuilder.rs @@ -25,7 +25,7 @@ impl StringBuilder { } } - // PORT NOTE: Zig's `append(comptime append_type: Append, value: append_type.Type())` + // Note: Zig's `append(comptime append_type: Append, value: append_type.Type())` // dispatches on a comptime enum to pick the value's *type*. Rust const // generics cannot vary a parameter's type by enum value, and a trait would // collide (e.g. `String` is used for both `.string` and `.quoted_json_string`). @@ -71,7 +71,7 @@ impl StringBuilder { } pub fn to_string(&mut self, global: &JSGlobalObject) -> JsResult { - // PORT NOTE: Zig wraps this in a TopExceptionScope. `from_js_host_call` + // Note: Zig wraps this in a TopExceptionScope. `from_js_host_call` // is the equivalent shape (call FFI → check pending exception); using it // here avoids the in-place-init / pinning dance TopExceptionScope needs. crate::from_js_host_call(global, || StringBuilder__toString(self, global)) @@ -88,8 +88,6 @@ impl Drop for StringBuilder { } } -// TODO(port): move to jsc_sys -// // `StringBuilder` is `#[repr(C, align(8))]` with a single `[u8; SIZE]` field, // so `&mut StringBuilder` is ABI-identical to a non-null aligned `void*` to the // inline `WTF::StringBuilder` storage. The shims that take only that handle diff --git a/src/jsc/Strong.rs b/src/jsc/Strong.rs index 1bf04d7255c0..76639355d46b 100644 --- a/src/jsc/Strong.rs +++ b/src/jsc/Strong.rs @@ -6,7 +6,7 @@ use core::ptr::NonNull; use crate::{JSGlobalObject, JSValue}; -// PORT NOTE: field renamed from `impl` (Rust keyword) to `handle`. +// Note: field renamed from `impl` (Rust keyword) to `handle`. pub struct Strong { handle: NonNull, // NonNull is already !Send + !Sync, matching the requirement that @@ -64,7 +64,7 @@ impl Drop for Strong { /// Holds a strong reference to a JS value, protecting it from garbage /// collection. When not holding a value, the strong may still be allocated. -// PORT NOTE: field renamed from `impl` (Rust keyword) to `handle`. +// Note: field renamed from `impl` (Rust keyword) to `handle`. // `#[repr(transparent)]` matches the Zig layout (`?*Impl` — single nullable // pointer) so it stays FFI-safe when embedded in `extern "C"` structs. #[repr(transparent)] @@ -109,7 +109,7 @@ impl Optional { let Some(function) = self.try_swap() else { return JSValue::ZERO; }; - // PORT NOTE: Zig source (Strong.zig:71) calls `function.call(global, args)` + // Note: Zig source (Strong.zig:71) calls `function.call(global, args)` // which predates the `thisValue` param on JSValue.call; pass `.undefined`. function .call(global, JSValue::UNDEFINED, args) @@ -202,11 +202,11 @@ impl Impl { pub fn get(this: NonNull) -> JSValue { // `this` is actually a pointer to a `JSC::JSValue`; see Strong.cpp. - // SAFETY: HandleSlot storage is a live, aligned JSC::JSValue (encoded i64) for the - // lifetime of the Impl handle. JSValue stub is `#[repr(transparent)] usize` (same - // size); reading it directly is the encode() operation. - // TODO(port): once DecodedJSValue.rs un-gates, switch back to `(*js_value).encode()`. - unsafe { *this.as_ptr().cast::() } + // Zig: `const js_value: *jsc.DecodedJSValue = @ptrCast(this); return js_value.encode();` + // SAFETY: HandleSlot storage is a live, aligned JSC::JSValue for the + // lifetime of the Impl handle; `DecodedJSValue` is its `#[repr(C)]` + // ABI-compatible mirror. + unsafe { (*this.as_ptr().cast::()).encode() } } pub fn set(this: NonNull, global: &JSGlobalObject, value: JSValue) { @@ -246,8 +246,6 @@ impl Impl { } } -// TODO(port): move to jsc_sys -// // `Impl` and `JSGlobalObject` are opaque `UnsafeCell`-backed ZST handles, so // `&Impl`/`&JSGlobalObject` are ABI-identical to non-null `*const T` and C++ // mutating through them (HandleSet slot write) is interior mutation invisible diff --git a/src/jsc/SystemError.rs b/src/jsc/SystemError.rs index f8d46cd1b0e4..ad77e4a4715a 100644 --- a/src/jsc/SystemError.rs +++ b/src/jsc/SystemError.rs @@ -60,7 +60,6 @@ impl From for SystemError { /// `core::result::Result` alias in Phase F so callers get `?` for free. pub type Maybe = core::result::Result; -// TODO(port): move to jsc_sys // SAFETY (safe fn): `SystemError` is `#[repr(C)]` and read-only on the C++ side; // `JSGlobalObject` is an opaque `UnsafeCell`-backed handle, so `&JSGlobalObject` // is ABI-identical to a non-null `JSGlobalObject*` with write provenance. @@ -184,7 +183,7 @@ pub fn verify_error_to_js( impl fmt::Display for SystemError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // PORT NOTE: bun.Output.prettyFmt is a comptime color-tag → ANSI transformer that + // Note: bun.Output.prettyFmt is a comptime color-tag → ANSI transformer that // takes (fmt_str, comptime enable_colors) and returns a comptime-expanded format // string; `bun_core::pretty_fmt!` is the Rust equivalent. The runtime bool → comptime // dispatch (`switch (b) { inline else => |c| ... }`) is preserved as an if/else. diff --git a/src/jsc/Task.rs b/src/jsc/Task.rs index ba49ca096fde..cb8a2180bab9 100644 --- a/src/jsc/Task.rs +++ b/src/jsc/Task.rs @@ -52,10 +52,14 @@ pub fn report_error_or_terminate( if ex.is_termination_exception() { return Err(JsTerminated::JSTerminated); } - // TODO(port): `global.report_uncaught_exception(ex.as_exception(vm))` — - // `JSValue::as_exception` / `JSGlobalObject::report_uncaught_exception` - // surface lands when JSGlobalObject.rs un-gates. - let _ = (global, ex); + // Zig: `_ = global.reportUncaughtException(ex)` (Task.zig reportErrorOrTerminate). + let vm = std::ptr::from_ref::(global.vm()).cast_mut(); + let exc = ex + .as_exception(vm) + .expect("exception value must be an Exception cell"); + // `as_exception` returned a non-null cell pointer rooted on the VM; + // `Exception` is an opaque ZST handle — safe deref (panics on null). + let _ = crate::js_global_object::report_uncaught_exception(global, crate::Exception::opaque_ref(exc)); Ok(()) } diff --git a/src/jsc/TextCodec.rs b/src/jsc/TextCodec.rs index 1ef0066660af..2e60f067a5d6 100644 --- a/src/jsc/TextCodec.rs +++ b/src/jsc/TextCodec.rs @@ -3,7 +3,6 @@ use core::ptr::NonNull; use crate::mark_binding; use bun_core::String as BunString; -// TODO(port): move to _sys unsafe extern "C" { fn Bun__createTextCodec( encoding_name: *const u8, @@ -46,7 +45,7 @@ impl TextCodec { unsafe { Bun__createTextCodec(encoding.as_ptr(), encoding.len()) } } - // PORT NOTE: FFI-owned opaque; constructed/destroyed across FFI, so explicit + // FFI-owned opaque; constructed/destroyed across FFI, so explicit // destroy instead of `impl Drop` (cannot own a `TextCodec` by value). pub unsafe fn destroy(this: *mut TextCodec) { mark_binding!(); diff --git a/src/jsc/URL.rs b/src/jsc/URL.rs index 91cde9561fe0..b6ddd74627d4 100644 --- a/src/jsc/URL.rs +++ b/src/jsc/URL.rs @@ -3,18 +3,11 @@ use core::ptr::NonNull; use bun_core::String; use bun_jsc::{JSGlobalObject, JSValue, JsResult}; -// PORT NOTE: `jsc.markBinding(@src())` calls were dropped — debug-only binding-trace -// helper with no Rust equivalent; a `mark_binding!()` macro could be added if wanted. - bun_opaque::opaque_ffi! { /// Opaque handle to a WebKit `WTF::URL` allocated on the C++ side. pub struct URL; } -// TODO(port): move to jsc_sys -// PORT NOTE: getters take `*const URL` — the C++ side (BunString.cpp) never mutates the -// WTF::URL on read. `JSGlobalObject` is an opaque FFI handle whose state Rust never -// observes directly, so it is passed `*const` per the JSGlobalObject.rs convention. // Getters take `&URL` (non-null `*const URL` at the C ABI; BunString.cpp never // mutates the WTF::URL on read). `&mut String` for the in/out params is // ABI-identical to non-null `*mut String`. `URL__deinit` consumes the C++ @@ -95,8 +88,8 @@ impl URL { let mut input = str; NonNull::new(URL__fromString(&mut input)) } - // TODO(port): from_js/from_string/from_utf8 return an owned C++ heap pointer that - // the caller must destroy(). Consider an RAII wrapper instead of NonNull. + // from_js/from_string/from_utf8 return an owned C++ heap pointer that the + // caller must destroy(). pub fn protocol(&self) -> String { URL__protocol(self) @@ -148,7 +141,7 @@ impl URL { URL__port(self) } - // PORT NOTE: kept as explicit destroy (not Drop) — URL is an opaque #[repr(C)] FFI + // Kept as explicit destroy (not Drop) — URL is an opaque #[repr(C)] FFI // handle constructed/destroyed across the C++ boundary. pub unsafe fn destroy(this: *mut Self) { // SAFETY: `this` is a valid *URL from C++; freed exactly once diff --git a/src/jsc/URLSearchParams.rs b/src/jsc/URLSearchParams.rs index d6b15d361a2d..2fe1f8fe0923 100644 --- a/src/jsc/URLSearchParams.rs +++ b/src/jsc/URLSearchParams.rs @@ -9,7 +9,6 @@ bun_opaque::opaque_ffi! { pub struct URLSearchParams; } -// TODO(port): move to jsc_sys unsafe extern "C" { safe fn URLSearchParams__create(global_object: &JSGlobalObject, init: &ZigString) -> JSValue; safe fn URLSearchParams__fromJS(value: JSValue) -> Option>; @@ -28,13 +27,14 @@ impl URLSearchParams { URLSearchParams__create(global_object, &init) } - // TODO(port): lifetime — opaque handle is owned by the JS GC heap, not by `value`. + // The returned opaque handle is owned by the JS GC heap, not by `value`; + // callers must keep the JS object alive while using it. pub fn from_js(value: JSValue) -> Option> { URLSearchParams__fromJS(value) } pub fn to_string(&mut self, ctx: &mut Ctx, callback: fn(ctx: &mut Ctx, str: ZigString)) { - // PORT NOTE: reshaped — Zig captured `callback` at comptime so the C trampoline + // Reshaped from Zig — Zig captured `callback` at comptime so the C trampoline // only needed `ctx` through the void*. Rust cannot take a fn pointer as a const // generic, so pack (ctx, callback) on the stack and pass that instead. struct Wrap<'a, Ctx> { diff --git a/src/jsc/VM.rs b/src/jsc/VM.rs index ab94568a0d9b..291453ffe3d5 100644 --- a/src/jsc/VM.rs +++ b/src/jsc/VM.rs @@ -2,8 +2,6 @@ use core::ffi::c_void; use crate::{Exception, JSGlobalObject, JSValue, JsError}; -// TODO(port): move to _sys -// // All JSC__VM__* shims take only a `JSC::VM*` (and at most a // `JSGlobalObject*` / `JSC::Exception*` / scalar). `VM` and `JSGlobalObject` // are opaque `UnsafeCell`-backed ZST handles, so `&VM` is ABI-identical to a @@ -64,11 +62,11 @@ pub enum HeapType { } impl VM { - // PORT NOTE: `JSC__VM__create` was removed from bindings.cpp (Bun creates + // Note: `JSC__VM__create` was removed from bindings.cpp (Bun creates // its VM via `Zig::GlobalObject::create` → `WebWorker__createVM` instead). // The Zig `VM.create` wrapper is dead code; do not port it. - // PORT NOTE: not `impl Drop` — takes a `global_object` param and `VM` is an opaque FFI handle. + // Note: not `impl Drop` — takes a `global_object` param and `VM` is an opaque FFI handle. pub fn deinit(&self, global_object: &JSGlobalObject) { JSC__VM__deinit(self, global_object) } @@ -97,7 +95,7 @@ impl VM { Lock { vm: self } } - // PORT NOTE: `JSC__VM__deferGC` was removed from bindings.cpp in the + // Note: `JSC__VM__deferGC` was removed from bindings.cpp in the // WebKit-bump that introduced `JSC::DeferGC` RAII; the Zig `deferGC` // wrapper is dead code. Callers should use `holdAPILock`/`DeferGC` on the // C++ side instead. diff --git a/src/jsc/VirtualMachine.rs b/src/jsc/VirtualMachine.rs index 6dfa9baa7086..58252faa5c96 100644 --- a/src/jsc/VirtualMachine.rs +++ b/src/jsc/VirtualMachine.rs @@ -142,15 +142,17 @@ pub struct VirtualMachine { pub global: *mut JSGlobalObject, // allocator dropped per §Allocators (global mimalloc) pub has_loaded_constructors: bool, - // TODO(port): lifetime — `Transpiler<'a>` borrows `log`/`allocator`; VM is + // LIFETIME-ERASED: `Transpiler<'a>` borrows `log`/`allocator`; VM is // self-referential and cannot carry `<'a>`, so we erase to `'static` and the // owner guarantees the borrowed `log` outlives the VM (see `init`). pub transpiler: Transpiler<'static>, - // TODO(b2-cycle): `bun_watcher` is `ImportWatcher` from hot_reloader.rs (gated sibling). - pub bun_watcher: *mut c_void, + /// Hot-reload import watcher (heap `Box`, installed by + /// [`crate::hot_reloader::HotReloaderCtx::install_bun_watcher`]); null when + /// hot reload is disabled. Read via [`Self::bun_watcher_ptr`]. + pub bun_watcher: *mut crate::hot_reloader::ImportWatcher, pub console: *mut crate::console_object::ConsoleObject, - // TODO(port): lifetime — LIFETIMES.tsv says BORROW_PARAM (`&'a mut bun_ast::Log`); - // raw NonNull used because VM is self-referential and cannot carry `<'a>`. + // BORROW_PARAM (`&'a mut bun_ast::Log` per LIFETIMES.tsv) — raw NonNull + // used because VM is self-referential and cannot carry `<'a>`. pub log: Option>, /// Path of the entry module. BACKREF — borrows process-argv, the resolver's /// process-lifetime `dirname_store`/`filename_store`, the standalone module @@ -166,7 +168,9 @@ pub struct VirtualMachine { pub overridden_main: crate::strong::Optional, pub entry_point: bun_bundler::entry_points::ServerEntryPoint, pub origin: bun_url::URL<'static>, - // TODO(b2-cycle): `node_fs` is `Option>`. + // LAYERING: real type is `Option>`, but + // `bun_runtime` is a forward dep of this crate; stored type-erased and + // cast back by the `bun_runtime` consumers. pub node_fs: Option<*mut c_void>, /// Opaque per-VM `bun_runtime` state (boxed `timer::All` + /// `Body::Value::HiveAllocator` + …). Set by @@ -174,30 +178,35 @@ pub struct VirtualMachine { /// `RuntimeHooks::deinit_runtime_state` in [`destroy`]. Null when no high /// tier is installed (e.g. `bun_jsc` unit tests). /// - /// PORT NOTE: the Zig `timer: api.Timer.All` and + /// Note: the Zig `timer: api.Timer.All` and /// `body_value_pool: webcore.Body.Value.HiveAllocator` value /// fields live inside this box rather than as `()` shadows here — both /// types are owned by `bun_runtime` (forward dep). Access goes through /// [`RuntimeHooks::timer_insert`] / [`RuntimeHooks::body_value_hive_ref`]. pub runtime_state: *mut c_void, pub event_loop_handle: Option<*mut PlatformEventLoop>, - pub pending_unref_counter: i32, + /// Pending `unref` count drained by the event-loop thread. Atomic because + /// `KeepAlive::unref_on_next_tick_concurrently` increments it from OTHER + /// threads (Zig: `@atomicRmw(.Add, .monotonic)` on `pending_unref_counter`). + pub pending_unref_counter: core::sync::atomic::AtomicI32, pub preload: Vec>, pub unhandled_pending_rejection_to_capture: Option<*mut JSValue>, - // PORT NOTE: layering — the concrete `bun_standalone_graph::Graph` lives + // Note: layering — the concrete `bun_standalone_graph::Graph` lives // in a higher-tier crate. The resolver already broke that cycle with the // `bun_resolver::StandaloneModuleGraph` trait; we hold the same trait // object here so `init_with_module_graph` can hand it straight to // `transpiler.resolver.standalone_module_graph` without a downcast. pub standalone_module_graph: Option<&'static dyn bun_resolver::StandaloneModuleGraph>, pub smol: bool, - // TODO(b2-cycle): `dns_result_order` is `bun_runtime::api::dns::Resolver::Order`. + // LAYERING: real type is `bun_runtime::api::dns::Resolver::Order` (forward + // dep); stored as its `u8` repr. pub dns_result_order: u8, pub cpu_profiler_config: Option, pub heap_profiler_config: Option, pub counters: Counters, - // TODO(b2-cycle): `hot_reload` is `bun_runtime::cli::Command::HotReload`. + // LAYERING: real type is `bun_runtime::cli::Command::HotReload` (forward + // dep); stored as its `u8` repr (see `HOT_RELOAD_*` constants). pub hot_reload: u8, pub jsc_vm: *mut VM, @@ -217,7 +226,8 @@ pub struct VirtualMachine { pub exit_handler: ExitHandler, pub default_tls_reject_unauthorized: Option, - // TODO(b2-cycle): `default_verbose_fetch` is `Option`. + // LAYERING: real type is `Option` (forward dep); + // stored as the enum's `u8` repr. pub default_verbose_fetch: Option, /// Do not access this field directly! It exists in the VirtualMachine struct so @@ -226,7 +236,8 @@ pub struct VirtualMachine { pub saved_source_map_table: crate::saved_source_map::HashTable, pub source_mappings: SavedSourceMap, - // TODO(port): lifetime — `&'a mut Arena`; caller-owned (web_worker). + // BACKREF — `&'a mut Arena` in spirit; caller-owned (web_worker) and + // outlives the VM. pub arena: Option>, pub has_loaded: bool, @@ -235,7 +246,8 @@ pub struct VirtualMachine { pub had_errors: bool, pub macros: MacroMap, - // TODO(b2-cycle): `MacroEntryPoint` from `bun_bundler::entry_points` (gated). + // LAYERING: values are `MacroEntryPoint` from `bun_bundler::entry_points` + // (forward dep); stored type-erased and cast back by the consumers. pub macro_entry_points: bun_collections::ArrayHashMap, pub macro_mode: bool, /// Depth of live [`MacroModeGuard`]s on this thread. Nonzero exactly while @@ -265,7 +277,7 @@ pub struct VirtualMachine { pub argv: Vec>, - pub origin_timer: std::time::Instant, // TODO(port): std.time.Timer + pub origin_timer: std::time::Instant, // Zig: `std.time.Timer` (monotonic elapsed) pub origin_timestamp: u64, /// For fake timers: override performance.now() with a specific value (in nanoseconds). pub overridden_performance_now: Option, @@ -297,7 +309,6 @@ pub struct VirtualMachine { pub is_handling_uncaught_exception: bool, pub exit_on_uncaught_exception: bool, - // TODO(b2): `modules` is `ModuleLoader::AsyncModule::Queue` (AsyncModule.rs gated). pub modules: crate::async_module::Queue, pub aggressive_garbage_collection: GCLevel, @@ -315,7 +326,7 @@ pub struct VirtualMachine { #[cfg(debug_assertions)] pub debug_thread_id: std::thread::ThreadId, - // PORT NOTE: Zig `if (Environment.isDebug) std.Thread.Id else void` — the + // Note: Zig `if (Environment.isDebug) std.Thread.Id else void` — the // release-build ZST is intentional spec parity, not a placeholder. #[cfg(not(debug_assertions))] pub debug_thread_id: (), @@ -348,8 +359,6 @@ pub struct VirtualMachine { // FFI declarations // ────────────────────────────────────────────────────────────────────────── -// TODO(port): move to jsc_sys -// // `JSGlobalObject` is an opaque `UnsafeCell`-backed ZST handle, so // `&JSGlobalObject` is ABI-identical to a non-null `JSGlobalObject*` and C++ // mutating VM/process state through it is interior mutation invisible to Rust. @@ -407,7 +416,7 @@ impl UnhandledRejectionScope { /// crate-level `VirtualMachine::get()`/`set_current()` accessors. pub(crate) struct VMHolder; -// PORT NOTE: Zig nests `pub var main_thread_vm` inside the struct namespace; +// Note: Zig nests `pub var main_thread_vm` inside the struct namespace; // Rust forbids associated `static`s, so it lives at module scope and is // re-exported as `VMHolder::MAIN_THREAD_VM` via a const fn accessor. pub(crate) static MAIN_THREAD_VM: core::sync::atomic::AtomicPtr = @@ -499,7 +508,7 @@ impl ExitHandler { vm.exit_handler.exit_code = code; } - /// PORT NOTE: spec calls `this.exit_handler.dispatchOnExit()` from a + /// Note: spec calls `this.exit_handler.dispatchOnExit()` from a /// `*VirtualMachine`. Taking `&mut self: ExitHandler` and recovering the /// parent via `container_of` would escape the provenance of `&mut self` /// (which only covers the `ExitHandler` field). Callers pass the VM @@ -792,7 +801,7 @@ impl VirtualMachine { } /// Raw accessor for the hot-reload import watcher. `bun_watcher` is the - /// type-erased `*mut ImportWatcher` installed by + /// `*mut ImportWatcher` installed by /// [`crate::hot_reloader::HotReloaderCtx::install_bun_watcher`] (separate /// `Box` heap allocation), or null when hot reload is disabled. /// @@ -807,7 +816,6 @@ impl VirtualMachine { #[inline] pub fn bun_watcher_ptr(&self) -> *mut crate::hot_reloader::ImportWatcher { self.bun_watcher - .cast::() } /// `event_loop().enter()` now, `.exit()` on drop. Safe wrapper over @@ -902,17 +910,13 @@ impl VirtualMachine { self.event_loop_handle.map(|h| unsafe { &mut *h }) } - /// Read-then-zero `pending_unref_counter`. Wraps the common - /// `let n = vm.pending_unref_counter; if n > 0 { vm.pending_unref_counter = 0; ... }` - /// pattern so callers don't open-code two raw-ptr writes. + /// Read-then-zero `pending_unref_counter`. `swap(0)` so a concurrent + /// `increment_pending_unref_counter()` from another thread can't be lost + /// between the read and the reset. #[inline] pub fn take_pending_unref(&self) -> i32 { - let this = self.as_mut(); - let n = this.pending_unref_counter; - if n > 0 { - this.pending_unref_counter = 0; - } - n + self.pending_unref_counter + .swap(0, core::sync::atomic::Ordering::Relaxed) } /// Safe shared accessor for the per-VM `bun_ast::Log`. The log is @@ -951,13 +955,13 @@ impl VirtualMachine { /// Returns a small adaptor whose `get()` produces the erased /// `js_printer::SourceMapHandler` for `print_with_source_map`. /// - /// PORT NOTE: takes `*mut BufferPrinter` (raw), not `&'a mut`, because the + /// Note: takes `*mut BufferPrinter` (raw), not `&'a mut`, because the /// SAME `BufferPrinter` is also passed as the live `writer` to /// `print_with_source_map`. Creating an `&'a mut` here would alias with /// that writer borrow for the entire print; instead we stash the raw /// pointer and only reborrow inside `on_source_map_chunk` once the /// writer's last use (`print_slice`) has retired. See jsc_hooks.rs - /// `print_with_source_map` call-site PORT NOTE. + /// `print_with_source_map` call-site Note. #[inline] pub fn source_map_handler<'a>( &'a mut self, @@ -1114,7 +1118,7 @@ impl VirtualMachine { _: &JSGlobalObject, value: JSValue, ) { - // SAFETY: BORROW_PARAM ptr set by caller, outlives this call (TODO(port): lifetime) + // SAFETY: BORROW_PARAM ptr set by caller; outlives this call. let list = this .on_unhandled_rejection_exception_list .map(|mut p| unsafe { p.as_mut() }); @@ -1232,7 +1236,7 @@ impl VirtualMachine { { use core::mem::{ManuallyDrop, MaybeUninit}; - // PORT NOTE: Zig's `OpaqueWrap(Context, function)` synthesizes a + // Note: Zig's `OpaqueWrap(Context, function)` synthesizes a // `fn(*anyopaque) void` that casts back and calls the body. The Rust // closure carries its own context, so the trampoline state is just // `{ closure, out-slot }`. `ManuallyDrop` lets us move the `FnOnce` @@ -1311,7 +1315,7 @@ impl VirtualMachine { let _ = writer.flush(); } - // PORT NOTE: Zig `defer this.had_errors = prev_had_errors;` — the hook + // Note: Zig `defer this.had_errors = prev_had_errors;` — the hook // does not unwind across the dispatch boundary, so restore linearly. self.had_errors = prev_had_errors; } @@ -1353,7 +1357,7 @@ impl VirtualMachine { } }; - // PORT NOTE: Zig used a `MacroEntryPointLoader` struct + `OpaqueWrap` + // Note: Zig used a `MacroEntryPointLoader` struct + `OpaqueWrap` // because `runWithAPILock` only accepts `fn(*Ctx) void`. The Rust // `run_with_api_lock` already takes a closure, so the loader struct // collapses into a captured local. @@ -1443,7 +1447,7 @@ impl VirtualMachine { self.exit_handler.exit_code = 1; (self.on_unhandled_rejection)(self, global_object, err); } - // PORT NOTE: Zig `defer this.is_handling_uncaught_exception = false;` + // Note: Zig `defer this.is_handling_uncaught_exception = false;` // (VirtualMachine.zig:707) covers BOTH the FFI call and the // `onUnhandledRejection` callback above. The flag must stay raised // while that callback runs so a re-entrant `uncaught_exception` from @@ -1460,11 +1464,8 @@ impl VirtualMachine { if self.hot_reload != HOT_RELOAD_HOT { return None; } - // TODO(b2-cycle): spec lazy-inits via `RareData::hotMap(allocator)`; - // that accessor is gated in `rare_data.rs::_accessor_body`. Until it - // un-gates, return whatever the field already holds (callers that need - // the lazy-init path are themselves gated on `bun_runtime`). - self.rare_data.as_deref_mut()?.hot_map.as_mut() + // Zig: `this.rareData().hotMap(this.allocator)` — lazy-init. + Some(self.rare_data().hot_map()) } pub fn on_before_exit(&mut self) { @@ -1516,7 +1517,7 @@ impl VirtualMachine { // Make sure we run new cleanup hooks introduced by running cleanup // hooks. - // PORT NOTE: each iteration re-fetches `rare_data` so the FFI hook + // Note: each iteration re-fetches `rare_data` so the FFI hook // bodies (which may re-enter `VirtualMachine` and push more hooks) do // not run while a `&mut RareData` is live — the borrow ends after // `mem::take` returns the owned `Vec`. @@ -1586,7 +1587,7 @@ impl VirtualMachine { // alive (closeAll() fires on_close → JS). After JSC teardown, // RareData's Drop only deinit()s the groups (asserts empty). if self.rare_data.is_some() { - // PORT NOTE: reshaped for borrowck — `close_all_socket_groups` + // Note: reshaped for borrowck — `close_all_socket_groups` // walks the loop's group list via `vm.uws_loop()` and never // touches `vm.rare_data`, so the disjoint reborrow is sound. // SAFETY: `self` is the live per-thread VM; the shared borrow @@ -1855,7 +1856,7 @@ pub struct RuntimeHooks { /// half of `bun_io`'s cycle-break manual vtable. Every slot is implementable /// from in-crate data (spec posix_event_loop.zig:100-104 / RareData.zig:441), /// so this is the single fully-populated instance; `aio::get_vm_ctx(.Js)` and -/// the websocket-client adapters resolve to it. PORT NOTE: in Zig the +/// the websocket-client adapters resolve to it. Note: in Zig the /// `KeepAlive::ref(anytype)` accepted `*VirtualMachine` directly. /// Recover `&mut VirtualMachine` from the erased vtable `owner`. Private to /// this module — every caller is an `unsafe fn` slot in @@ -1876,10 +1877,13 @@ bun_io::link_impl_EventLoopCtx! { }, // CROSS-THREAD: reached via `KeepAlive::unref_on_next_tick_concurrently`. // Do NOT route through `vm_from_owner()` — that mints `&mut VM`, which - // would alias the JS thread's `&mut`. Raw place RMW only (the - // field-level non-atomic race is pre-existing; TODO: make - // `pending_unref_counter` an `AtomicI32` with `fetch_add`). - increment_pending_unref_counter() => (*this).pending_unref_counter += 1, + // would alias the JS thread's `&mut`. Atomic RMW through a shared ref + // (Zig: `@atomicRmw(.Add, .monotonic)`). + increment_pending_unref_counter() => { + (*this) + .pending_unref_counter + .fetch_add(1, core::sync::atomic::Ordering::Relaxed); + }, // CROSS-THREAD: reached via `KeepAlive::{,un}ref_concurrently`. Do NOT // use `vm_from_owner()` / `event_loop_mut()` — both mint `&mut`, UB // against the JS thread's borrow. `event_loop()` takes `&self` (Sync) @@ -1970,7 +1974,6 @@ pub fn runtime_hooks() -> Option<&'static RuntimeHooks> { Some(&__BUN_RUNTIME_HOOKS) } -// TODO(port): move to jsc_sys #[allow(improper_ctypes)] // VirtualMachine is opaque to C++; passed as `void*` unsafe extern "C" { // Spec JSGlobalObject.zig:863 / headers.h:435 — note the real symbol is @@ -2020,7 +2023,7 @@ fn get_origin_timestamp() -> u64 { impl VirtualMachine { /// `VirtualMachine.init(opts)` — allocate + wire the per-thread VM. /// - /// PORT NOTE: every step that names a `bun_runtime` / `bun_webcore` type + /// Note: every step that names a `bun_runtime` / `bun_webcore` type /// (`Timer.All.init`, `Body.Value.HiveAllocator`, `configureDebugger`, /// `Config.configureTransformOptionsForBunVM`, `ParentDeathWatchdog`) is /// dispatched through `RuntimeHooks::init_runtime_state` so `bun_jsc` does @@ -2040,7 +2043,7 @@ impl VirtualMachine { // allocation lives for the thread lifetime (never freed on the main // thread; worker `destroy()` frees it explicitly). // - // PORT NOTE (validity): the zeroed bytes are NOT a valid + // Note (validity): the zeroed bytes are NOT a valid // `VirtualMachine` — `origin_timer: Instant`, `on_unhandled_rejection: // fn(...)`, (debug) `debug_thread_id: ThreadId`, every `Vec`/`Box`/ // `HashMap`/`ArrayHashMap` field (NonNull dangling-when-empty), `URL` @@ -2172,7 +2175,7 @@ impl VirtualMachine { } // High-tier per-VM state — Transpiler / Timer::All / entry_point. - // PORT NOTE (init order): spec VirtualMachine.zig:1241/1259 builds + // Note (init order): spec VirtualMachine.zig:1241/1259 builds // `Transpiler.init` and `.timer = bun.api.Timer.All.init()` as part of // the struct initializer BEFORE `JSGlobalObject.create`. The C++ body // of `Zig__GlobalObject__create` re-enters via `WTFTimer__create`/ @@ -2397,7 +2400,7 @@ impl VirtualMachine { } } - // PORT NOTE: reshaped for borrowck — capture raw ptr before &self call. + // Note: reshaped for borrowck — capture raw ptr before &self call. let global = self.global; let global_ref = self.global(); let promise = if !self.main_is_html_entrypoint { @@ -2553,7 +2556,7 @@ pub fn process_fetch_log( } 1 => { - // PORT NOTE: Zig copied `log.msgs.items[0]` by value; `Msg` is not + // Note: Zig copied `log.msgs.items[0]` by value; `Msg` is not // `Copy` here, so move it out — the caller `defer log.deinit()`s // immediately after, so consuming the vec is sound. let msg = log.msgs.swap_remove(0); @@ -2615,7 +2618,7 @@ pub fn process_fetch_log( /// `source_mappings` (fast path) or `self` (debugger / inline-sourcemap path) /// without the two `&mut` borrows colliding. /// -/// PORT NOTE: Zig stored `*VirtualMachine` / `*BufferPrinter` directly. Here +/// Note: Zig stored `*VirtualMachine` / `*BufferPrinter` directly. Here /// we keep raw pointers + a `PhantomData<&'a mut ()>` so the getter's lifetime /// is tied to the `&'a mut VirtualMachine` it was built from in /// `source_map_handler`, but `get()` can still hand out a @@ -2806,19 +2809,21 @@ impl<'a> bun_js_printer::OnSourceMapChunk for SourceMapHandlerGetter<'a> { pub struct Options { pub args: bun_options_types::schema::api::TransformOptions, pub log: Option>, - // TODO(port): lifetime — `&'a mut bun_dot_env::Loader`. + // BORROW_PARAM (`&'a mut bun_dotenv::Loader`) — caller-owned; the loader + // outlives the VM, so the inner lifetime is erased to `'static`. pub env_loader: Option>>, pub store_fd: bool, pub smol: bool, - // TODO(b2-cycle): real type is `bun_runtime::api::dns::Resolver::Order`. + // LAYERING: real type is `bun_runtime::api::dns::Resolver::Order` (forward + // dep); stored as its `u8` repr. pub dns_result_order: u8, /// `--print` needs the result from evaluating the main module. pub eval: bool, - // PORT NOTE: layering — concrete `bun_standalone_graph::Graph` is in a + // Note: layering — concrete `bun_standalone_graph::Graph` is in a // forward-dep crate; callers pass it as the resolver's trait object so // both VM and resolver can hold it without the cycle. pub graph: Option<&'static dyn bun_resolver::StandaloneModuleGraph>, - // PORT NOTE: Zig `debugger: bun.cli.Command.Debugger` dropped — debugger + // Note: Zig `debugger: bun.cli.Command.Debugger` dropped — debugger // configuration is plumbed through `RuntimeHooks::ensure_debugger` (the // CLI option struct lives in `bun_cli`, a forward dep). See // `runtime/jsc_hooks.rs` for the spec :1321 `configureDebugger` call site. @@ -2957,7 +2962,10 @@ impl crate::ipc::SendQueueOwner for IPCInstance { #[derive(Default)] pub struct ResolveFunctionResult { pub result: Option, - pub path: &'static [u8], // TODO(port): lifetime — borrows resolver arena + // LIFETIME-ERASED: `path`/`query_string` borrow argv or the resolver's + // process-lifetime arena (`detach_lifetime` in `resolve_maybe_need_dirname_uncached`), + // which outlives every `ResolveFunctionResult`. + pub path: &'static [u8], pub query_string: &'static [u8], } @@ -3213,7 +3221,7 @@ impl VirtualMachine { pub fn get_verbose_fetch(&mut self) -> bun_http::HTTPVerboseLevel { use bun_http::HTTPVerboseLevel as L; if let Some(v) = self.default_verbose_fetch { - // PORT NOTE: field is `Option` until the b2-cycle widens it; + // Note: field is `Option` until the b2-cycle widens it; // map ordinals back. return match v { 1 => L::Headers, @@ -3272,7 +3280,7 @@ impl VirtualMachine { .map(|i| map.map.swap_remove_at(i).1) .and_then(|v| crate::ipc::Mode::from_string(&v.value)) .unwrap_or(crate::ipc::Mode::Json); - // PORT NOTE: Zig `IPC.log()` debug-only; the `IPC` scope static + // Note: Zig `IPC.log()` debug-only; the `IPC` scope static // lives in `crate::ipc` and `scoped_log!` requires a bare ident, // so the log line is dropped here. // Spec: `std.fmt.parseInt(u31, fd_s, 10)` — accept only @@ -3351,7 +3359,7 @@ impl VirtualMachine { return; } - // PORT NOTE: Zig `defer eventLoop().drainMicrotasks()` per-arm — + // Note: Zig `defer eventLoop().drainMicrotasks()` per-arm — // hoisted into a closure. let drain = |this: &mut Self| { let _ = this.event_loop_mut().drain_microtasks(); @@ -3367,7 +3375,7 @@ impl VirtualMachine { }); if let Err(e) = r { let exc = global_object.take_exception(e); - // PORT NOTE: Zig went `exc.asException(vm)` → `reportUncaughtException`, + // Note: Zig went `exc.asException(vm)` → `reportUncaughtException`, // which itself just does `uncaughtException(global, exception.value(), false)`. // `JSValue::as_exception` is not yet ported; inline the body — `exc` is // already the exception's value. @@ -3403,7 +3411,7 @@ impl VirtualMachine { if handled { return; } - // PORT NOTE: Zig returned unconditionally after warn; mirror it. + // Note: Zig returned unconditionally after warn; mirror it. return; } Mode::Strict => { @@ -3443,7 +3451,7 @@ impl VirtualMachine { /// Spec VirtualMachine.zig:718 `reportExceptionInHotReloadedModuleIfNeeded`. pub fn report_exception_in_hot_reloaded_module_if_needed(&mut self) { - // PORT NOTE: Zig `defer this.addMainToWatcherIfNeeded()`. + // Note: Zig `defer this.addMainToWatcherIfNeeded()`. let promise = match self.pending_internal_promise { Some(p) => p, None => { @@ -3581,7 +3589,7 @@ impl VirtualMachine { } // reload_entry_point() stores into pending_internal_promise on every return path. let main = self.main; - // PORT NOTE: reshaped for borrowck — copy the `RawSlice` first to avoid + // Note: reshaped for borrowck — copy the `RawSlice` first to avoid // overlapping `&self`/`&mut self` borrows. if self.reload_entry_point(main.slice()).is_err() { panic!("Failed to reload"); @@ -3619,7 +3627,7 @@ impl VirtualMachine { /// Spec VirtualMachine.zig:1016 `enqueueImmediateTask`. /// - /// PORT NOTE (§Dispatch): `task` is an erased + /// Note (§Dispatch): `task` is an erased /// `*mut bun_runtime::timer::ImmediateObject` — see /// [`crate::event_loop::RunImmediateFn`]. #[inline] @@ -3675,7 +3683,7 @@ impl VirtualMachine { /// Spec VirtualMachine.zig:1107 `initWithModuleGraph`. /// - /// PORT NOTE: shares ~90% with [`init`]; the differences are (a) the + /// Note: shares ~90% with [`init`]; the differences are (a) the /// transpiler is built without `Config::configureTransformOptionsForBunVM`, /// (b) `standalone_module_graph` is mandatory and propagated into the /// resolver, (c) `configureLinkerWithAutoJSX(false)` instead of @@ -3707,7 +3715,7 @@ impl VirtualMachine { /// Spec VirtualMachine.zig:1394 `initWorker`. /// - /// PORT NOTE: takes `&WebWorker` (not `&mut`) — the worker thread may only + /// Note: takes `&WebWorker` (not `&mut`) — the worker thread may only /// hold a shared reference to its `WebWorker` (the parent / main thread /// concurrently observes it; see `web_worker.rs` worker-thread `&self` /// note). All accesses on `worker` here are read-only. @@ -3733,7 +3741,7 @@ impl VirtualMachine { mini_mode: worker.mini(), ..Default::default() }; - // PORT NOTE: Zig open-coded the full struct init; we route through + // Note: Zig open-coded the full struct init; we route through // [`init`] (which already wires console / event-loop / global / jsc_vm // / RuntimeHooks) and then patch the worker-specific fields. let vm = Self::init(init_opts)?; @@ -3772,7 +3780,7 @@ impl VirtualMachine { is_main_thread: opts.is_main_thread, ..Default::default() }; - // PORT NOTE: shares the console / log / event-loop wiring with `init`; + // Note: shares the console / log / event-loop wiring with `init`; // the only delta is the global is created via `BakeCreateProdGlobal` // instead of `ZigGlobalObject__create`. Route through `init` then // swap the global. @@ -3830,7 +3838,7 @@ impl VirtualMachine { ..Default::default() }; } - // PORT NOTE: Zig `refCountedString(code, hash_, !add_double_ref)` — + // Note: Zig `refCountedString(code, hash_, !add_double_ref)` — // const-generic bool can't be `!ADD_DOUBLE_REF`, so branch. let source = if ADD_DOUBLE_REF { self.ref_counted_string::(code, hash_) @@ -3868,10 +3876,10 @@ impl VirtualMachine { jsc::mark_binding(); debug_assert!(!input_.is_empty()); let hash = hash_.unwrap_or_else(|| RefString::compute_hash(input_)); - // PORT NOTE: Zig `lock(); defer unlock()` — RAII guard releases on every + // Note: Zig `lock(); defer unlock()` — RAII guard releases on every // exit (including the early-return `Occupied` arm). let _unlock = self.ref_strings_mutex.lock_guard(); - // PORT NOTE: reshaped for borrowck — capture the back-pointer before + // Note: reshaped for borrowck — capture the back-pointer before // `ref_strings.entry()` takes its unique borrow on `self`. let self_ctx = NonNull::new(std::ptr::from_mut::(self).cast::()); @@ -3932,7 +3940,7 @@ impl VirtualMachine { } /// Spec VirtualMachine.zig:1656 `fetchWithoutOnLoadPlugins`. - // PORT NOTE: Zig `comptime flags: FetchFlags` lowered to a runtime arg — + // Note: Zig `comptime flags: FetchFlags` lowered to a runtime arg — // `FetchFlags` would need `ConstParamTy` (unstable derive on the enum's // owning module) to stay a const generic; the only branches are cheap // equality tests so the runtime form is fine. PERF(port): revisit. @@ -4000,7 +4008,7 @@ impl VirtualMachine { .map(|pkg| pkg.module_type) .unwrap_or(bun_bundler::options::ModuleType::Unknown); - // PORT NOTE: Zig `defer if (flags != .print_source) resetArena(); + // Note: Zig `defer if (flags != .print_source) resetArena(); // errdefer if (flags == .print_source) resetArena()`. Model with a // drop-guard so both paths reset on the right edge. struct ArenaReset<'a>(&'a mut VirtualMachine, bool); @@ -4019,7 +4027,7 @@ impl VirtualMachine { .get() .expect("source_code_printer not initialized"); - // PORT NOTE: Zig passes path/loader/module_type/printer/promise_ptr as + // Note: Zig passes path/loader/module_type/printer/promise_ptr as // positional params to `transpileSourceCode`; the §Dispatch shim takes // them bundled as `TranspileExtra` behind `args.extra` (see // ModuleLoader.rs `TranspileArgs`). @@ -4072,7 +4080,7 @@ impl VirtualMachine { /// Spec VirtualMachine.zig:1724 `_resolve`. /// - /// PORT NOTE: Zig has `comptime is_a_file_path: bool`; folded to a runtime + /// Note: Zig has `comptime is_a_file_path: bool`; folded to a runtime /// arg here to avoid duplicating the body for both monomorphizations. pub fn _resolve( &mut self, @@ -4085,10 +4093,10 @@ impl VirtualMachine { use bun_js_parser::Macro; use bun_resolver::{ResultUnion, node_fallbacks}; - // SAFETY: PORT — `specifier`/`source` borrow argv / resolver-arena - // bytes that outlive `ResolveFunctionResult` (`'static` per the - // struct's TODO(port) lifetime note). Erase to `'static` to seat the - // result paths without threading a lifetime parameter through the VM. + // SAFETY: `specifier`/`source` borrow argv / resolver-arena bytes that + // outlive `ResolveFunctionResult` (see the struct's lifetime-erasure + // note). Erase to `'static` to seat the result paths without threading + // a lifetime parameter through the VM. let specifier: &'static [u8] = unsafe { bun_ptr::detach_lifetime(specifier) }; // `Runtime.Runtime.Imports.{alt_name, Name}` are both `"bun:wrap"` @@ -4166,7 +4174,7 @@ impl VirtualMachine { top_level_dir }; - // PORT NOTE: Zig modeled this as a labeled `while (true)` with + // Note: Zig modeled this as a labeled `while (true)` with // `continue` after a successful cache bust. Expressed as a `loop` // returning the resolver result; `retry_on_not_found` is consumed on // the first miss. @@ -4254,7 +4262,8 @@ impl VirtualMachine { .path_const() .ok_or_else(|| bun_core::err!("ModuleNotFound"))?; // SAFETY: `result_path.text` borrows the resolver's arena, which - // outlives `ResolveFunctionResult` (see field TODO(port) lifetime). + // outlives `ResolveFunctionResult` (see the struct's lifetime-erasure + // note). ret.path = unsafe { bun_ptr::detach_lifetime(result_path.text) }; ret.result = Some(result); self.resolved_count += 1; @@ -4377,7 +4386,7 @@ impl VirtualMachine { // (sole impl — see `VirtualMachine::package_manager`). unsafe { (*pm.cast::().as_ptr()).log = &raw mut log }; } - // PORT NOTE: Zig `defer { restore old_log }` — fires on every exit + // Note: Zig `defer { restore old_log }` — fires on every exit // (including `?` from `ResolveMessage::create` below), so the VM's // `log` cannot be left pointing at the dropped stack `log`. Hand-roll // a drop guard (no captured borrows) so the unique `&mut *jsc_vm_ptr` @@ -4411,7 +4420,7 @@ impl VirtualMachine { vm: bun_ptr::BackRef::from(NonNull::new(jsc_vm_ptr).expect("vm non-null")), old_log, }; - // PORT NOTE: reshaped for borrowck — re-derive from raw so the unique + // Note: reshaped for borrowck — re-derive from raw so the unique // borrow doesn't span the guard's drop. // SAFETY: per-thread VM is live for this synchronous call. let jsc_vm = unsafe { &mut *jsc_vm_ptr }; @@ -4485,13 +4494,13 @@ impl VirtualMachine { self.regular_event_loop.deinit(); self.macro_event_loop.deinit(); - // PORT NOTE: Zig `auto_killer.deinit()` — `ProcessAutoKiller`'s `Drop` + // Note: Zig `auto_killer.deinit()` — `ProcessAutoKiller`'s `Drop` // is the deinit body; take()+drop runs it without dropping `self`. drop(core::mem::take(&mut self.auto_killer)); drop_source_code_printer(); - // PORT NOTE: `SavedSourceMap`'s `Drop` is the Zig `deinit()`; it frees + // Note: `SavedSourceMap`'s `Drop` is the Zig `deinit()`; it frees // each stored map and `deinit()`s the sibling `saved_source_map_table`. drop(core::mem::take(&mut self.source_mappings)); @@ -4509,7 +4518,7 @@ impl VirtualMachine { drop(rare); } - // PORT NOTE: Zig `proxy_env_storage.deinit()` — drops all `Arc`-held + // Note: Zig `proxy_env_storage.deinit()` — drops all `Arc`-held // proxy strings; `ProxyEnvStorage: Default` so take()+drop suffices. drop(core::mem::take(&mut self.proxy_env_storage)); @@ -4528,7 +4537,7 @@ impl VirtualMachine { self.overridden_main.deinit(); - // PORT NOTE: Zig frees `timer`/`entry_point` as value fields of `self`; + // Note: Zig frees `timer`/`entry_point` as value fields of `self`; // here they live in the high-tier `RuntimeState` box, so dispatch the // reclaim through the hook. PERF(port): was inline switch. if let Some(hooks) = runtime_hooks() { @@ -4542,7 +4551,7 @@ impl VirtualMachine { } /// Spec VirtualMachine.zig:2134 `printException`. /// - /// PORT NOTE: Zig is `comptime Writer`-generic; collapse to the concrete + /// Note: Zig is `comptime Writer`-generic; collapse to the concrete /// `bun_core::io::Writer` since every call site passes /// `Output.errorWriterBuffered()`. pub fn print_exception( @@ -4613,7 +4622,7 @@ impl VirtualMachine { } } - // PORT NOTE: reshaped for borrowck. + // Note: reshaped for borrowck. let global = self.global; let main_str = bun_core::String::from_bytes(self.main()); let promise = jsc::JSModuleLoader::load_and_evaluate_module_ptr(global, Some(&main_str)) @@ -4826,7 +4835,7 @@ impl VirtualMachine { /// Spec VirtualMachine.zig:2663 `printErrorlikeObject`. /// - /// PORT NOTE: Zig is `comptime Writer` + `comptime allow_ansi_color` + + /// Note: Zig is `comptime Writer` + `comptime allow_ansi_color` + /// `comptime allow_side_effects` — collapse to runtime bools and the /// concrete `bun_core::io::Writer`. pub fn print_errorlike_object( @@ -4839,7 +4848,7 @@ impl VirtualMachine { allow_ansi_color: bool, allow_side_effects: bool, ) { - // PORT NOTE: Zig declared `was_internal` and ran the + // Note: Zig declared `was_internal` and ran the // exception-stack-trace `defer` after the body. Reshape: handle the // post-print stack/exception_list block at the tail instead of via a // drop guard (the body has no early-`?` returns once the AggregateError @@ -4847,7 +4856,7 @@ impl VirtualMachine { let global_ref = self.global(); if value.is_aggregate_error(global_ref) { - // PORT NOTE: Zig comptime-generated `AggregateErrorIterator` with + // Note: Zig comptime-generated `AggregateErrorIterator` with // `extern "C"` callbacks. `JSValue::for_each` takes a C-ABI fn // pointer + erased ctx, so thread the captures through a struct. // The C trampoline erases lifetimes via `*mut c_void`; round-trip @@ -4910,7 +4919,7 @@ impl VirtualMachine { return; } - // PORT NOTE: reborrow so the Zig `defer { addToErrorList(exception_list) }` + // Note: reborrow so the Zig `defer { addToErrorList(exception_list) }` // tail can still see it after `print_error_from_maybe_private_data`. let mut exception_list = exception_list; let was_internal = self.print_error_from_maybe_private_data( @@ -4925,7 +4934,7 @@ impl VirtualMachine { if was_internal { if let Some(exception_) = exception { let mut holder = crate::zig_exception::Holder::init(); - // PORT NOTE: Zig calls `holder.deinit(this)` *before* + // Note: Zig calls `holder.deinit(this)` *before* // `getStackTrace` (no-op on the empty holder); reordered to // the tail for borrowck — semantics unchanged because // `need_to_clear_parser_arena_on_deinit` is false here. @@ -5044,7 +5053,7 @@ impl VirtualMachine { /// Spec VirtualMachine.zig:2813 `printStackTrace`. /// - /// PORT NOTE: Zig is `comptime Writer` + `comptime allow_ansi_colors`; + /// Note: Zig is `comptime Writer` + `comptime allow_ansi_colors`; /// collapse to runtime bool + concrete writer. pub fn print_stack_trace( writer: &mut bun_core::io::Writer, @@ -5081,7 +5090,7 @@ impl VirtualMachine { !probe.is_empty() }; - // PORT NOTE: Zig used `comptime Output.prettyFmt(...)` per arm; + // Note: Zig used `comptime Output.prettyFmt(...)` per arm; // route through `bun_core::pretty_fmt!` with a local wrapper that // dispatches on the runtime `allow_ansi_colors` flag. macro_rules! pretty_write { @@ -5133,11 +5142,11 @@ impl VirtualMachine { } // **Warning** this method can be called in the heap collector thread!! self.remap_stack_frames_mutex.lock(); - // PORT NOTE: Zig `defer unlock()`. + // Note: Zig `defer unlock()`. self.source_mappings.lock(); - // PORT NOTE: the Zig body caches the last `(hash → InternalSourceMap)` + // Note: the Zig body caches the last `(hash → InternalSourceMap)` // pair across the loop and falls back to `resolve_source_mapping` on a // miss. The cache is purely a perf optimization (most stacks repeat // the same source); port the straightforward per-frame resolve and @@ -5209,7 +5218,7 @@ impl VirtualMachine { .unwrap_or(false)), ); - // PORT NOTE: Zig modeled the two `defer` blocks below at fn-top; in + // Note: Zig modeled the two `defer` blocks below at fn-top; in // Rust we run them on the way out via this guard so every early // `return` is covered. struct Tail<'a> { @@ -5322,7 +5331,7 @@ impl VirtualMachine { { continue; } - // PORT NOTE: `frames[j] = frame`. `ZigStackFrame` impls + // Note: `frames[j] = frame`. `ZigStackFrame` impls // `Drop` so `copy_within` is unavailable; swap instead — // the discarded tail past `j` is never read after we // truncate `frames_len` below. @@ -5449,12 +5458,12 @@ impl VirtualMachine { return; }; *must_reset_parser_arena_later = true; - // PORT NOTE: spec ModuleLoader.zig:358 *borrows* + // Note: spec ModuleLoader.zig:358 *borrows* // `parse_result.source.contents` for `.print_source` // (`bun.String.init`), so the Zig caller has nothing to // release. The Rust transpile path must `clone_utf8` instead // (the backing `parse_result` drops on return — see - // jsc_hooks.rs PORT NOTE at the `PrintSource` arm), leaving + // jsc_hooks.rs Note at the `PrintSource` arm), leaving // `source_code` with a +1 strong ref this caller never // consumed. `to_utf8()` takes its own ref via // `ZigStringSlice::WTF`, so balance the clone here. Also @@ -5581,7 +5590,7 @@ impl VirtualMachine { allow_ansi_color: bool, allow_side_effects: bool, ) -> Result<(), bun_core::Error> { - // PORT NOTE: stack-safety guard for the Error recursion path. + // Note: stack-safety guard for the Error recursion path. // Zig's `printErrorInstance` is `comptime Writer`/`allow_ansi_color`/ // `allow_side_effects`-monomorphized so each instantiation carries // only one branch's locals. The Rust port collapses those to runtime @@ -5616,13 +5625,13 @@ impl VirtualMachine { return Ok(()); } - // PORT NOTE: `Holder` is ~4 KB (32 ZigStackFrames + 6 source lines + + // Note: `Holder` is ~4 KB (32 ZigStackFrames + 6 source lines + // ZigException). Zig stack-allocates it inside a small monomorphized // function; here it sits next to the large runtime-dispatched body, so // box it to keep the per-level recursion frame small enough for the // 16K-deep `bun-inspect.test.ts` Error chain on Windows debug. let mut exception_holder = Box::new(crate::zig_exception::Holder::init()); - // PORT NOTE: reshaped for borrowck — `zig_exception()` returns a + // Note: reshaped for borrowck — `zig_exception()` returns a // `&mut` into the holder; we need to also borrow // `need_to_clear_parser_arena_on_deinit` disjointly. Route through a // raw pointer (the holder is heap-pinned for the call). @@ -5644,7 +5653,7 @@ impl VirtualMachine { // SAFETY: see above. unsafe { &mut *exception }, error_instance, - None, // PORT NOTE: spec passes `exception_list` but it was already + None, // Note: spec passes `exception_list` but it was already // consumed by `remap_zig_exception` above (only writer). formatter, writer, @@ -5684,7 +5693,7 @@ impl VirtualMachine { let prev_had_errors = self.had_errors; self.had_errors = true; - // PORT NOTE: Zig `defer this.had_errors = prev_had_errors;` — restore on + // Note: Zig `defer this.had_errors = prev_had_errors;` — restore on // every exit (including `?` from `JSError` paths). `BackRef` holds the // VM without a live borrow; the write at drop routes through // `VirtualMachine::as_mut` (thread-local provenance). @@ -5709,7 +5718,7 @@ impl VirtualMachine { } } - // PORT NOTE: Zig `defer if (allow_side_effects and Output.is_github_action) + // Note: Zig `defer if (allow_side_effects and Output.is_github_action) // printGithubAnnotation(exception);`. struct DeferGhAnnotation { run: bool, @@ -5827,7 +5836,7 @@ impl VirtualMachine { // `Formatter<'2>::format`, which requires an unbounded (VM-lifetime) // borrow. `global()` returns `&'static` so the borrow detaches. let global_ref = self.global(); - // PORT NOTE: Zig keeps a borrowed `[]const u8` whose backing + // Note: Zig keeps a borrowed `[]const u8` whose backing // `bun.String` is `defer .deref()`-ed; hold the owning `bun_core::String` // alongside the slice so the latin1 view stays live for this fn. // `bun_core::String` is `Copy` (no `Drop`), so use a scopeguard to @@ -5998,7 +6007,7 @@ impl VirtualMachine { // This is usually unsafe to do, but we are protecting them each time first. let mut errors_to_append: Vec = Vec::new(); - // PORT NOTE: Zig `defer { for (..) |e| e.unprotect(); deinit(); }`. + // Note: Zig `defer { for (..) |e| e.unprotect(); deinit(); }`. // `BackRef` (constructed from `&raw mut` via `NonNull` so the tag is // not popped by later `errors_to_append.push` reborrows) lets the drop // body read the Vec safely. @@ -6065,7 +6074,7 @@ impl VirtualMachine { formatter.max_depth = 1; formatter.quote_strings = true; formatter.disable_inspect_custom = true; - // PORT NOTE: Zig `defer { restore }` — hand-rolled drop guard. + // Note: Zig `defer { restore }` — hand-rolled drop guard. struct RestoreFmt<'a, 'f> { f: &'a mut crate::console_object::Formatter<'f>, d: bool, @@ -6489,7 +6498,7 @@ impl VirtualMachine { self.event_loop_mut().ensure_waker(); - // PORT NOTE: reshaped for borrowck — `rare_data()` borrows `self` and + // Note: reshaped for borrowck — `rare_data()` borrows `self` and // `spawn_ipc_group` then needs `&mut VirtualMachine`. Split via raw // pointers (disjoint fields) per the existing `Bun__RareData__*` // accessors in virtual_machine_exports.rs. diff --git a/src/jsc/WTF.rs b/src/jsc/WTF.rs index 4e586823dfbc..f30c69dab7ed 100644 --- a/src/jsc/WTF.rs +++ b/src/jsc/WTF.rs @@ -1,11 +1,5 @@ -// PORT NOTE: Zig wraps everything in `pub const WTF = struct { ... }` as a -// namespace. In Rust the file itself is the module, so items live at module -// level. Callers use `bun_jsc::wtf::foo()`. -// -// PORT NOTE: `jsc.markBinding(@src())` calls dropped — debug-only binding -// presence marker with no Rust equivalent. - -// without a T6 dep. Re-exported here to keep the original Zig namespace shape. +// Canonical lives in `bun_alloc::wtf` so it is callable without a T6 dep. +// Re-exported here to keep the original Zig namespace shape. pub use bun_alloc::wtf::release_fast_malloc_free_memory_for_this_thread; // `WTF.parseDouble` canonical lives in bun_core::fmt (tier-0); re-exported here @@ -16,7 +10,8 @@ pub type ParseDoubleError = bun_core::fmt::InvalidCharacter; // Canonical lives in bun_core (tier-0) so install/ can call it without bun_jsc. pub use bun_core::wtf::{InvalidDate, parse_es5_date, parse_es5_date_raw}; -// it without a bun_jsc dep. Re-exported here to keep the Zig namespace shape +// Canonical lives in `bun_http_types` so http code can call it without a +// bun_jsc dep. Re-exported here to keep the Zig namespace shape // (src/jsc/WTF.zig:52). pub use bun_http_types::ETag::wtf::write_http_date; diff --git a/src/jsc/Weak.rs b/src/jsc/Weak.rs index 14e8ffb8f5ae..d671e8d2b1c1 100644 --- a/src/jsc/Weak.rs +++ b/src/jsc/Weak.rs @@ -58,8 +58,6 @@ impl WeakImpl { } } -// TODO(port): move to jsc_sys -// // `WeakImpl` is an opaque `UnsafeCell`-backed ZST handle (`&WeakImpl` is // ABI-identical to non-null `*const WeakImpl`; C++ slot mutation is interior). // `new` is `safe fn`: `&JSGlobalObject` is the non-null handle proof, and `ctx` @@ -103,7 +101,7 @@ impl Weak { let Some(function) = self.try_swap() else { return JSValue::ZERO; }; - // PORT NOTE: Zig source (Weak.zig:50) calls `function.call(global, args)` + // The Zig source (Weak.zig:50) calls `function.call(global, args)`, // which predates the `thisValue` param on JSValue.call; pass `.undefined`. function .call(&self.global_this.unwrap(), JSValue::UNDEFINED, args) diff --git a/src/jsc/WorkTask.rs b/src/jsc/WorkTask.rs index 9d3541f7ed3e..66fc15ebcd18 100644 --- a/src/jsc/WorkTask.rs +++ b/src/jsc/WorkTask.rs @@ -45,7 +45,6 @@ pub struct WorkTask { pub async_task_tracker: AsyncTaskTracker, // This is a poll because we want it to enter the uSockets loop - // PORT NOTE: `ref` is a Rust keyword; field renamed to `ref_`. pub ref_: KeepAlive, } @@ -79,15 +78,15 @@ impl WorkTask { }); this.ref_.ref_(Async::js_vm_ctx()); - // PORT NOTE: intrusive `task` field is recovered via container_of in + // The intrusive `task` field is recovered via container_of in // run_from_thread_pool, so this must live at a stable heap address as a // raw pointer. Paired with `heap::take` in `destroy`. bun_core::heap::into_raw(this) } - // PORT NOTE: not `impl Drop` — `ref_.unref` is also called from `run_from_js`, - // and `Self` is held as a raw pointer (intrusive task). Explicit destroy matches - // the Zig `bun.destroy(this)` shape. + // Not `impl Drop` — `ref_.unref` is also called from `run_from_js`, + // and `Self` is held as a raw pointer (intrusive task), so destruction + // is explicit. pub unsafe fn destroy(this: *mut Self) { // SAFETY: `this` was produced by heap::alloc in create_on_js_thread and // has not been freed. diff --git a/src/jsc/ZigErrorType.rs b/src/jsc/ZigErrorType.rs index 5b5af8d38ff8..c732fa44771f 100644 --- a/src/jsc/ZigErrorType.rs +++ b/src/jsc/ZigErrorType.rs @@ -5,7 +5,7 @@ use crate::error_code::ErrorCode; #[derive(Copy, Clone)] pub struct ZigErrorType { pub code: ErrorCode, - // PORT NOTE: bare JSValue field is OK here — this is a #[repr(C)] FFI payload + // Bare JSValue field is OK here — this is a #[repr(C)] FFI payload // passed by value across the C++ boundary, not a heap-allocated Rust struct. pub value: JSValue, } diff --git a/src/jsc/ZigException.rs b/src/jsc/ZigException.rs index a0503184e4d8..a3a675019d3c 100644 --- a/src/jsc/ZigException.rs +++ b/src/jsc/ZigException.rs @@ -54,7 +54,7 @@ impl ZigException { ZigException__collectSourceLines(value, global, self); } - // PORT NOTE: kept as explicit `deinit` (not `Drop`) — this is a #[repr(C)] FFI + // Kept as explicit `deinit` (not `Drop`) — this is a #[repr(C)] FFI // payload whose lifetime is gated by `Holder.loaded`; C++ constructs/populates it. pub fn deinit(&mut self) { self.syscall.deref(); @@ -79,10 +79,10 @@ impl ZigException { } } - // PORT NOTE: `ZigException__fromException` is declared in headers.h but - // has no C++ body (bindings.cpp dropped it; the only producer is - // `JSC__JSValue__toZigException` which writes through an out-param). The - // Zig `fromException` re-export is dead code; do not port it. + // `ZigException__fromException` is declared in headers.h but has no C++ + // body (bindings.cpp dropped it; the only producer is + // `JSC__JSValue__toZigException` which writes through an out-param), so + // there is intentionally no `from_exception` here. pub fn add_to_error_list( &mut self, @@ -95,20 +95,17 @@ impl ZigException { let name = name_slice.slice(); let message = message_slice.slice(); - // PORT NOTE: `defer name_slice.deinit()` / `defer message_slice.deinit()` — - // `ZigStringSlice` drops at scope exit. let mut is_empty = true; let mut api_exception = api::JsException { - // PORT NOTE: `@intFromEnum` — JSRuntimeType/JSErrorCode are - // transparent newtypes over u16/u8 (non-exhaustive Zig enums). + // JSRuntimeType/JSErrorCode are transparent newtypes over u16/u8 + // (non-exhaustive enums). runtime_type: self.runtime_type.0, code: u16::from(self.r#type.0), ..Default::default() }; if !name.is_empty() { - // PORT NOTE: `error_list.allocator.dupe(u8, _name)` → Box<[u8]> (global mimalloc). api_exception.name = Box::<[u8]>::from(name); is_empty = false; } @@ -137,8 +134,8 @@ pub struct Holder { pub source_lines: [String; Self::SOURCE_LINES_COUNT], pub frames: [ZigStackFrame; Self::FRAME_COUNT], pub loaded: bool, - // PORT NOTE: Zig had `= undefined` (never read until `loaded` flips and - // `zig_exception()` writes it). Use MaybeUninit and gate access on `loaded`. + // Never read until `loaded` flips and `zig_exception()` writes it; + // all access must be gated on `loaded`. pub zig_exception: MaybeUninit, pub need_to_clear_parser_arena_on_deinit: bool, } @@ -147,12 +144,8 @@ impl Holder { const FRAME_COUNT: usize = 32; pub const SOURCE_LINES_COUNT: usize = 6; - // PORT NOTE: Zig had `pub const Zero: Holder`; Rust const requires every - // initializer to be const-evaluable. Using a fn instead. pub fn zero() -> Self { Self { - // PORT NOTE: `[ZigStackFrame::ZERO; N]` would require `Copy`; - // mirror the Zig `@memset` via from_fn. frames: core::array::from_fn(|_| ZigStackFrame::ZERO), source_line_numbers: [-1; Self::SOURCE_LINES_COUNT], source_lines: core::array::from_fn(|_| String::EMPTY), @@ -166,11 +159,11 @@ impl Holder { Self::zero() } - // PORT NOTE: not just `Drop` — takes `vm` parameter for `reset_arena`. Zig - // callers all `defer holder.deinit(vm)`; Rust callers should still call this - // explicitly at the tail (it does the arena reset which `Drop` cannot), but - // the string-ref release half is also covered by `Drop` below so an early - // `?`/return between population and the tail call won't leak WTF string refs. + // Not just `Drop` — takes the `vm` parameter for `reset_arena`. Callers + // should call this explicitly at the tail (it does the arena reset which + // `Drop` cannot), but the string-ref release half is also covered by + // `Drop` below so an early `?`/return between population and the tail + // call won't leak WTF string refs. pub fn deinit(&mut self, vm: &mut VirtualMachine) { if self.loaded { // SAFETY: `loaded == true` ⇔ `zig_exception()` has written this slot. @@ -179,9 +172,6 @@ impl Holder { self.loaded = false; } if self.need_to_clear_parser_arena_on_deinit { - // PORT NOTE: reshaped for borrowck — Zig `vm.module_loader.resetArena(vm)` - // would borrow `vm` twice; the Rust port made `reset_arena` an - // associated fn on `ModuleLoader` taking only `&mut VirtualMachine`. ModuleLoader::reset_arena(vm); } } @@ -224,10 +214,10 @@ impl Holder { } impl Drop for Holder { - // PORT NOTE: restores the string-ref-release half of Zig's - // `defer holder.deinit(vm)`. The explicit `deinit(&mut self, vm)` clears - // `loaded` after running, so this is a no-op on the happy path; it only - // fires when an early-return/`?`/panic skips the tail `deinit` call. + // Covers the string-ref-release half of cleanup. The explicit + // `deinit(&mut self, vm)` clears `loaded` after running, so this is a + // no-op on the happy path; it only fires when an early-return/`?`/panic + // skips the tail `deinit` call. fn drop(&mut self) { if self.loaded { // SAFETY: `loaded == true` ⇔ `zig_exception()` has written this slot. diff --git a/src/jsc/ZigStackFrameCode.rs b/src/jsc/ZigStackFrameCode.rs index 545cef86c331..aa9153ad7675 100644 --- a/src/jsc/ZigStackFrameCode.rs +++ b/src/jsc/ZigStackFrameCode.rs @@ -1,8 +1,7 @@ -// PORT NOTE: Zig source is `enum(u8) { ..., _ }` (non-exhaustive — any u8 is a -// valid bit pattern). A `#[repr(u8)] enum` in Rust would be UB for values >6 -// arriving over FFI, so this is ported as a transparent newtype with associated -// consts. The `match` arms below mirror the Zig `switch` exactly, including the -// `else` fallthrough for unknown values. +// This is logically a non-exhaustive enum(u8): any u8 is a valid bit pattern +// over FFI. A `#[repr(u8)] enum` in Rust would be UB for values >6 arriving +// from C++, so this is a transparent newtype with associated consts; the +// `match` arms below must keep a fallthrough for unknown values. #[repr(transparent)] #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct ZigStackFrameCode(pub u8); @@ -22,8 +21,7 @@ impl ZigStackFrameCode { /// 👷 pub const CONSTRUCTOR: Self = Self(6); - // PORT NOTE: Zig returns `u21` (Unicode code point width). Rust has no u21; - // u32 is the narrowest native type that fits. + /// Returns a Unicode code point. pub fn emoji(self) -> u32 { match self { Self::EVAL => 0x1F3C3, diff --git a/src/jsc/ZigStackFramePosition.rs b/src/jsc/ZigStackFramePosition.rs index 881456735168..fe9901f9fa5e 100644 --- a/src/jsc/ZigStackFramePosition.rs +++ b/src/jsc/ZigStackFramePosition.rs @@ -25,7 +25,6 @@ impl ZigStackFramePosition { *self == Self::INVALID } - // TODO(port): narrow error set pub fn decode(reader: &mut R) -> Result where R: ?Sized + bun_analytics::Reader, @@ -33,9 +32,9 @@ impl ZigStackFramePosition { Ok(Self { line: Ordinal::from_zero_based(reader.read_value::()?), column: Ordinal::from_zero_based(reader.read_value::()?), - // TODO(port): Zig's `decode` omits `line_start_byte` in the struct literal - // (extern-struct field left at zero/default). Confirm intended value. - line_start_byte: 0, + // `encode` never writes this field, so a decoded frame has no + // line-start-byte information: -1 means "not present". + line_start_byte: -1, }) } diff --git a/src/jsc/ZigStackTrace.rs b/src/jsc/ZigStackTrace.rs index fe7012e33537..c5d69f0cc1ee 100644 --- a/src/jsc/ZigStackTrace.rs +++ b/src/jsc/ZigStackTrace.rs @@ -61,11 +61,9 @@ impl ZigStackTrace { if source_line_len > 0 { let n_lines = usize::try_from((source_lines_iter.i + 1).max(0)).expect("int cast"); let mut source_lines: Vec = Vec::with_capacity(n_lines); - // PORT NOTE: Zig packed all line texts into a single contiguous - // `source_line_buf` and stored sub-slices in each `SourceLine`. - // The Rust `api::SourceLine.text` is `Box<[u8]>` (owns its bytes), - // so each line gets its own allocation instead. - // PERF(port): one alloc per line vs one shared buffer — profile if hot. + // `api::SourceLine.text` is `Box<[u8]>` (owns its bytes), so each + // line gets its own allocation rather than sub-slices of one shared + // buffer. One alloc per line — profile if this ever shows up hot. source_lines_iter = self.source_line_iterator(); while let Some(source) = source_lines_iter.next() { let text = source.text.slice(); diff --git a/src/jsc/ZigString.rs b/src/jsc/ZigString.rs index 00cfcc110f03..3beb8193b61a 100644 --- a/src/jsc/ZigString.rs +++ b/src/jsc/ZigString.rs @@ -66,14 +66,14 @@ pub unsafe fn to_external_u16(ptr: *const u16, len: usize, global: &JSGlobalObje if len > BunString::max_length() { // SAFETY: caller contract — `ptr` came from the default (global) allocator. unsafe { bun_alloc::default_alloc::free(ptr.cast_mut().cast::()) }; - // TODO(port): Zig used `global.ERR(.STRING_TOO_LONG, msg).throw()`; - // the codegen'd `ErrorCode::ERR_STRING_TOO_LONG` builder hasn't landed - // yet, so throw a plain RangeError with the same message. Propagation - // is swallowed (matches Zig's `catch {}`). - let err = global.create_range_error_instance(format_args!( - "Cannot create a string longer than 2^32-1 characters" - )); - let _ = global.throw_value(err); + // Zig: `global.ERR(.STRING_TOO_LONG, msg).throw()`; propagation is + // swallowed (matches Zig's `catch {}`). + let _ = global + .err( + crate::ErrorCode::STRING_TOO_LONG, + format_args!("Cannot create a string longer than 2^32-1 characters"), + ) + .throw(); return JSValue::ZERO; } // SAFETY: ptr/len describe a globally-allocated UTF-16 buffer; ownership @@ -92,8 +92,11 @@ pub(crate) unsafe extern "C" fn ZigString__free( let Some(allocator_) = core::ptr::NonNull::new(allocator_) else { return; }; - // TODO(port): Zig dereferenced *std.mem.Allocator from opaque ptr — Rust uses global mimalloc; - // verify no callers pass a non-default allocator here. + // Zig dereferenced `*std.mem.Allocator` from the opaque pointer; on the + // Rust side the buffer is always owned by the global allocator. Verified: + // no C++ call site passes a non-default allocator — the only reference to + // this symbol outside this file is the declaration in + // headers-handwritten.h (helpers.h frees via `ZigString__freeGlobal`). let _ = allocator_; // SAFETY: raw/len describe a valid slice allocated by the caller-provided allocator. let s = unsafe { bun_core::ffi::slice(raw, len) }; diff --git a/src/jsc/array_buffer.rs b/src/jsc/array_buffer.rs index 0541d38e7de1..7fa48eb2b406 100644 --- a/src/jsc/array_buffer.rs +++ b/src/jsc/array_buffer.rs @@ -44,9 +44,7 @@ impl Default for ArrayBuffer { } } -// TODO(port): move to _sys -// -// PORT NOTE (aliasing): Zig declares these with `*jsc.JSGlobalObject` (mutable +// Aliasing: Zig declares these with `*jsc.JSGlobalObject` (mutable // pointer), but `JSGlobalObject` is an opaque ZST handle on the Rust side — the // `&JSGlobalObject` reference covers zero bytes, and all mutation happens inside // C++ on memory Rust never observes. Declaring the FFI parameter as @@ -295,7 +293,8 @@ impl ArrayBuffer { #[inline] pub fn stream(self) -> ArrayBufferStream<'static> { - // TODO(port): lifetime — Zig returns a stream over self.slice() (raw ptr-backed). + // Lifetime: Zig returns a stream over self.slice() (raw ptr-backed); the + // caller must keep the backing JSValue alive for the stream's lifetime. // Spec routes through `slice()` which yields `&.{}` for detached buffers; mirror // that here to avoid passing a null ptr to `from_raw_parts_mut` (UB even at len 0). if self.is_detached() { @@ -307,7 +306,7 @@ impl ArrayBuffer { std::io::Cursor::new(slice) } - // PORT NOTE: Zig took `comptime kind: JSType`. Restored via + // Zig took `comptime kind: JSType`. Restored via // `#![feature(adt_const_params)]` — `JSType` derives `ConstParamTy`, so // `KIND` is a true const-generic and the `match` const-folds (Zig's // `@compileError` arm becomes a post-mono `panic!` on the unreachable arm). @@ -403,7 +402,7 @@ impl ArrayBuffer { JSType::ArrayBuffer => unsafe { JSArrayBuffer__fromDefaultAllocator(global, bytes.as_mut_ptr(), bytes.len()) }, - // PORT NOTE: `JSUint8Array::from_bytes` takes `Box<[u8]>`; reconstruct + // `JSUint8Array::from_bytes` takes `Box<[u8]>`; reconstruct // ownership from the mimalloc-backed slice the caller hands us. JSType::Uint8Array => { // SAFETY: caller guarantees `bytes` is exactly a `Box<[u8]>` @@ -474,7 +473,7 @@ impl ArrayBuffer { self.ptr.cast(), self.byte_len, Some(MarkedArrayBuffer_deallocator), - // PORT NOTE: Zig passes `&bun.default_allocator` as opaque ctx; the + // Zig passes `&bun.default_allocator` as opaque ctx; the // deallocator ignores it (mi_free needs no ctx). Any non-null // sentinel would do; pass the data ptr itself for symmetry with // `MarkedArrayBuffer::to_js`. @@ -570,7 +569,7 @@ impl ArrayBuffer { /// ```js /// new ArrayBuffer(view.buffer, view.byteOffset, view.byteLength) /// ``` - // PORT NOTE: Zig `byteSlice(self: *const @This()) []u8` is sound under Zig's + // Zig `byteSlice(self: *const @This()) []u8` is sound under Zig's // aliasing model but cannot be transliterated to `&self -> &mut [_]` in Rust // (forbidden aliased-`&mut` per PORTING.md §Forbidden). Split into a shared // accessor (`&self -> &[u8]`) and an exclusive one (`&mut self -> &mut [u8]`). @@ -674,9 +673,9 @@ impl Default for ArrayBufferStrong { impl ArrayBufferStrong { pub fn clear(&mut self) { - // TODO(port): Zig source references `this.ref` which is not a field on this struct - // (only `array_buffer` and `held` exist). This appears to be dead/broken code upstream. - // Porting as a no-op matching the orelse-return on a missing field. + // Zig source references `this.ref`, which is not a field on this struct + // (only `array_buffer` and `held` exist) — dead/broken code upstream. + // Deliberately ported as a no-op matching the orelse-return on a missing field. let _ = self; } @@ -820,7 +819,7 @@ impl BinaryType { let buffer = ArrayBuffer::create::<{ JSType::ArrayBuffer }>(global, bytes)?; // SAFETY: FFI — `global` is a live opaque ZST handle; `JSGlobalObject` is // a ZST on the Rust side so the `*const` → `*mut` cast launders no - // provenance (see PORT NOTE on the extern block above). `buffer` is a + // provenance (see the aliasing note on the extern block above). `buffer` is a // fresh ArrayBuffer JSValue (cell pointer), so `as_object_ref` yields a // valid `JSObjectRef`. let obj = unsafe { @@ -909,7 +908,8 @@ pub(crate) type ArrayBufferStream<'a> = std::io::Cursor<&'a mut [u8]>; impl MarkedArrayBuffer { #[inline] pub fn stream(&mut self) -> ArrayBufferStream<'_> { - // TODO(port): see ArrayBuffer::stream lifetime note. + // See the ArrayBuffer::stream lifetime note: caller must keep the + // backing JSValue alive for the stream's lifetime. std::io::Cursor::new(self.buffer.byte_slice_mut()) } diff --git a/src/jsc/bindgen.rs b/src/jsc/bindgen.rs index 1578c126ef5c..36c661deb9b7 100644 --- a/src/jsc/bindgen.rs +++ b/src/jsc/bindgen.rs @@ -123,11 +123,9 @@ pub struct BindgenOptional(PhantomData); // Default path: `Child` does NOT define a custom optional repr — wrap in // `ExternTaggedUnion<(u8, Child::ExternType)>` and produce `Option`. // -// PORT NOTE: Zig switches on `@hasDecl(Child, "OptionalExternType")` to pick -// between this default and `Child::convertOptionalFromExtern`. Stable Rust -// cannot specialize on "does Child impl BindgenOptionalRepr", so the bindgen -// codegen emits `BindgenOptional` vs `BindgenOptionalCustom` -// explicitly per call site. +// Stable Rust cannot specialize on "does Child impl BindgenOptionalRepr", +// so the bindgen codegen emits `BindgenOptional` vs +// `BindgenOptionalCustom` explicitly per call site. impl Bindgen for BindgenOptional { type ZigType = Option; type ExternType = ExternTaggedUnion2; @@ -197,10 +195,8 @@ impl BindgenOptionalRepr for BindgenString { // generated per arity (or by a proc-macro from the bindgen codegen). // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: `BindgenUnion(children)` reflects over a comptime type list to -// build a tagged union and dispatch `convertFromExtern` per arm via -// `inline else`. The bindgen TS codegen emits a concrete `enum` + `#[repr(C)]` -// union pair per call site rather than a generic Rust combinator (see +// The bindgen TS codegen emits a concrete `enum` + `#[repr(C)]` union pair +// per call site rather than a generic Rust combinator (see // `src/jsc/generated.rs`). This marker type exists for documentation parity. pub struct BindgenUnion; @@ -216,9 +212,10 @@ pub struct ExternTaggedUnion2 { pub tag: u8, } -/// PORT NOTE: union fields wrapped in `ManuallyDrop` so non-`Copy` payloads +/// Union fields are wrapped in `ManuallyDrop` so non-`Copy` payloads /// (e.g. nested `ExternTaggedUnion2`, `ExternArrayList`) are permitted without -/// trait bounds. Matches Zig `extern union` semantics (no auto-drop). +/// trait bounds. There is no auto-drop — the active arm must be dropped +/// explicitly by whoever knows the tag. #[repr(C)] pub union ExternUnion2 { pub _0: ManuallyDrop, @@ -256,13 +253,12 @@ impl Bindgen for BindgenArray { // Don't reuse memory in this case; it would be freed by the wrong allocator. } else if size_of::() == size_of::() && align_of::() == align_of::() - // PORT NOTE: Zig checks `Child.ZigType == Child.ExternType` (type identity). - // Rust has no stable type-equality test in generic context. Gate this - // fast-path on a `const SAME_REPR: bool` opt-in so it only fires when the - // bindgen codegen has proven layout identity. + // Rust has no stable type-equality test in generic context, so this + // fast-path is gated on the `const SAME_REPR: bool` opt-in: it only + // fires when the bindgen codegen has proven layout identity. && Child::SAME_REPR { - // PORT NOTE: when the types are identical the Vec is returned as-is. + // The layouts are identical, so the Vec is returned as-is. let (ptr, len, cap) = { let mut v = ManuallyDrop::new(unmanaged); (v.as_mut_ptr(), v.len(), v.capacity()) @@ -276,23 +272,20 @@ impl Bindgen for BindgenArray { { // We can reuse the allocation, but we still need to convert the elements. // - // PORT NOTE: Zig's `@ptrCast(unmanaged.allocatedSlice())` to a `[]u8` - // is fine under Zig's (lack of a) memory model. In Rust, materializing - // a `&mut [u8]` over the full capacity would assert that every byte — - // including uninitialized tail elements and `ExternType` padding — is - // a valid `u8`, which is UB. Work entirely through raw `*mut u8` and - // `ptr::copy_nonoverlapping` instead; no reference to the storage is - // ever formed. + // Materializing a `&mut [u8]` over the full capacity would assert that + // every byte — including uninitialized tail elements and `ExternType` + // padding — is a valid `u8`, which is UB. Work entirely through raw + // `*mut u8` and `ptr::copy_nonoverlapping` instead; no reference to + // the storage is ever formed. let mut v = ManuallyDrop::new(unmanaged); let mut storage_ptr: *mut u8 = v.as_mut_ptr().cast::(); let storage_len = v.capacity() * size_of::(); // Convert the elements. for i in 0..length { - // Zig doesn't have a formal aliasing model, so we should be maximally - // pessimistic. - // PORT NOTE: Rust DOES — but we keep the byte-wise copy to match behavior - // exactly (in-place reinterpretation of overlapping element slots). + // Byte-wise copy: this is an in-place reinterpretation of + // overlapping element slots, so each element is copied out + // before conversion. let mut old_elem = core::mem::MaybeUninit::::uninit(); // SAFETY: source range lies within the mimalloc block and holds a // valid (C++-initialized) `ExternType` for `i < length`. diff --git a/src/jsc/bindgen_test.rs b/src/jsc/bindgen_test.rs index d6d1c4119720..1d63068b5ee9 100644 --- a/src/jsc/bindgen_test.rs +++ b/src/jsc/bindgen_test.rs @@ -6,8 +6,8 @@ use crate::{JSGlobalObject, JSObject, JSValue, JsResult}; use crate::r#gen::bindgen_test as generated; pub fn get_bindgen_test_functions(global: &JSGlobalObject) -> JsResult { - // PORT NOTE: Zig used an anon struct with `jsc.JSObject.create`; Rust has no - // field reflection, so a local `PojoFields` impl enumerates the fields. + // Rust has no field reflection, so a local `PojoFields` impl enumerates + // the fields. struct Fns { add: JSValue, required_and_optional_arg: JSValue, diff --git a/src/jsc/btjs.rs b/src/jsc/btjs.rs index 0272ce18402f..4b09ed12a981 100644 --- a/src/jsc/btjs.rs +++ b/src/jsc/btjs.rs @@ -100,7 +100,7 @@ mod tty { // `file.getOrEnableAnsiEscapeSupport()` — on POSIX this is `isatty(fd)`; // on Windows it tries to enable VT processing on the console handle. - // PORT NOTE: btjs writes into a `Vec` returned to lldb, so the + // btjs writes into a `Vec` returned to lldb, so the // `.windows_api` variant (which calls `SetConsoleTextAttribute` mid-write) // cannot apply; fall through to escape_codes / no_color. if bun_sys::isatty(bun_sys::Fd::stdout()) { @@ -206,7 +206,6 @@ fn print_source_at_address( tty_config: tty::Config, fp: usize, ) -> Result<(), Error> { - // TODO(port): narrow error set if !cfg!(debug_assertions) { unreachable!(); } @@ -285,7 +284,6 @@ fn print_unknown_source( address: usize, tty_config: tty::Config, ) -> Result<(), Error> { - // TODO(port): narrow error set if !cfg!(debug_assertions) { unreachable!(); } @@ -315,7 +313,6 @@ fn print_line_info( print_line_from_file: impl Fn(&mut Vec, &SourceLocation) -> Result<(), Error>, do_llint: bool, ) -> Result<(), Error> { - // TODO(port): narrow error set if !cfg!(debug_assertions) { unreachable!(); } @@ -382,16 +379,16 @@ fn print_line_from_file_any_os( out_stream: &mut Vec, source_location: &SourceLocation, ) -> Result<(), Error> { - // TODO(port): narrow error set if !cfg!(debug_assertions) { unreachable!(); } // Need this to always block even in async I/O mode, because this could potentially // be called from e.g. the event loop code crashing. - // TODO(port): Zig used std.fs.cwd().openFile directly (bypassing bun.sys). PORTING.md - // forbids std::fs; using bun_sys here. Confirm bun_sys::File is safe to call - // from inside a crash handler / lldb (must not re-enter event loop). + // Zig used std.fs.cwd().openFile directly (bypassing bun.sys); PORTING.md + // forbids std::fs, so bun_sys is used here. `bun_sys::File::open_at`/`read` + // are direct openat(2)/read(2) wrappers with no event-loop involvement, so + // they are safe to call from inside a crash handler / lldb. let f = bun_sys::File::open_at( bun_sys::Fd::cwd(), &source_location.file_name, diff --git a/src/jsc/bun_string_jsc.rs b/src/jsc/bun_string_jsc.rs index 8cf0cfe6ecfb..d1ea25c082fc 100644 --- a/src/jsc/bun_string_jsc.rs +++ b/src/jsc/bun_string_jsc.rs @@ -94,11 +94,8 @@ pub fn to_js(this: &String, global_object: &JSGlobalObject) -> JsResult /// `simulateThrow()` leaves `m_needExceptionCheck` set and the caller's /// `to_js_host_call` scope dtor asserts "unchecked exception". /// -/// PORT NOTE: Zig's `toJSDOMURL` returns bare `JSValue` (no `JSError!`), which -/// is a latent spec gap — it relies on the generated `toJSHostCall` thunk's -/// `assertExceptionPresenceMatches(normal == .zero)` to satisfy the check. The -/// Rust port routes the FFI through `from_js_host_call` so the exception is -/// observed at the call site and surfaced as `Err(JsError::Thrown)`. +/// Routing the FFI through `from_js_host_call` observes the exception at the +/// call site and surfaces it as `Err(JsError::Thrown)`. #[track_caller] pub fn to_jsdomurl(this: &mut String, global_object: &JSGlobalObject) -> JsResult { crate::from_js_host_call(global_object, || BunString__toJSDOMURL(global_object, this)) @@ -169,16 +166,15 @@ fn slice_with_underlying_string_to_js_with_options( debug_assert!(!this.utf8.is_wtf_allocated()); } - // PORT NOTE: Zig checked `utf8.allocator.get()` for "owns an - // allocator". The Rust `ZigStringSlice` enum encodes ownership in the - // variant: `Owned`/`WTF` ⇒ allocated, `Static` ⇒ borrowed. + // `ZigStringSlice` encodes ownership in the variant: + // `Owned`/`WTF` ⇒ allocated, `Static` ⇒ borrowed. if this.utf8.is_allocated() { if let Some(utf16) = strings::to_utf16_alloc(this.utf8.slice(), false, false).unwrap_or(None) { // Drop the now-unused utf8 allocation (Zig: `this.utf8.deinit()`). this.utf8 = ZigStringSlice::default(); - // PORT NOTE: ownership of `utf16` is transferred to JSC as an + // Ownership of `utf16` is transferred to JSC as an // external string; do not drop it here. let mut utf16 = core::mem::ManuallyDrop::new(utf16); utf16.shrink_to_fit(); @@ -188,7 +184,7 @@ fn slice_with_underlying_string_to_js_with_options( zig_string::to_external_u16(utf16.as_ptr(), utf16.len(), global_object) }); } else if let Some((ptr, len)) = this.utf8.take_owned_raw() { - // PORT NOTE: ownership of utf8 bytes transferred to JSC via + // Ownership of the utf8 bytes is transferred to JSC via // `to_external_value`; `take_owned_raw` already cleared `utf8` // and leaked the buffer (mimalloc-freed by JSC). let zig = ZigString::from_bytes( @@ -305,9 +301,8 @@ pub mod unicode_testing_apis { } }; - // PORT NOTE: Rust's `to_utf16_alloc_for_real(.., sentinel=true)` includes - // the trailing NUL **in** `result.len()` (Zig's `[:0]u16` kept it past-the-end), - // so slice it off before handing to JSC. + // `to_utf16_alloc_for_real(.., sentinel=true)` includes the trailing + // NUL **in** `result.len()`, so slice it off before handing to JSC. debug_assert_eq!(result.last().copied(), Some(0)); let out = String::clone_utf16(&result[..result.len() - 1]); diff --git a/src/jsc/codegen.rs b/src/jsc/codegen.rs index 0f06ea8810b6..0b3acd444877 100644 --- a/src/jsc/codegen.rs +++ b/src/jsc/codegen.rs @@ -15,8 +15,8 @@ pub(crate) type CallbackSetterFn = extern "C" fn(JSValue, JSValue); pub struct CallbackWrapper { get_fn: CallbackGetterFn, set_fn: CallbackSetterFn, - // PORT NOTE: bare JSValue field is sound here — this wrapper is a by-value stack helper - // (Zig methods take `self: @This()`, not `*@This()`); never heap-stored. + // Bare JSValue field is sound here — this wrapper is a by-value stack + // helper (methods take `self` by value); never heap-stored. pub container: JSValue, } @@ -54,9 +54,9 @@ impl CallbackWrapper { global_object: &JSGlobalObject, args: &[JSValue], ) -> JsResult> { - // PORT NOTE: codegen.zig's `callback.call(globalObject, args)` predates the - // 3-arg JSValue.call signature and is dead Zig (never instantiated). The intent - // is `callWithGlobalThis`; the JS exception is propagated rather than swallowed. + // codegen.zig's `callback.call(globalObject, args)` is dead Zig (never + // instantiated, predates the 3-arg JSValue.call signature); `call_with_global_this` + // is the intended semantics. The JS exception is propagated rather than swallowed. if let Some(callback) = self.get() { return Ok(Some(callback.call_with_global_this(global_object, args)?)); } diff --git a/src/jsc/comptime_string_map_jsc.rs b/src/jsc/comptime_string_map_jsc.rs index 465d47d43e9e..28d5c90e9d5e 100644 --- a/src/jsc/comptime_string_map_jsc.rs +++ b/src/jsc/comptime_string_map_jsc.rs @@ -3,16 +3,10 @@ use crate::{JSGlobalObject, JSValue, JsResult}; use bun_core::{OwnedString, String as BunString, Tag}; -// PORT NOTE: `from_js` on `bun_core::String` is provided by the `StringJsc` -// extension trait, which is allowed here because this file lives in `src/jsc/`. +// `from_js` on `bun_core::String` is provided by the `StringJsc` extension +// trait, which is allowed here because this file lives in `src/jsc/`. use crate::StringJsc as _; -// PORT NOTE: reshaped for borrowck / Rust generics. Zig took `comptime Map: type` -// (the `ComptimeStringMap(V, ...)` instantiation, a namespace with static -// lookup decls). The Rust port of `ComptimeStringMap` is a `phf::Map` *value* -// (see PORTING.md §Collections), so callers pass a `&'static phf::Map` instead -// of a type parameter, and `Map.Value` becomes the generic `V`. - /// `map` is the `phf::Map<&'static [u8], V>` instance (Rust port of /// `ComptimeStringMap(V, ...)`); `V` is the value type. pub fn from_js( diff --git a/src/jsc/event_loop.rs b/src/jsc/event_loop.rs index b19b78e84654..aae597ca9c8f 100644 --- a/src/jsc/event_loop.rs +++ b/src/jsc/event_loop.rs @@ -63,7 +63,7 @@ pub struct EventLoop { /// /// Having two queues avoids infinite loops creating by calling `setImmediate` in a `setImmediate` callback. /// - /// PORT NOTE (§Dispatch): payload is `*mut ()` — the real + /// Note (§Dispatch): payload is `*mut ()` — the real /// `bun_runtime::timer::ImmediateObject` lives in the higher-tier crate /// (cycle). Low tier stores the erased pointer; the high-tier hook /// (link-time `__bun_run_immediate_task`) casts it back. @@ -71,16 +71,16 @@ pub struct EventLoop { pub next_immediate_tasks: Vec<*mut ()>, pub concurrent_tasks: ConcurrentQueue, - // TODO(port): lifetime — *JSGlobalObject backref owned by VM + // BACKREF — `*JSGlobalObject` owned by the VM; outlives this EventLoop. pub global: Option>, - // TODO(port): lifetime — *VirtualMachine backref (EventLoop is a field of VirtualMachine) + // BACKREF — owning `*VirtualMachine` (EventLoop is a value field of it). pub virtual_machine: Option>, pub waker: Option, - // TODO(port): lifetime — ?*uws.Timer FFI handle + // `?*uws.Timer` FFI handle. pub forever_timer: Option>, pub deferred_tasks: DeferredTaskQueue::DeferredTaskQueue, #[cfg(windows)] - // TODO(port): lifetime — ?*uws.Loop FFI handle + // `?*uws.Loop` FFI handle. pub uws_loop: Option>, #[cfg(not(windows))] pub uws_loop: (), @@ -90,7 +90,7 @@ pub struct EventLoop { pub concurrent_ref: AtomicI32, /// `std.atomic.Value(?*Timer.WTFTimer)` — atomic nullable pointer. /// - /// PORT NOTE (§Dispatch): payload is `*mut ()` — the real + /// Note (§Dispatch): payload is `*mut ()` — the real /// `bun_runtime::timer::WTFTimer` lives in the higher-tier crate (cycle). /// Low tier stores the erased pointer; the high-tier hook installed via /// (link-time `__bun_run_wtf_timer`) casts it back. @@ -216,8 +216,6 @@ mod drain_result { pub(super) const JS_TERMINATED: u8 = 1; } -// TODO(port): move to jsc_sys -// // `JSGlobalObject` is an opaque `UnsafeCell`-backed ZST handle; C++ mutating // the microtask queue through it is interior mutation invisible to Rust. unsafe extern "C" { @@ -335,7 +333,7 @@ impl EventLoop { self.entered_event_loop_count -= 1; self.debug.exit(); - // PORT NOTE: reshaped for borrowck — Zig `defer this.debug.exit()` moved to tail; no early returns + // Note: reshaped for borrowck — Zig `defer this.debug.exit()` moved to tail; no early returns } /// `enter()` now, `exit()` on drop. Takes the raw VM-owned pointer so the @@ -365,7 +363,7 @@ impl EventLoop { Ok(()) }; - // PORT NOTE: spec event_loop.zig:92-103 uses `try drainMicrotasksWithGlobal(...)` + // Note: spec event_loop.zig:92-103 uses `try drainMicrotasksWithGlobal(...)` // which returns BEFORE reaching `entered_event_loop_count -= 1`; only // `defer this.debug.exit()` runs on the error path. Mirror that here. if result.is_ok() { @@ -422,7 +420,7 @@ impl EventLoop { self.deferred_tasks.run(); vm.is_inside_deferred_task_queue.set(false); - // PORT NOTE: spec event_loop.zig:144-146 guards on `event_loop_handle != null` + // Note: spec event_loop.zig:144-146 guards on `event_loop_handle != null` // but then calls `this.virtual_machine.uwsLoop().drainQuicIfNecessary()`. // On Windows `uwsLoop()` returns `uws.Loop.get()` (NOT `event_loop_handle`, // which is the libuv loop). Mirror that here. @@ -489,7 +487,7 @@ impl EventLoop { let this: *mut Self = core::hint::black_box(this); // SAFETY: as above. unsafe { (*this).exit() }; - // PORT NOTE: reshaped for borrowck — `defer this.exit()` moved to tail; no early returns + // Note: reshaped for borrowck — `defer this.exit()` moved to tail; no early returns } pub fn run_callback_with_result( @@ -513,7 +511,7 @@ impl EventLoop { let this: *mut Self = core::hint::black_box(this); // SAFETY: as above. unsafe { (*this).exit() }; - // PORT NOTE: reshaped for borrowck — `defer this.exit()` moved to tail + // Note: reshaped for borrowck — `defer this.exit()` moved to tail result } @@ -571,7 +569,7 @@ impl EventLoop { let mut iter = concurrent.iterator(); let start_count = self.tasks.readable_length(); - // PORT NOTE: Zig resets `head = 0` when empty as a micro-opt; LinearFifo + // Note: Zig resets `head = 0` when empty as a micro-opt; LinearFifo // realigns internally on grow, so this is folded into `ensure_unused_capacity`. let _ = self.tasks.ensure_unused_capacity(count); @@ -610,7 +608,7 @@ impl EventLoop { } fn update_counts(&mut self) { - // PORT NOTE: spec event_loop.zig:283-284 unwraps `event_loop_handle.?` + // Note: spec event_loop.zig:283-284 unwraps `event_loop_handle.?` // (panic). Do NOT silently drop the swapped delta when the handle is // missing — refs queued via `ref_concurrently()` would be lost forever. let delta = self.concurrent_ref.swap(0, Ordering::SeqCst); @@ -686,7 +684,7 @@ impl EventLoop { crate::top_scope!(scope, self.global_ref()); self.entered_event_loop_count += 1; self.debug.enter(); - // PORT NOTE: reshaped for borrowck — Zig + // Note: reshaped for borrowck — Zig // `defer scope.deinit(); defer { entered_event_loop_count -= 1; debug.exit() }` // is inlined at each return site below (a scopeguard closure would // alias `&mut self`). @@ -695,7 +693,7 @@ impl EventLoop { self.tick_concurrent(); self.process_gc_timer(); - // PORT NOTE: reshaped for borrowck — `vm_ref()` is `&'static`, so the + // Note: reshaped for borrowck — `vm_ref()` is `&'static`, so the // global borrow detaches from `&self` and survives the `&mut self` call. let global = self.vm_ref().global(); let global_vm = self.vm_ref().jsc_vm(); @@ -747,7 +745,7 @@ impl EventLoop { } self.vm_ref().suppress_microtask_drain.set(prev); - // PORT NOTE: reshaped for borrowck — `defer vm.suppress_microtask_drain = prev` moved to tail + // Note: reshaped for borrowck — `defer vm.suppress_microtask_drain = prev` moved to tail } pub fn enqueue_task(&mut self, task: Task) { @@ -858,7 +856,7 @@ impl EventLoop { requeue.push(task); } } - // PORT NOTE: Zig's `tasks.deinit()` / `clearAndFree()` map to dropping + // Note: Zig's `tasks.deinit()` / `clearAndFree()` map to dropping // the owned buffers; reassigning a fresh value drops the old in place. self.tasks = Queue::init(); for task in requeue { @@ -875,7 +873,7 @@ impl EventLoop { } } - /// PORT NOTE (§Dispatch): `task` is an erased + /// Note (§Dispatch): `task` is an erased /// `*mut bun_runtime::timer::ImmediateObject` — see [`RunImmediateFn`]. pub fn enqueue_immediate_task(&mut self, task: *mut ()) { self.immediate_tasks.push(task); @@ -885,7 +883,7 @@ impl EventLoop { /// immediate queues, drains the now-current batch, then recycles the /// drained Vec as the next-tick buffer. /// - /// PORT NOTE: the real `ImmediateObject` lives in `bun_runtime` (cycle), so + /// Note: the real `ImmediateObject` lives in `bun_runtime` (cycle), so /// the per-task body dispatches through `__bun_run_immediate_task` (link- /// time, definer in `bun_runtime`). The swap always happens — this is /// load-bearing for `auto_tick`'s `has_pending_immediate` read, which must @@ -962,7 +960,7 @@ impl EventLoop { let vm = self.vm(); // SAFETY: `vm` is the live owning VM. unsafe { (*vm).event_loop_handle = Some(Async::Loop::get()) }; - // PORT NOTE: reshaped for borrowck — Zig passes `vm.gc_controller` and + // Note: reshaped for borrowck — Zig passes `vm.gc_controller` and // `vm` simultaneously; route through raw addr_of to avoid stacked-borrow // aliasing of the embedded field with its parent. // SAFETY: `vm` is the live owning VM; gc_controller is embedded. @@ -978,7 +976,7 @@ impl EventLoop { self.uws_loop = NonNull::new(uws::Loop::get()); } } - // PORT NOTE: `EventLoopHandle` lives in `bun_event_loop` (lower tier), + // Note: `EventLoopHandle` lives in `bun_event_loop` (lower tier), // which cannot name `jsc::EventLoop`, so it stores `*mut ()`. The // typed `set_parent_event_loop` extension trait in `bun_uws` expects // a `ParentEventLoopHandle` impl, but `EventLoopHandle` already @@ -1081,11 +1079,9 @@ impl EventLoop { } // ──────────── private helpers (port-only; not in Zig) ──────────── - // TODO(port): lifetime — these unwrap NonNull backrefs; replace with - // proper borrow plumbing. // - // PORT NOTE: returns a raw pointer, NOT `&mut VirtualMachine`. `EventLoop` - // is a value field of `VirtualMachine`, so materializing `&mut VM` while a + // `vm()` returns a raw pointer, NOT `&mut VirtualMachine`. `EventLoop` is a + // value field of `VirtualMachine`, so materializing `&mut VM` while a // `&EventLoop`/`&mut EventLoop` is live would alias (PORTING.md §Forbidden). // Callers must dereference per-field at use sites. #[inline(always)] diff --git a/src/jsc/generated.rs b/src/jsc/generated.rs index f5b75aa1b12c..326e4e9aaabd 100644 --- a/src/jsc/generated.rs +++ b/src/jsc/generated.rs @@ -90,10 +90,11 @@ pub mod bun_object { // already provides. pub type GenString = bun_core::String; -/// `bun.bun_js.jsc.JSCArrayBuffer.Ref` — adopted `*mut JSC::ArrayBuffer` (refcount -/// already +1 from C++); deref via `JSC__ArrayBuffer__deref` on drop. -// TODO(port): wrap in `bun_ptr::ExternalShared` once that crate -// exposes `adopt(*mut T)`. Raw ptr for now (leaks on drop). +/// `bun.bun_js.jsc.JSCArrayBuffer.Ref` — adopted `*mut JSC::ArrayBuffer` +/// (refcount already +1 from C++). Stays a raw `Copy` pointer because it is +/// also embedded in the `#[repr(C)]` extern unions below, which require POD +/// arms; the +1 ref is released exactly once via +/// `release_gen_val_array_buffer` in the owning containers' `Drop` impls. pub type GenArrayBuffer = *mut JSCArrayBuffer; /// `bun.bun_js.webcore.Blob.Ref` — adopted `*mut Blob` (the codegen `m_ctx` @@ -482,10 +483,9 @@ impl SSLConfigFile { let elem = unsafe { *arr.data.add(i) }; out.push(SSLConfigSingleFile::convert_from_extern(elem)); } - // PORT NOTE: Zig `BindgenArray` reuses the allocation in-place - // when `ZigType == ExternType`. This path copies-then-frees the - // source buffer; in-place reuse deferred. - // PERF(port): was BindgenArray in-place convert — profile if it shows up on a hot path. + // Zig `BindgenArray` reuses the allocation in-place when + // `ZigType == ExternType`. This path copies-then-frees the + // source buffer instead; profile if it shows up on a hot path. // SAFETY: `arr.data` was allocated by `WTF::fastMalloc` ≡ mimalloc // (per crate prereq); `mi_free` is size-agnostic. unsafe { bun_alloc::basic::free_without_size(arr.data.cast()) }; diff --git a/src/jsc/host_fn.rs b/src/jsc/host_fn.rs index 57d90c6b6ac7..69efcbce55f1 100644 --- a/src/jsc/host_fn.rs +++ b/src/jsc/host_fn.rs @@ -9,7 +9,8 @@ //! - the runtime result-mapping helpers the macros call into, //! - the FFI surface for `JSFunction` creation, //! - `DomEffect` (plain data), -//! and stubs the reflection-driven generators with `// TODO(port): proc-macro`. +//! and stubs the reflection-driven generators (callers hand-write the +//! equivalent decode/dispatch glue until the proc-macros grow those modes). use core::ffi::c_void; @@ -737,8 +738,9 @@ pub fn from_js_host_call_generic( // from_js_host_call_generic is only for functions where the return value tells you nothing // about whether an exception was thrown. // - // TODO(port): static-assert `R != JSValue` (Zig used @compileError; Rust needs a - // negative trait bound or specialization — neither stable). A sealed-trait trick could enforce this. + // Zig statically rejected `R == JSValue` via @compileError; Rust would need a + // negative trait bound or specialization (neither stable), so this is enforced + // by convention only. crate::call_check_slow(global_this, f) } @@ -762,23 +764,17 @@ pub fn void_from_js_error(err: JsError, global_this: &JSGlobalObject) { // // Zig `wrap1`..`wrap5` / `wrap4v` each take a `comptime func: anytype`, reflect on // its parameter list with `@typeInfo`, and return a fresh `extern fn` of matching -// arity that forwards through `toJSHostCall`. This is signature reflection — -// `// TODO(port): proc-macro`. The Rust replacement is a single attribute: -// -// #[bun_jsc::host_call(wrap)] // -> extern "C" (wrap1..wrap5) -// #[bun_jsc::host_call(wrap, sysv)] // -> jsc.conv ABI (wrap4v) -// -// `checkWrapParams` (arity + first-arg-is-*JSGlobalObject assertion) is enforced by -// the macro at expansion time. -// TODO(port): proc-macro — `wrap1`..`wrap5`, `wrap4v`, `checkWrapParams`. +// arity that forwards through `toJSHostCall`. Rust has no equivalent signature +// reflection and no Rust call site uses a `wrapN`: each site hand-writes its +// `extern` shim, decoding arguments explicitly and forwarding through +// `to_js_host_call`. `checkWrapParams` (arity + first-arg-is-*JSGlobalObject) +// is simply the shim's own signature. // ───────────────────────────── FFI: JSFunction creation ────────────────────────────── mod private { use super::*; - // TODO(port): move to jsc_sys - // // safe fn: `JSGlobalObject` is an opaque `UnsafeCell`-backed ZST handle (`&` // is ABI-identical to non-null `*mut`); `Option<&ZigString>` is ABI-identical // to a nullable `*const ZigString` via the guaranteed null-pointer @@ -973,9 +969,8 @@ pub enum DomEffectId { // // `DOMCallArgumentType` / `DOMCallArgumentTypeWrapper` / `DOMCallResultType` map a // Zig type to a C++ spec-string at comptime. They feed the C++ codegen -// (`generate-classes.ts`), not runtime. The proc-macro for `#[bun_jsc::dom_call]` -// owns this mapping in Rust. -// TODO(port): proc-macro — DOMCall type→spec-string tables move into the macro crate. +// (`generate-classes.ts`), not runtime — there is nothing for the Rust side to +// hold; the spec-strings stay in the codegen script. // Zig: `pub fn DOMCall(comptime class_name, comptime Container, comptime functionName, // comptime dom_effect) type` @@ -985,12 +980,10 @@ pub enum DomEffectId { // - `@extern`s `____put`, // - exposes `effect`, `put()`, and `Arguments`. // -// This is link-name synthesis + signature reflection. Rust replacement: -// -// #[bun_jsc::dom_call(class = "Foo", effect = DomEffect::PURE)] -// impl Foo { fn bar(...) -> ... { ... } fn bar_without_type_checks(...) -> ... { ... } } -// -// TODO(port): proc-macro — `DOMCall` type-generator. +// This is link-name synthesis + signature reflection with no Rust equivalent; +// the hand-written [`DomCall`] descriptor below is the Rust mechanism — call +// sites write the slow/fast paths and the `____put` extern +// themselves. /// Runtime descriptor for a `DOMCall(...)`-generated DOMJIT entry. /// @@ -1022,10 +1015,9 @@ pub type InstanceMethodType = fn(&mut C, &JSGlobalObject, &CallFrame) -> JsRe // `?jsc.ArrayBuffer`, `*WebCore.Response`, `?HTMLRewriter.ContentOptions`, ...) and // emits per-param argument-decoding + error-throwing glue, then `@call`s the target. // There is no value-level translation; the entire body is a type-directed code -// generator. Per PORTING.md §"Comptime reflection": -// -// TODO(port): proc-macro — `#[bun_jsc::host_fn(method, auto_protect)]` replaces -// `wrapInstanceMethod`. The macro must reproduce the per-type decode table: +// generator with no Rust equivalent (PORTING.md §"Comptime reflection"), so each +// Rust call site hand-writes its own argument decoding. For reference, the Zig +// per-type decode table was: // *Container -> `this` // *JSGlobalObject -> `global` // *CallFrame -> `frame` @@ -1047,8 +1039,7 @@ pub type InstanceMethodType = fn(&mut C, &JSGlobalObject, &CallFrame) -> JsRe // jsc.JSHostFnZig` // // Same as `wrapInstanceMethod` minus the `*Container`/`*CallFrame`/`ExceptionRef` -// arms, plus a `Node.BlobOrStringOrBuffer` arm. -// TODO(port): proc-macro — `#[bun_jsc::host_fn(static, auto_protect)]` replaces -// `wrapStaticMethod` (decode table as above + BlobOrStringOrBuffer). +// arms, plus a `Node.BlobOrStringOrBuffer` arm. As above, Rust call sites +// hand-write the equivalent decoding. // ported from: src/jsc/host_fn.zig diff --git a/src/jsc/hot_reloader.rs b/src/jsc/hot_reloader.rs index 69d98e141115..bf6db54f9777 100644 --- a/src/jsc/hot_reloader.rs +++ b/src/jsc/hot_reloader.rs @@ -30,6 +30,12 @@ pub enum ImportWatcher { Watch(Box), } +// Drift guard for the bun_watcher CYCLEBREAK `Loader` newtype: its `File` +// constant must mirror `bun_ast::Loader::File` (Zig `options.Loader.file`) — +// the watcher stores that value for auto-watched directories. This crate sees +// both types, so the compile-time check lives here. +const _: () = assert!(bun_watcher::Loader::File.0 == bun_ast::Loader::File as u8); + impl ImportWatcher { pub fn start(&mut self) -> Result<(), bun_core::Error> { match self { @@ -102,7 +108,7 @@ impl ImportWatcher { #[inline] pub fn add_file_by_path_slow(&mut self, file_path: &[u8], loader: bun_ast::Loader) -> bool { - // PORT NOTE: bun_watcher::Loader is an opaque newtype over u8; + // Note: bun_watcher::Loader is an opaque newtype over u8; // wrap the bun_ast::Loader discriminant. match self { ImportWatcher::Hot(w) | ImportWatcher::Watch(w) => { @@ -120,7 +126,7 @@ impl ImportWatcher { hash: bun_watcher::HashType, loader: bun_ast::Loader, dir_fd: Fd, - // PORT NOTE: bun_watcher::PackageJSON is an opaque forward-decl; + // Note: bun_watcher::PackageJSON is an opaque forward-decl; // callers cast from `&bun_resolver::PackageJSON`. package_json: Option<&'static bun_watcher::PackageJSON>, ) -> bun_sys::Result<()> { @@ -154,9 +160,9 @@ impl HotReloaderCtx for VirtualMachine { } fn bun_watcher_mut(&mut self) -> &mut Watcher { - // PORT NOTE: Zig's three-way `@TypeOf(this.ctx.bun_watcher)` reflection - // collapses here — `VirtualMachine.bun_watcher` is the type-erased - // `*mut ImportWatcher` (TODO(b2-cycle) field comment in + // Zig's three-way `@TypeOf(this.ctx.bun_watcher)` reflection + // collapses here — `VirtualMachine.bun_watcher` is the + // `*mut ImportWatcher` (see the field comment in // VirtualMachine.rs), and `getContext` only runs after // `enable_hot_module_reloading` has populated it, so the `.None` arm // is unreachable. @@ -195,8 +201,8 @@ impl HotReloaderCtx for VirtualMachine { } fn is_watcher_enabled(&self) -> bool { - // Zig: `this.bun_watcher != .none`. The field is stored type-erased - // (`*mut c_void` → `*mut ImportWatcher`); a null pointer means `.none`. + // Zig: `this.bun_watcher != .none`. The field is a typed + // `*mut ImportWatcher`; a null pointer means `.none`. if self.bun_watcher.is_null() { return false; } @@ -228,8 +234,7 @@ impl HotReloaderCtx for VirtualMachine { ImportWatcher::Hot(w) | ImportWatcher::Watch(w) => &raw mut **w, ImportWatcher::None => unreachable!(), }; - // The VM holds `bun_watcher` type-erased as `*mut c_void` (b2-cycle). - self.bun_watcher = bun_core::heap::into_raw(iw).cast::(); + self.bun_watcher = bun_core::heap::into_raw(iw); // Wire the resolver's directory-watch callback at the same time. // Zig: `ResolveWatcher(*Watcher, Watcher.onMaybeWatchDirectory).init(w)`; @@ -483,15 +488,15 @@ fn flush_changed_paths_for_reload() { } } -// TODO(port): move to _sys unsafe extern "C" { safe fn BunDebugger__willHotReload(); } -// TODO(port): in Zig this was a `pub var` inside the generic struct, giving one -// static per monomorphization. Rust can't put a static in a generic impl; both -// HotReloader and WatchReloader now share this. Revisit if the per-type split -// was load-bearing. +// In Zig this was a `pub var` inside the generic struct, giving one static per +// monomorphization. Rust can't put a static in a generic impl, so HotReloader +// and WatchReloader share this. That is equivalent in practice: the flag is +// derived from the same CLI clear-screen setting and written only during +// single-threaded reloader init, before the watcher thread starts. static CLEAR_SCREEN: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false); pub struct NewHotReloader { @@ -511,7 +516,9 @@ pub struct NewHotReloader { pub struct MainFile { /// Includes a trailing "/" - // TODO(port): lifetime — borrows from `entry_path` (owned by Ctx, outlives Reloader) + // `'static` is the API contract of `enable_hot_module_reloading` + // (`entry_path: Option<&'static [u8]>`): the entry path is owned by the + // Ctx, which outlives the leaked Reloader for the process lifetime. pub dir: &'static [u8], pub dir_hash: bun_watcher::HashType, @@ -570,10 +577,10 @@ impl MainFile { pub struct Task { pub count: u8, pub hashes: [u32; 8], - // TODO(port): in Zig this field's type is `[8][]const u8` only when + // In Zig this field's type is `[8][]const u8` only when // `Ctx == bun.bake.DevServer`, else `void`. Rust can't branch a field - // type on a generic parameter without specialization; storing it - // unconditionally for now. + // type on a generic parameter without specialization, so it is stored + // unconditionally (8 fat pointers of overhead for non-DevServer Ctx). pub paths: [&'static [u8]; 8], /// Left `None` until [`Self::enqueue`] populates it on the heap copy. pub concurrent_task: Option, @@ -592,7 +599,8 @@ where Self { reloader, hashes: [0u32; 8], - // TODO(port): was `if (Ctx == bun.bake.DevServer) [_][]const u8{&.{}} ** 8` + // Zig: `if (Ctx == bun.bake.DevServer) [_][]const u8{&.{}} ** 8` — + // see the `paths` field comment for why this is unconditional. paths: [b"".as_slice(); 8], count: 0, concurrent_task: None, @@ -687,7 +695,7 @@ where // BundleV2, never ImportWatcher itself), so the call is dead code in // the spec. Match spec literally: no-op for every Ctx. // - // PORT NOTE: this is almost certainly a Zig typo — the intent was + // Note: this is almost certainly a Zig typo — the intent was // likely `@TypeOf(ctx.bun_watcher) == ImportWatcher`, which would // close listen sockets before exec()-restarting under --watch on a // VirtualMachine. But fixing that here would diverge from observable @@ -712,7 +720,7 @@ where })); // SAFETY: `that` was just allocated above and is exclusively owned here. unsafe { - // PORT NOTE: `JscTask::init` requires `Taskable`, but const-generic + // Note: `JscTask::init` requires `Taskable`, but const-generic // `Task` can't implement it (one tag per monomorphization). // The Zig source tagged the concrete `HotReloader.HotReloadTask` — // use the raw `(tag, ptr)` constructor. @@ -720,8 +728,9 @@ where task: JscTask::new(task_tag::HotReloadTask, that.cast::<()>()), ..Default::default() }); - // TODO(port): `&that.concurrent_task` is an interior pointer into a - // Box-allocated Task; event loop must not outlive `that`. Matches Zig. + // `&that.concurrent_task` is an interior pointer into a + // Box-allocated Task; the event loop must not outlive `that` + // (matches the Zig original's layout and lifetime contract). // // Inlines `NewHotReloader::enqueue_task_concurrent` to avoid forming // a whole-struct `&NewHotReloader` (see `Self::pending_count` doc). @@ -770,8 +779,7 @@ where let mut watcher = match Watcher::init(reloader, fs.top_level_dir) { Ok(w) => w, Err(err) => { - // TODO(port): bun.handleErrorReturnTrace — debug-only diagnostics; no Rust equivalent yet - let _ = &err; + bun_core::handle_error_return_trace(&err); Output::panic(format_args!( "Failed to enable File Watcher: {}", err.name() @@ -779,8 +787,7 @@ where } }; if let Err(err) = watcher.start() { - // TODO(port): bun.handleErrorReturnTrace — debug-only diagnostics; no Rust equivalent yet - let _ = &err; + bun_core::handle_error_return_trace(&err); Output::panic(format_args!("Failed to start File Watcher: {}", err.name())); } watcher @@ -838,7 +845,7 @@ where let watcher = match Watcher::init(reloader, ctx.watcher_top_level_dir()) { Ok(w) => w, Err(err) => { - // TODO(port): bun.handleErrorReturnTrace — debug-only diagnostics; no Rust equivalent yet + bun_core::handle_error_return_trace(&err); Output::panic(format_args!( "Failed to enable File Watcher: {}", err.name() @@ -896,7 +903,7 @@ where } pub fn get_context(&mut self) -> &mut Watcher { - // PORT NOTE: Zig branched three ways on `@TypeOf(this.ctx.bun_watcher)` + // Note: Zig branched three ways on `@TypeOf(this.ctx.bun_watcher)` // (ImportWatcher / Option / bare). Folded into `HotReloaderCtx::bun_watcher_mut`; // each impl picks the right unwrap. self.ctx_mut().bun_watcher_mut() @@ -911,7 +918,7 @@ where ) { let slice = watchlist.slice(); let file_paths = slice.items_file_path(); - // PORT NOTE: `WatchItemColumns` doesn't expose a `count` accessor; reach + // Note: `WatchItemColumns` doesn't expose a `count` accessor; reach // through the generic SoA column directly. Zig mutates this in place // (`counts[index] = update_count`) — build the &mut from the raw column // pointer rather than ref-casting `&[u32]` (which is UB). @@ -924,7 +931,7 @@ where let hashes = slice.items_hash(); let parents = slice.items_parent_hash(); let file_descriptors = slice.items_fd(); - // PORT NOTE: reshaped for borrowck — `ctx` is held as a raw pointer so + // Note: reshaped for borrowck — `ctx` is held as a raw pointer so // `self` can be reborrowed inside the loop body for tombstone access, // and so the deferred `flush_evictions` doesn't hold `&mut Watcher` // across the loop. @@ -933,7 +940,7 @@ where // so any exit path (including future early-returns) flushes the buffered // hashes. Dereferenced as `&mut *current_task` for the loop body below. // - // PORT NOTE: declared *before* `_flush` (inverting the Zig defer order) + // Note: declared *before* `_flush` (inverting the Zig defer order) // so `flush_evictions()` runs **before** `enqueue()` on drop. The Zig // order (`enqueue` → `Output.flush` → `flushEvictions`) opens a window // where the JS thread can pick up the concurrent task, look the file up @@ -946,7 +953,7 @@ where Task::::init_empty(self), |mut t| t.enqueue(), ); - // `defer ctx.flushEvictions(); defer Output.flush();` — see PORT NOTE + // `defer ctx.flushEvictions(); defer Output.flush();` — see the note // above for why this drops *before* `current_task`. let _flush = scopeguard::guard(ctx, |ctx| { Output::flush(); @@ -988,7 +995,7 @@ where if self.verbose { Self::debug(format_args!( "File changed: {}", - // PORT NOTE: `fs.relative_to(file_path)` would borrow `&*fs` + // Note: `fs.relative_to(file_path)` would borrow `&*fs` // while `rfs = &mut fs.fs` is live; inline the body so the // split-borrow on `fs.top_level_dir` is visible to borrowck. bstr::BStr::new(bun_paths::resolve_path::relative( @@ -1041,7 +1048,7 @@ where let mut affected_buf: [&[u8]; 128] = [b"".as_slice(); 128]; let mut entries_option: Option<*mut Fs::EntriesOption> = None; - // PORT NOTE: the Zig labeled block produced a slice whose + // Note: the Zig labeled block produced a slice whose // element type differs by platform (`[]const u8` on kqueue, // `?[:0]u8` on inotify). Split into two locals; only one is // populated per cfg. @@ -1237,7 +1244,7 @@ where .get(PathName::find_extname(changed_name)) .copied() .unwrap_or(bun_ast::Loader::File); - // PORT NOTE: Zig declares `prev_entry_id` per-iteration and + // Note: Zig declares `prev_entry_id` per-iteration and // reassigns it just before `break`; the write is dead there // too (hot_reloader.zig:535/563). Keep the shape for // fidelity; the post-assignment `_ = prev_entry_id` below @@ -1298,12 +1305,16 @@ where _on_file_update_path_buf [file_path_without_trailing_slash.len()] = SEP; - // PORT NOTE: Zig copies `changed_name` starting at - // index `len` (overlapping the just-written SEP) - // and then slices `len + changed_name.len + 1` - // bytes — this includes one byte past the copy. - // Ported verbatim. - // TODO(port): verify intended off-by-one in Zig source + // Zig (hot_reloader.zig:606-608) writes the + // separator at index `len`, then memcpys + // `changed_name` starting at the same index `len` + // (overwriting the separator) and slices + // `len + changed_name.len + 1` bytes — one stale + // byte past the copy. Verified against the Zig + // source: the overlap/extra byte is exactly what + // Zig does (likely the copy was meant to start at + // `len + 1`). Ported verbatim so the resulting + // path hash matches Zig; fix upstream first. _on_file_update_path_buf [file_path_without_trailing_slash.len() ..file_path_without_trailing_slash.len() @@ -1354,7 +1365,7 @@ where // Drop order (LIFO): `_flush` guard → Output::flush() + // ctx.flush_evictions(), then `current_task` guard → enqueue(). See - // PORT NOTE on `current_task` above for why this inverts the Zig + // the note on `current_task` above for why this inverts the Zig // defer order. } } diff --git a/src/jsc/ipc.rs b/src/jsc/ipc.rs index 8bdcb2522466..5b3b740aabcb 100644 --- a/src/jsc/ipc.rs +++ b/src/jsc/ipc.rs @@ -48,7 +48,7 @@ use bun_uws; /// to describe different events it performs. It will send a message with an incrementing sequence number and then call a callback /// when a message is received with an 'ack' property of the same sequence number. /// -/// PORT NOTE: moved down from `bun_runtime::node::node_cluster_binding` (cycle-break per +/// Note: moved down from `bun_runtime::node::node_cluster_binding` (cycle-break per /// docs/PORTING.md) — `SendQueue` stores one inline so the struct must live at this tier. /// All field accesses + dispatch methods need only `bun_jsc`/`bun_collections` symbols. pub struct InternalMsgHolder { @@ -103,7 +103,7 @@ impl InternalMsgHolder { if let Some(p) = message.get(global, "ack")? { if !p.is_undefined() { let ack = p.to_int32(); - // PORT NOTE: reshaped for borrowck — Zig copied the Strong out of the + // Note: reshaped for borrowck — Zig copied the Strong out of the // entry, then conditionally deinit+swapRemove. Here we peek the JSValue // first (ending the immutable borrow), then swap_remove (which drops the // Strong == `defer cbstrong.deinit()`). @@ -666,8 +666,10 @@ pub fn get_nack_packet(mode: Mode) -> &'static [u8] { } } +// Zig `uws.NewSocketHandler(false)`: `bun_uws::SocketHandler` is an alias +// for `NewSocketHandler` (uws_sys/socket.rs), so `` is the same +// non-SSL handler shape. pub type Socket = bun_uws::SocketHandler; -// TODO(port): uws.NewSocketHandler(false) — verify generic shape in bun_uws pub struct Handle { pub fd: Fd, @@ -1003,7 +1005,7 @@ impl SendQueue { // owner is about to free the memory that backs `this`, so scheduling // a task that points back into it would use-after-free. if was_open && self.after_close_task.is_none() { - // PORT NOTE: `bun_event_loop::JsResult` erases the error to `*mut ()`; + // Note: `bun_event_loop::JsResult` erases the error to `*mut ()`; // adapt the jsc-crate `JsResult` via a non-capturing closure (coerces to fn ptr). let task = ManagedTask::new(std::ptr::from_mut::(self), |p| { let _ = Self::_on_after_ipc_closed(p); @@ -1060,7 +1062,7 @@ impl SendQueue { self.close_socket(CloseReason::Normal, CloseFrom::User); return; } - // PORT NOTE: see `_socket_closed` — adapt `bun_event_loop::JsResult` via closure. + // Note: see `_socket_closed` — adapt `bun_event_loop::JsResult` via closure. let task = ManagedTask::new(std::ptr::from_mut::(self), |p| { let _ = Self::_close_socket_task(p); Ok(()) @@ -1111,7 +1113,7 @@ impl SendQueue { // optimal case: appending a message without a handle to the end of the queue when the last message also doesn't have a handle and isn't ack/nack // this is rare. it will only happen if messages stack up after sending a handle, or if a long message is sent that is waiting for writable - // PORT NOTE: reshaped for borrowck (NLL limitation: early-return of + // Note: reshaped for borrowck (NLL limitation: early-return of // `&mut self.queue[..]` would otherwise extend the borrow across the // fallback push). Compute the predicate first, then re-borrow. let use_last = if handle.is_none() && !self.queue.is_empty() { @@ -1222,7 +1224,7 @@ impl SendQueue { pub fn update_ref(&mut self, global: &JSGlobalObject) { let _ = global; - // PORT NOTE: KeepAlive::{ref_,unref} take an `EventLoopCtx` (aio cycle- + // Note: KeepAlive::{ref_,unref} take an `EventLoopCtx` (aio cycle- // break vtable), not `&VirtualMachine`. The Zig anytype dispatch is // routed through `bun_io::get_vm_ctx` which `bun_runtime` registers. let ctx = bun_io::posix_event_loop::get_vm_ctx(bun_io::AllocatorType::Js); @@ -1242,8 +1244,10 @@ impl SendQueue { } ); self.debug_log_message_queue(); - // defer this.updateRef(global) — handled at every return below. - // TODO(port): errdefer — use scopeguard for update_ref-on-exit. + // Zig: `defer this.updateRef(global)` — handled manually at every + // return below (the recursive `continue_send` path delegates to its + // own tail `update_ref`). A scopeguard can't hold `&mut self` while + // the body also uses `self`, so the manual spelling stays. if self.queue.is_empty() { self.update_ref(global); @@ -1368,7 +1372,7 @@ impl SendQueue { ) -> SerializeAndSendResult { log!("SendQueue#serializeAndSend"); let indicate_backoff = self.waiting_for_ack.is_some() && !self.queue.is_empty(); - // PORT NOTE: reshaped for borrowck — work on msg via local then drop borrow before continue_send. + // Note: reshaped for borrowck — work on msg via local then drop borrow before continue_send. let mode = self.mode; let msg = match self.start_message(global, callback, handle) { Ok(m) => m, @@ -1568,7 +1572,7 @@ impl SendQueue { // Zig: `defer vm.eventLoop().exit()` — handled by `_scope` drop. } fn get_global_this(&self) -> crate::GlobalRef { - // PORT NOTE: lifetime detached from `&self` so callers can hold the + // Note: lifetime detached from `&self` so callers can hold the // global across `&mut self` borrows (Zig passes `*JSGlobalObject` by // raw pointer everywhere). The owner (Subprocess / IPCInstance) // outlives this SendQueue and the JSGlobalObject is heap-allocated by @@ -1637,7 +1641,6 @@ impl SendQueue { this: *mut Self, pipe_fd: Fd, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set log!("configureClient"); let ipc_pipe: *mut uv::Pipe = bun_core::heap::into_raw(Box::new(bun_core::ffi::zeroed::())); @@ -1765,19 +1768,22 @@ fn handle_ipc_message( ) { #[cfg(debug_assertions)] { - // PORT NOTE: Zig formats the JSValue via ConsoleObject.Formatter for - // the scoped log; the Rust `Formatter` has no `Default` and threading - // it through here pulls in the full table-printer machinery for a - // debug-only log line. Log the variant tag instead. - // TODO(port): wire `console_object::Formatter::new(global_this)` once - // its construction stabilises. - let _ = global_this; + // Zig: `var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis }; + // defer formatter.deinit();` — the Rust `Formatter` runs its deinit in `Drop`. + let mut formatter = jsc::ConsoleObject::Formatter::new(global_this); match &message { DecodedIPCMessage::Version(version) => { log!("received ipc message: version: {}", version) } - DecodedIPCMessage::Data(_) => log!("received ipc message: \\"), - DecodedIPCMessage::Internal(_) => log!("received ipc message: internal"), + DecodedIPCMessage::Data(jsvalue) => { + log!("received ipc message: {}", jsvalue.to_fmt(&mut formatter)) + } + DecodedIPCMessage::Internal(jsvalue) => { + log!( + "received ipc message: internal: {}", + jsvalue.to_fmt(&mut formatter) + ) + } } } let mut internal_command: Option = None; @@ -1898,7 +1904,7 @@ fn on_data2(send_queue: &mut SendQueue, all_data: &[u8]) { // Decode the message with just the temporary buffer, and if that // fails (not enough bytes) then we allocate to .ipc_buffer - // PORT NOTE: reshaped for borrowck — match on raw discriminant pointer to allow + // Note: reshaped for borrowck — match on raw discriminant pointer to allow // calling &mut self methods on send_queue inside arms. match &mut send_queue.incoming { IncomingBuffer::Json(_) => { diff --git a/src/jsc/javascript_core_c_api.rs b/src/jsc/javascript_core_c_api.rs index 06795c5d48b1..effbf3df85c2 100644 --- a/src/jsc/javascript_core_c_api.rs +++ b/src/jsc/javascript_core_c_api.rs @@ -32,7 +32,6 @@ pub(crate) type OpaqueJSValue = Generic; pub type JSValueRef = *mut OpaqueJSValue; pub type JSObjectRef = *mut OpaqueJSValue; -// TODO(port): move to jsc_sys unsafe extern "C" { pub fn JSGarbageCollect(ctx: *mut JSGlobalObject); } @@ -51,8 +50,9 @@ pub enum JSType { } /// From JSValueRef.h:81 -// TODO(port): Zig enum is non-exhaustive (`_`); only ever passed *to* C in this file so a -// #[repr(u32)] enum is sound here. If a future extern returns this, switch to a newtype. +// The Zig enum is non-exhaustive (`_`); this enum is only ever passed *to* C in +// this file, so an exhaustive #[repr(u32)] enum is sound here. If a future +// extern ever *returns* this type, switch to a newtype over u32. #[repr(u32)] // c_uint #[derive(Copy, Clone, PartialEq, Eq)] pub enum JSTypedArrayType { @@ -71,7 +71,6 @@ pub enum JSTypedArrayType { kJSTypedArrayTypeBigUint64Array, } -// TODO(port): move to jsc_sys unsafe extern "C" { pub fn JSValueGetType(ctx: *mut JSGlobalObject, value: JSValueRef) -> JSType; pub fn JSValueMakeNull(ctx: *mut JSGlobalObject) -> JSValueRef; @@ -105,7 +104,6 @@ pub enum JSClassAttributes { pub type ExceptionRef = *mut JSValueRef; -// TODO(port): move to jsc_sys unsafe extern "C" { pub fn JSObjectGetPrototype(ctx: *mut JSGlobalObject, object: JSObjectRef) -> JSValueRef; pub fn JSObjectGetPropertyAtIndex( @@ -145,7 +143,6 @@ unsafe extern "C" { ) -> JSObjectRef; } -// TODO(port): move to jsc_sys unsafe extern "C" { pub fn JSObjectMakeTypedArray( ctx: *mut JSGlobalObject, @@ -204,7 +201,6 @@ unsafe extern "C" { ) -> usize; } -// TODO(port): move to jsc_sys unsafe extern "C" { /// This is a workaround for not receiving a JSException* object /// This function lets us use the C API but returns a plain old JSValue diff --git a/src/jsc/lib.rs b/src/jsc/lib.rs index 1f10634d4a50..ef7fa3649878 100644 --- a/src/jsc/lib.rs +++ b/src/jsc/lib.rs @@ -740,7 +740,7 @@ impl JsResultExt for JsResult { impl From for JsError { fn from(_: bun_core::Error) -> Self { - // PORT NOTE: Zig coerces arbitrary `anyerror` into the JS error union by + // Note: Zig coerces arbitrary `anyerror` into the JS error union by // throwing a generic Error; the throw happens at the call site. Mapping // to `Thrown` here lets `?` propagate while the actual throw is handled // by the host-fn wrapper. @@ -908,7 +908,7 @@ pub mod __macro_support { global: &JSGlobalObject, f: impl FnOnce() -> R, ) -> JSValue { - // PORT NOTE: Zig passed `@src()` explicitly; `to_js_host_call` is + // Note: Zig passed `@src()` explicitly; `to_js_host_call` is // `#[track_caller]` so the caller's `Location` propagates through this // `#[track_caller]` shim into `ExceptionValidationScope::init`. super::host_fn::to_js_host_call(global, move || f().into_host_fn_result()) @@ -1658,7 +1658,7 @@ pub use self::Node as node; /// has no jsc dep) so lower-tier crates can mark bindings without depending on /// `bun_jsc`. This fn is the thin wrapper `jsc.zig` exposes for in-crate use. /// -/// PORT NOTE: `std.builtin.SourceLocation.fn_name` has no Rust equivalent; +/// Note: `std.builtin.SourceLocation.fn_name` has no Rust equivalent; /// `#[track_caller]` only surfaces file/line, so the leading `{fn_name}` is /// dropped. Prefer the `mark_binding!()` macro form (re-exported above) which /// captures `module_path!()` at the call site. @@ -2156,7 +2156,6 @@ pub fn to_js_time(sec: isize, nsec: isize) -> JSTimeType { pub const MAX_SAFE_INTEGER: i64 = 9007199254740991; pub const MIN_SAFE_INTEGER: i64 = -9007199254740991; -// TODO(port): move to jsc_sys unsafe extern "C" { fn JSCInitialize( env: *const *const c_char, @@ -2167,9 +2166,8 @@ unsafe extern "C" { ); } -// TODO(port): generated module — re-run bindgen with .rs output. Hand-stubbed -// in `generated.rs` until `src/codegen/generate-classes.ts` grows a `.rs` -// backend. +// Hand-stubbed in `generated.rs` until `src/codegen/generate-classes.ts` +// grows a `.rs` backend (same arrangement as `r#gen` below). #[path = "generated.rs"] pub mod generated; diff --git a/src/jsc/rare_data.rs b/src/jsc/rare_data.rs index d237bf69459c..4e1739c1bb37 100644 --- a/src/jsc/rare_data.rs +++ b/src/jsc/rare_data.rs @@ -91,17 +91,14 @@ impl HotMap { if gop.found_existing { panic!("HotMap already contains key"); } - // PORT NOTE: `get_or_put` already boxed the key; Zig wrote - // `entry.key_ptr.* = dupe(key)` because its map didn't own keys. + // `get_or_put` already boxed the key; the map owns its keys. *gop.value_ptr = entry; } pub fn remove(&mut self, key: &[u8]) { - // PORT NOTE: Zig captured the stored key ptr to free post-removal; here - // the map owns the Box<[u8]> key and `swap_remove` drops it. Preserve - // the aliasing assert (caller must not pass the map's own key storage). - // Ordering doesn't matter for HotMap consumers — Zig's `orderedRemove` - // was incidental, not load-bearing. + // The map owns the Box<[u8]> key and `swap_remove` drops it. The + // aliasing assert below means the caller must not pass the map's own + // key storage. Ordering doesn't matter for HotMap consumers. let Some(i) = self._map.get_index(key) else { return; }; @@ -175,8 +172,9 @@ pub(crate) type CleanupHookFunction = extern "C" fn(*mut c_void); pub struct CleanupHook { pub ctx: *mut c_void, pub func: CleanupHookFunction, - // PORT NOTE: LIFETIMES.tsv says &'a JSGlobalObject (JSC_BORROW); raw ptr - // avoids threading a lifetime param through `RareData`. + // Conceptually a borrow of the JSGlobalObject (JSC_BORROW per + // LIFETIMES.tsv); a raw ptr avoids threading a lifetime param through + // `RareData`. pub global_this: *const JSGlobalObject, } @@ -300,11 +298,11 @@ pub struct RareData { pub temp_pipe_read_buffer: Option>, - // PORT NOTE: `aws_signature_cache` field dropped — storage moved DOWN to - // `bun_s3_signing::credentials::AWS_SIGNATURE_CACHE` (process static). The - // Zig code always reached it via `getMainThreadVM()`, so it was a - // singleton in practice; hosting it in the consumer crate removes the - // upward `s3_signing → jsc` hook. + // There is intentionally no `aws_signature_cache` field — storage lives in + // `bun_s3_signing::credentials::AWS_SIGNATURE_CACHE` (process static; it + // was always reached via the main-thread VM, so it was a singleton in + // practice). Hosting it in the consumer crate removes the upward + // `s3_signing → jsc` hook. pub s3_default_client: Strong, pub default_csrf_secret: Box<[u8]>, @@ -530,8 +528,7 @@ impl ProxyEnvSlots { /// parent's strings. Caller passes the parent's locked guard — the `Arc` /// load + clone is not atomic with respect to `Bun__setEnvValue`'s drop. pub fn clone_from(&mut self, parent: &ProxyEnvSlots) { - // PORT NOTE: reshaped for borrowck — Zig iterated fields via @typeInfo; - // here Arc::clone bumps the refcount. + // Arc::clone bumps the refcount. self.HTTP_PROXY.clone_from(&parent.HTTP_PROXY); self.http_proxy.clone_from(&parent.http_proxy); self.HTTPS_PROXY.clone_from(&parent.HTTPS_PROXY); @@ -569,9 +566,8 @@ impl ProxyEnvSlots { /// A ref-counted heap-allocated byte slice. The env map stores borrowed /// `.bytes` slices; as long as any VM holds a ref, the bytes stay valid. /// -/// PORT NOTE: Zig used intrusive `ThreadSafeRefCount`; LIFETIMES.tsv classifies -/// holders as `Arc`, so the refcount lives in the `Arc` -/// header and `ref`/`deref` become `Arc::clone`/`drop`. +/// Holders are `Arc` (per LIFETIMES.tsv): the refcount +/// lives in the `Arc` header, so ref/deref are `Arc::clone`/`drop`. pub struct RefCountedEnvValue { pub bytes: Box<[u8]>, } @@ -705,10 +701,10 @@ impl RareData { } pub fn boring_engine(&mut self) -> *mut boring::ENGINE { - // PORT NOTE: Zig spec is `ENGINE_new().?` (panic on null). We cache the - // raw result; `EVP_DigestInit_ex` tolerates a NULL engine, so OOM here - // degrades to "no engine" rather than crashing. Debug-assert to surface - // the divergence without altering release behavior. + // The raw `ENGINE_new()` result is cached without a null check: + // `EVP_DigestInit_ex` tolerates a NULL engine, so OOM here degrades to + // "no engine" rather than crashing. Debug-assert to surface it without + // altering release behavior. let ptr = *self .boring_ssl_engine .get_or_insert_with(|| boring::ENGINE_new()); @@ -726,11 +722,10 @@ impl RareData { } pub fn tls_default_ciphers(&self) -> Option<&[u8]> { - // PORT NOTE: Zig returns `[:0]const u8` whose `.len` excludes the NUL - // sentinel. The stored buffer is NUL-terminated (set_tls_default_ciphers - // appends 0), so strip the trailing NUL from the returned slice's length - // to match `dupeZ` semantics. Callers needing a C string can still take - // `.as_ptr()` — the NUL byte remains in storage one-past-the-end. + // The stored buffer is NUL-terminated (set_tls_default_ciphers + // appends 0); the trailing NUL is stripped from the returned slice's + // length. Callers needing a C string can still take `.as_ptr()` — the + // NUL byte remains in storage one-past-the-end. self.tls_default_ciphers .as_deref() .map(|s| &s[..s.len() - 1]) @@ -756,9 +751,9 @@ impl RareData { pub fn spawn_sync_event_loop(&mut self, vm: &mut VirtualMachine) -> &mut SpawnSyncEventLoop { if self.spawn_sync_event_loop_.is_none() { - // PORT NOTE: in-place out-param init — Zig used Owned::new(undefined) - // then ptr.init(vm). `event_loop` inside captures `self`-addr, so the - // value must not move after init; allocate the Box first, init into it. + // In-place out-param init: `event_loop` inside captures the + // `self` address, so the value must not move after init; allocate + // the Box first, then init into it. let mut boxed = Box::::new_uninit(); SpawnSyncEventLoop::init( &mut *boxed, @@ -872,8 +867,6 @@ impl RareData { // ── socket groups: lazy init ────────────────────────────────────────── #[inline] fn lazy_group<'a>(g: &'a mut SocketGroup, vm: &VirtualMachine) -> &'a mut SocketGroup { - // PORT NOTE: Zig took `comptime field: []const u8` + @field; Rust takes - // the field reference directly since callers know the field statically. if g.loop_.is_null() { g.init(vm.uws_loop(), None, core::ptr::null_mut()); } @@ -1082,8 +1075,8 @@ pub(crate) enum StdinFdType { #[unsafe(no_mangle)] pub(crate) extern "C" fn Bun__Process__getStdinFdType(vm: &VirtualMachine, fd: i32) -> StdinFdType { let rare = vm.as_mut().rare_data(); - // PORT NOTE: Zig read `store.data.file.mode`; the store is erased here, so - // `stderr/stdout/stdin()` cache `mode` alongside the pointer. + // The store is type-erased here, so `stderr/stdout/stdin()` cache `mode` + // alongside the pointer. let mode = match fd { 0 => { rare.stdin(); diff --git a/src/jsc/resolver_jsc.rs b/src/jsc/resolver_jsc.rs index 12fa66826a09..e3d6a5bfc06e 100644 --- a/src/jsc/resolver_jsc.rs +++ b/src/jsc/resolver_jsc.rs @@ -32,8 +32,9 @@ pub(crate) extern "C" fn Resolver__propForRequireMainPaths(global: &JSGlobalObje node_module_paths_js_value(in_str, global, false) } -// TODO(port): C++ callers pass `in_str` by value without transferring a ref; verify -// `bun_core::String` Drop semantics match (Zig callee did not `deref`). +// C++ callers pass `in_str` by value without transferring a ref. That matches +// the Zig callee (which did not `deref`): `bun_core::String` is `Copy` with no +// `Drop` impl, so receiving it by value never releases the caller's ref. #[unsafe(export_name = "Resolver__nodeModulePathsJSValue")] pub(crate) extern "C" fn node_module_paths_js_value( in_str: BunString, @@ -68,7 +69,7 @@ pub(crate) extern "C" fn node_module_paths_js_value( }; let mut root_path: &[u8] = &full_path[0..root_index]; if full_path.len() > root_path.len() { - // PORT NOTE: reshaped for borrowck — `std.mem.splitBackwardsScalar` exposes + // Note: reshaped for borrowck — `std.mem.splitBackwardsScalar` exposes // `.buffer` and `.index`, which Rust's `rsplit` does not. Manual iteration // mirrors the Zig SplitBackwardsIterator state machine exactly. let suffix: &[u8] = &full_path[root_index..]; diff --git a/src/jsc/uuid.rs b/src/jsc/uuid.rs index 491d7ffa66ee..3f928cf1a05f 100644 --- a/src/jsc/uuid.rs +++ b/src/jsc/uuid.rs @@ -179,8 +179,7 @@ pub struct UUID5 { pub bytes: [u8; 16], } -// PORT NOTE: Zig nested `pub const namespaces = struct { ... }` used as a namespace; -// Rust cannot nest a module inside an `impl`, so it lives adjacent to `UUID5`. +/// Well-known UUID v5 namespaces (RFC 4122 Appendix C). pub mod namespaces { use super::*; diff --git a/src/jsc/virtual_machine_exports.rs b/src/jsc/virtual_machine_exports.rs index ccc145f26f1d..3415e8d13464 100644 --- a/src/jsc/virtual_machine_exports.rs +++ b/src/jsc/virtual_machine_exports.rs @@ -42,7 +42,6 @@ pub fn read_origin_timer(vm: &VirtualMachine) -> u64 { if let Some(overridden) = vm.overridden_performance_now { return overridden; } - // PORT NOTE: Zig `std.time.Timer.read()`; the Rust field is `Instant`. vm.origin_timer.elapsed().as_nanos() as u64 } @@ -155,9 +154,9 @@ pub fn handle_rejected_promise(global: &JSGlobalObject, promise: &mut JSPromise) struct HandledPromiseContext { // VM-lifetime backref (JSC_BORROW) — `GlobalRef` encapsulates the deref. global_this: crate::GlobalRef, - // PORT NOTE: Zig stored a bare JSValue rooted via `.protect()`/`.unprotect()`. - // PORTING.md forbids bare JSValue fields on heap-allocated structs; `Strong` - // is the prescribed root type and its `Drop` releases the handle slot. + // PORTING.md forbids bare JSValue fields on heap-allocated structs; + // `Strong` is the prescribed root type and its `Drop` releases the + // handle slot. promise: Strong, } diff --git a/src/jsc/web_worker.rs b/src/jsc/web_worker.rs index ce353170c3b1..f654379b5410 100644 --- a/src/jsc/web_worker.rs +++ b/src/jsc/web_worker.rs @@ -98,7 +98,6 @@ pub struct WebWorker { eval_mode: bool, store_fd: bool, /// Borrowed from C++ `WorkerOptions` (kept alive by the owning `Worker`). - // TODO(port): lifetime — borrowed from cpp_worker (BACKREF). argv_ptr: *const WTFStringImpl, argv_len: usize, exec_argv_ptr: *const WTFStringImpl, @@ -121,8 +120,6 @@ pub struct WebWorker { /// Rust's aliasing model still requires interior mutability. `*mut T` is /// `Copy`, so `Cell` (not `UnsafeCell`) suffices and every read/write is /// safe `.get()`/`.set()`. - // TODO(port): intrusive doubly-linked list node — `bun_collections` has no - // `IntrusiveList` yet; raw next/prev pointers used directly. live_next: Cell<*mut WebWorker>, live_prev: Cell<*mut WebWorker>, @@ -201,7 +198,6 @@ pub enum Status { Terminated, } -// TODO(port): move to _sys // `JSGlobalObject` is an opaque FFI handle (ZST); per codebase convention // (see JSGlobalObject.rs externs) it crosses FFI as `*const` even when C++ // mutates — Rust never reads/writes bytes through it, so no `*mut` needed. @@ -217,7 +213,7 @@ unsafe extern "C" { // by-value scalars/`#[repr(C)]` PODs. safe fn WebWorker__dispatchExit(cpp_worker: *mut c_void, exit_code: i32); // Re-declared here (also private in VM.rs) so `thread_main` can take the - // API lock as a raw FFI call with NO RAII guard — see PORT NOTE there. + // API lock as a raw FFI call with NO RAII guard — see the note there. safe fn JSC__VM__getAPILock(vm: &jsc::VM); safe fn WebWorker__dispatchOnline(cpp_worker: *mut c_void, global: &JSGlobalObject); safe fn WebWorker__fireEarlyMessages(cpp_worker: *mut c_void, global: &JSGlobalObject); @@ -239,11 +235,8 @@ unsafe extern "C" { mod live_workers { use super::*; - // PORT NOTE: `Mutex::new()` is the prevailing const-init spelling across - // un-gated jsc modules (ConsoleObject.rs, bundler/ThreadPool.rs); the - // `bun_threading` crate provides it. pub(super) static MUTEX: Mutex = Mutex::new(); - // TODO(port): std.DoublyLinkedList — intrusive, nodes are `WebWorker.live_{next,prev}` + // Intrusive doubly-linked list head; nodes are `WebWorker.live_{next,prev}`. // PORTING.md §Global mutable state: list head, every read/write is under // `MUTEX` above. `AtomicCell` so the slot itself is `Sync` with safe // load/store (the mutex still provides the actual happens-before for the @@ -591,7 +584,7 @@ impl WebWorker { // worker whose thread is already running. live_workers::register(worker); - // PORT NOTE: Zig's `std.Thread.spawn(.{ .stack_size }, threadMain, .{worker})`. + // Zig's `std.Thread.spawn(.{ .stack_size }, threadMain, .{worker})`. // `std::thread` is permitted (only `std::{fs,net,process}` are banned); // bun_threading has no generic spawn helper. struct SendPtr(*mut WebWorker); @@ -792,7 +785,7 @@ impl WebWorker { } }; - // PORT NOTE: `start_vm()` may have observed `requested_terminate` and + // `start_vm()` may have observed `requested_terminate` and // run `shutdown()` itself (which now returns instead of `noreturn`). // In that case it returns `Ok(null)` and there is nothing left to do — // fall out of `thread_main` so the thread exits cleanly. We must NOT @@ -811,7 +804,7 @@ impl WebWorker { // the safe thread-local accessor returns the same allocation. debug_assert!(core::ptr::eq(vm_ptr, VirtualMachine::get_mut_ptr())); let global = VirtualMachine::get().global(); - // PORT NOTE: Zig calls `holdAPILock(this, OpaqueWrap(spin))`; the + // Zig calls `holdAPILock(this, OpaqueWrap(spin))`; the // callback ends in `bun.exitThread()` (`pthread_exit`), whose forced // unwind cannot walk Zig frames (no unwind tables), so glibc falls // back to a longjmp and the C++ `JSLockHolder` destructor never @@ -819,7 +812,7 @@ impl WebWorker { // accounts for that abandoned ref by `deref`ing twice. // // In Rust we cannot use `pthread_exit` (its forced unwind aborts at - // the first `extern "C"` boundary — see `shutdown` PORT NOTE), so + // the first `extern "C"` boundary — see the `shutdown` note), so // `spin()` returns. To preserve the Zig invariant that the API lock // is simply abandoned along with the destroyed VM, take the lock via // raw FFI (NOT the `Lock<'_>` RAII guard) and never release it. @@ -893,7 +886,7 @@ impl WebWorker { // state. let mut temp_proxy_slots = jsc::rare_data::ProxyEnvSlots::default(); - // PORT NOTE: Zig allocated Map/Loader on the worker arena (bulk-freed + // Zig allocated Map/Loader on the worker arena (bulk-freed // in shutdown). Rust's `Arena = bumpalo::Bump` doesn't run Drop, so // box on the global heap instead and hand ownership to the VM via // `transpiler.env`; reclaimed in `vm.destroy()` in `shutdown()`. @@ -1026,7 +1019,7 @@ impl WebWorker { /// Phase 2: load the entry point, dispatch 'online', run the event loop. /// Runs inside `holdAPILock`. Always ends by calling `shutdown()`. /// - /// PORT NOTE: Zig's `spin` is `noreturn` (every path ends in `shutdown` + /// Zig's `spin` is `noreturn` (every path ends in `shutdown` /// → `bun.exitThread`). The Rust port returns `()` so the thread can /// unwind-free fall out of the `extern "C"` trampoline — see `shutdown`. fn spin(&self) { @@ -1060,7 +1053,7 @@ impl WebWorker { } // `preloads` is owned by `self` (heap `WebWorker` outlives the VM). - // PORT NOTE: Zig's slice-copy assignment; here `preload: Vec>` + // Zig's slice-copy assignment; here `preload: Vec>` // so clone the boxes (cheap, ≤handful). vm.as_mut().preload.clone_from(&self.preloads); @@ -1205,8 +1198,8 @@ impl WebWorker { /// 3. `teardownJSCVM()` — collectNow + vm.deref (single — Zig /// derefs ×2 because `JSLockHolder` holds /// a `RefPtr`; the Rust API-lock path - /// takes no extra ref, see `thread_main` - /// PORT NOTE); can re-enter via + /// takes no extra ref, see the `thread_main` + /// note); can re-enter via /// finalizers, so must precede step 5. /// 4. `dispatchExit()` — posts close task → parent releases /// parent_poll_ref + thread-held Worker ref. @@ -1215,7 +1208,7 @@ impl WebWorker { /// /// Does NOT free `this` — see ownership rule in the file header. /// - /// PORT NOTE: Zig's `shutdown` is `noreturn` (ends in `bun.exitThread`). + /// Zig's `shutdown` is `noreturn` (ends in `bun.exitThread`). /// The Rust port returns `()` and lets the thread fall out of the spawn /// closure instead — see the note at the bottom of this fn. fn shutdown(&self) { @@ -1270,7 +1263,7 @@ impl WebWorker { // closeAll() fires on_close → JS callbacks. RareData.deinit() runs // after teardownJSCVM and only deinit()s (asserts empty in debug). if let Some(rare) = vm.rare_data.as_deref_mut() { - // PORT NOTE: reshaped for borrowck — `close_all_socket_groups` + // reshaped for borrowck — `close_all_socket_groups` // wants `&VirtualMachine` while `rare` is `&mut` borrowed from // `vm`. Re-derive `vm` through the raw ptr (sole owner). @@ -1374,7 +1367,7 @@ impl WebWorker { bun_uws::on_thread_exit(); drop(arena.take()); - // PORT NOTE: Zig calls `bun.exitThread()` (`pthread_exit`) here. In + // Zig calls `bun.exitThread()` (`pthread_exit`) here. In // Rust we MUST NOT — glibc's `pthread_exit` throws a `__forced_unwind` // C++ exception to run destructors, and unwinding that across an // `extern "C"` (`nounwind`) Rust frame on the way out to @@ -1503,15 +1496,15 @@ fn on_unhandled_rejection( } error_instance = global_object.try_take_exception().unwrap(); } - // PORT NOTE: Zig's `writer.flush()` — `Vec` writer is unbuffered, so + // Zig's `writer.flush()` — `Vec` writer is unbuffered, so // there is nothing to flush; the `bun.outOfMemory()` arm is unreachable. jsc::mark_binding(); - // PORT NOTE: Zig calls `WebWorker__dispatchError` bare here because the + // Zig calls `WebWorker__dispatchError` bare here because the // very next statement is `worker.shutdown()` (noreturn — `bun.exitThread` // longjmps), so the C++ `DECLARE_THROW_SCOPE` inside // `SerializedScriptValue::create` (reached via `dispatchErrorWithValue`) // never has its simulated-throw bookkeeping validated. Rust RETURNS through - // the live C++ frames (see PORT NOTE below), so that simulated throw must + // the live C++ frames (see the note below), so that simulated throw must // be checked before unwinding, or the next `TopExceptionScope` ctor on the // stack — `performMicrotaskCheckpoint` / NodeTimerObject `call()` — trips // `verifyExceptionCheckNeedIsSatisfied`. Wrap in `from_js_host_call_generic` @@ -1533,7 +1526,7 @@ fn on_unhandled_rejection( let _ = global_object.try_take_exception(); } let _ = worker.set_requested_terminate(); - // PORT NOTE: Zig calls `worker.shutdown()` here, which is `noreturn` + // Zig calls `worker.shutdown()` here, which is `noreturn` // (`bun.exitThread` longjmps out, abandoning the C++ frames on the // stack). In Rust `shutdown()` RETURNS — calling it here would destroy // the `JSC::VM`, free the Bun `VirtualMachine` + arena, and post @@ -1606,7 +1599,7 @@ unsafe fn resolve_entry_point_specifier<'s>( ); let base_len = base.len(); let extname_len = bun_paths::extension(base).len(); - // PORT NOTE: reshaped for borrowck — Zig held `extname` as a + // reshaped for borrowck — Zig held `extname` as a // sub-slice of `pathbuf` while writing into `pathbuf`. Compare // by re-slicing after dropping the mutable borrow. let extname = &pathbuf[base_len - extname_len..base_len]; diff --git a/src/jsc/webcore_types.rs b/src/jsc/webcore_types.rs index 327426677954..f32ec50db770 100644 --- a/src/jsc/webcore_types.rs +++ b/src/jsc/webcore_types.rs @@ -66,7 +66,7 @@ pub struct Blob { pub store: JsCell>, /// Either a `&'static [u8]` (mime constant / literal) or a heap allocation /// owned by this Blob, discriminated by `content_type_allocated`. - // TODO(port): model as Cow<'static, [u8]> once callers are audited. + /// (`Cow<'static, [u8]>` is not `#[repr(C)]`, so the manual encoding stays.) pub content_type: Cell<*const [u8]>, pub content_type_allocated: Cell, pub content_type_was_set: Cell, @@ -509,7 +509,7 @@ pub mod store { pub mime_type: MimeType, pub ref_count: bun_ptr::ThreadSafeRefCount, pub is_all_ascii: Option, - // PORT NOTE: `allocator: std.mem.Allocator` field dropped — global + // Zig's `allocator: std.mem.Allocator` field dropped — global // mimalloc everywhere (PORTING.md §Allocators). } @@ -886,7 +886,7 @@ pub mod store { } } - // PORT NOTE: `S3.deinit` body deleted — only freed owned fields + // Zig's `S3.deinit` body deleted — only freed owned fields // (`pathlike`, `credentials.deref()`), all handled by `PathLike::drop` / // `Option>::drop`. Per PORTING.md §Idiom map, no explicit `Drop`. diff --git a/src/libarchive/lib.rs b/src/libarchive/lib.rs index 3028985f47cc..c002e176b5e9 100644 --- a/src/libarchive/lib.rs +++ b/src/libarchive/lib.rs @@ -939,7 +939,7 @@ pub mod lib { /// Port of `Archive.Iterator` (src/libarchive_sys/bindings.zig). pub struct Iterator { pub archive: *mut Archive, - // PORT NOTE: Zig had a `filter: std.EnumSet(std.fs.File.Kind)` field + // Zig had a `filter: std.EnumSet(std.fs.File.Kind)` field // that every caller leaves at `.initEmpty()` and never sets. Dropped // here (would need `EnumSetType` on `FileKind`); re-add if a caller // ever needs it. @@ -1097,7 +1097,7 @@ impl BufferReadStream { /// (including its `Drop`). Violating this makes [`buf()`], [`buf_left()`], /// and [`open_read()`] dereference a dangling pointer (UB). pub unsafe fn init(buf: &[u8]) -> Self { - // PORT NOTE: was an out-param constructor (`this.* = ...`) + // was an out-param constructor (`this.* = ...`) Self { buf: std::ptr::from_ref::<[u8]>(buf), pos: 0, @@ -1442,11 +1442,22 @@ pub mod archiver { pub all_files: EntryMap, } - // TODO(port): Zig used a custom U64Context (hash = truncate u64→u32, eql = ==). - // bun_collections::ArrayHashMap should accept a custom hasher; encode that here. - pub type EntryMap = ArrayHashMap; + // Zig used a custom U64Context (hash = truncate u64→u32, eql = ==); the + // keys are already wyhash u64s, so truncation is the entire hash. + pub type EntryMap = ArrayHashMap; + #[derive(Default, Clone, Copy)] pub struct U64Context; + impl bun_collections::array_hash_map::ArrayHashContext for U64Context { + #[inline] + fn hash(&self, k: &u64) -> u32 { + *k as u32 // @truncate + } + #[inline] + fn eql(&self, a: &u64, b: &u64, _: usize) -> bool { + a == b + } + } impl bun_collections::array_hash_map::ArrayHashAdapter for U64Context { #[inline] fn hash(&self, k: &u64) -> u32 { @@ -1470,7 +1481,6 @@ pub mod archiver { filepath: &[OSPathChar], estimated_size: usize, ) -> Result { - // TODO(port): narrow error set Ok(Plucker { contents: MutableString::init(estimated_size)?, filename_hash: hash(slice_as_bytes(filepath)), @@ -1535,7 +1545,6 @@ impl Archiver { ctx: &mut Context, appender: &mut A, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let mut entry: *mut lib::Entry = ptr::null_mut(); // SAFETY: `file_buffer` outlives `stream` (stack-local, dropped at fn exit). @@ -1543,7 +1552,7 @@ impl Archiver { let _ = stream.open_read(); let archive = stream.archive; - // PORT NOTE: std.fs.Dir / openDirAbsolute / cwd().openDir — mapped to + // std.fs.Dir / openDirAbsolute / cwd().openDir — mapped to // bun_sys directory-fd helpers (open_dir_absolute / open_dir_at). let dir: Fd = 'brk: { let cwd = Fd::cwd(); @@ -1561,7 +1570,7 @@ impl Archiver { break 'brk d; } }; - // PORT NOTE: Zig spec also lacks `defer dir.close()` here (pre-existing leak). + // Zig spec also lacks `defer dir.close()` here (pre-existing leak). // Fd has no Drop impl; close explicitly on every return path to avoid leaking // a directory HANDLE on Windows. Mirrors the guard pattern in extract_to_disk. let _close_dir_guard = scopeguard::guard(dir, |d| d.close()); @@ -1584,9 +1593,10 @@ impl Archiver { let pathname_full = lib::Entry::opaque_ref(entry).pathname(); let pathname_bytes = pathname_full.as_bytes(); - // TODO(port): std.mem.tokenizeScalar + .rest() — approximated by - // skipping DEPTH_TO_SKIP separator-delimited tokens then taking the - // remainder. TODO: verify edge cases (leading/trailing seps). + // Zig: std.mem.tokenizeScalar + .rest(). `next()` skips leading + // separators and returns null only when no token remains; + // `rest()` also skips leading separators — both behaviors are + // reproduced below (leading/trailing-separator edge cases match). let mut remaining = pathname_bytes; let mut depth_i = 0usize; while depth_i < DEPTH_TO_SKIP { @@ -1625,14 +1635,14 @@ impl Archiver { let size: usize = usize::try_from(lib::Entry::opaque_ref(entry).size().max(0)).unwrap(); if size > 0 { - // PORT NOTE: Zig used `dir.openFile(pathname, .{ .mode = .write_only })`. + // Zig used `dir.openFile(pathname, .{ .mode = .write_only })`. let Ok(opened) = bun_sys::openat_a(dir, pathname, bun_sys::O::WRONLY, 0) else { continue 'loop_; }; - // PORT NOTE: defer opened.close() + // defer opened.close() let _close_guard = scopeguard::guard(opened, |fd| fd.close()); - // PORT NOTE: Zig `opened.getEndPos()` → bun_sys::get_file_size. + // Zig `opened.getEndPos()` → bun_sys::get_file_size. let stat_size = bun_sys::get_file_size(opened)?; if stat_size > 0 { @@ -1676,7 +1686,6 @@ impl Archiver { appender: &mut A, options: ExtractOptions, ) -> Result { - // TODO(port): narrow error set let mut entry: *mut lib::Entry = ptr::null_mut(); // SAFETY: `file_buffer` outlives `stream` (stack-local, dropped at fn exit). @@ -1686,7 +1695,7 @@ impl Archiver { let mut count: u32 = 0; let dir_fd = dir; - // PORT NOTE: reshaped for borrowck — ctx is Option<&mut>, rebound as needed + // reshaped for borrowck — ctx is Option<&mut>, rebound as needed let mut ctx = ctx; #[cfg(unix)] @@ -1925,7 +1934,7 @@ impl Archiver { // It's possible for some tarballs to return a directory twice, with and // without `./` in the beginning. So if it already exists, continue to the // next entry. - // PORT NOTE: Zig matched error.PathAlreadyExists / error.NotDir. + // Zig matched error.PathAlreadyExists / error.NotDir. match err.get_errno() { bun_sys::E::EEXIST | bun_sys::E::ENOTDIR => continue, _ => {} @@ -1973,7 +1982,7 @@ impl Archiver { }; match bun_sys::symlinkat(link_target, dir_fd, path_z) { Ok(()) => {} - // PORT NOTE: Zig matched error.EPERM / error.ENOENT (errnoToZigErr maps 1:1). + // Zig matched error.EPERM / error.ENOENT (errnoToZigErr maps 1:1). Err(err) => match err.get_errno() { bun_sys::E::EPERM | bun_sys::E::ENOENT => { let dirname = bun_paths::dirname_simple(path_slice); @@ -2051,14 +2060,14 @@ impl Archiver { #[cfg(not(windows))] let file_handle_native: Fd = { - // PORT NOTE: dir.createFileZ(.{truncate, mode}) → bun_sys::openat + // dir.createFileZ(.{truncate, mode}) → bun_sys::openat // SAFETY: normalized_buf[path_slice.len()] == 0 (written above). let path_z: &ZStr = unsafe { ZStr::from_raw(path_slice.as_ptr(), path_slice.len()) }; match bun_sys::openat(dir_fd, path_z, flags, mode) { Ok(fd) => fd, - // PORT NOTE: Zig matched error.AccessDenied / error.FileNotFound. + // Zig matched error.AccessDenied / error.FileNotFound. Err(err) => match err.get_errno() { bun_sys::E::EACCES | bun_sys::E::EPERM @@ -2085,7 +2094,7 @@ impl Archiver { owned }; - // PORT NOTE: reshaped for borrowck — `plucked_file` is captured by + // reshaped for borrowck — `plucked_file` is captured by // the guard tuple; mutate via close_guard.1. let mut close_guard = scopeguard::guard((file_handle, false), |(fh, plucked)| { @@ -2241,7 +2250,6 @@ impl Archiver { appender: &mut A, options: ExtractOptions, ) -> Result { - // TODO(port): narrow error set let dir: Fd = 'brk: { let cwd = Fd::cwd(); let _ = cwd.make_path_u8(root); diff --git a/src/libuv_sys/libuv.rs b/src/libuv_sys/libuv.rs index 379c038fd6d1..7d8fd8734737 100644 --- a/src/libuv_sys/libuv.rs +++ b/src/libuv_sys/libuv.rs @@ -951,9 +951,9 @@ impl uv_write_t { /// the trampoline recovers it and dispatches to `on_write` as a plain Rust /// `&mut`. Generic monomorphisation gives one `extern "C"` thunk per ``. /// - /// PORT NOTE: Zig captures `onWrite` at *comptime* (one thunk per - /// callsite, direct call) and returns `Maybe(void)` with the - /// `.toError(.write)` already applied. The Rust port can do neither + /// The Zig original captured `onWrite` at *comptime* (one thunk per + /// callsite, direct call) and returned `Maybe(void)` with the + /// `.toError(.write)` already applied. The Rust version can do neither /// without a `bun_sys` dependency / unstable const-generic fn pointers, so /// it (a) keeps `on_write` runtime-dispatched but stashes it as a `usize` /// (fn-ptr ↔ integer is well-defined; fn-ptr ↔ data-ptr is not — Miri diff --git a/src/lolhtml_sys/lol_html.rs b/src/lolhtml_sys/lol_html.rs index 34913fc842d0..e1090c0ccca7 100644 --- a/src/lolhtml_sys/lol_html.rs +++ b/src/lolhtml_sys/lol_html.rs @@ -6,7 +6,6 @@ pub enum Error { #[error("Fail")] Fail, } -// TODO(port): impl From for bun_core::Error #[repr(C)] #[derive(Copy, Clone)] @@ -140,8 +139,8 @@ impl HTMLRewriter { Ok(()) } - // TODO(port): opaque FFI handle freed via C — cannot impl Drop on zero-sized opaque marker. - // Consider an owning newtype `OwnedRewriter(NonNull)` with Drop. + // Opaque FFI handle freed via C — Drop cannot be implemented on the + // zero-sized opaque marker, so freeing stays an explicit call. pub unsafe fn destroy(this: *mut HTMLRewriter) { auto_disable(); // SAFETY: caller guarantees `this` was returned by lol_html_rewriter_build and not yet freed @@ -191,7 +190,7 @@ unsafe extern "C" { } impl HTMLRewriterBuilder { - // TODO(port): opaque FFI handle — see HTMLRewriter::destroy note re: owning wrapper + Drop + // Opaque FFI handle — see HTMLRewriter::destroy note. pub unsafe fn destroy(this: *mut HTMLRewriterBuilder) { auto_disable(); // SAFETY: caller guarantees `this` came from lol_html_rewriter_builder_new and not yet freed @@ -221,7 +220,7 @@ impl HTMLRewriterBuilder { /// /// WARNING: Pointers passed to handlers are valid only during the /// handler execution. So they should never be leaked outside of handlers. - // PORT NOTE: handler-data params are `Option>`, not + // handler-data params are `Option>`, not // `Option<&mut H>` — callers routinely pass the SAME allocation for // multiple slots (one `DocumentHandler` services doctype/comment/text/end), // and materializing several live `&mut` to one object is UB under Stacked @@ -282,10 +281,10 @@ impl HTMLRewriterBuilder { /// /// WARNING: Pointers passed to handlers are valid only during the /// handler execution. So they should never be leaked outside of handlers. - // PORT NOTE: Zig also checked `handler != null` (in addition to `handler_data != null`); the trait + // Zig also checked `handler != null` (in addition to `handler_data != null`); the trait // model assumes the handler is always present when data is Some, so (handler=null, data=non-null) // is unrepresentable here. - // PORT NOTE: see `add_document_content_handlers` — `Option>` to + // see `add_document_content_handlers` — `Option>` to // permit the same handler allocation in multiple slots without aliased // `&mut`. pub fn add_element_content_handlers( @@ -323,7 +322,7 @@ impl HTMLRewriterBuilder { } } - // PORT NOTE: takes `*mut S` (not `&mut S`) so the userdata pointer stored + // takes `*mut S` (not `&mut S`) so the userdata pointer stored // in the C rewriter retains the caller's raw-pointer provenance (typically // a `heap::alloc` root). If we took `&mut S`, the userdata would carry a // tag derived from that short-lived Unique borrow, and any subsequent @@ -396,7 +395,7 @@ unsafe extern "C" { impl HTMLSelector { /// Frees the memory held by the parsed selector object. - // TODO(port): opaque FFI handle — see HTMLRewriter::destroy note + // Opaque FFI handle — see HTMLRewriter::destroy note. pub unsafe fn destroy(selector: *mut HTMLSelector) { auto_disable(); // SAFETY: caller guarantees `selector` was returned by parse() and not yet freed @@ -874,7 +873,7 @@ unsafe extern "C" { } impl HTMLString { - // TODO(port): #[repr(C)] value crosses FFI by-value; explicit deinit kept instead of Drop + // #[repr(C)] value crosses FFI by-value; explicit deinit kept instead of Drop. pub fn deinit(self) { auto_disable(); // if (this.len > 0) { @@ -1057,7 +1056,7 @@ impl AttributeIterator { } } - // TODO(port): opaque FFI handle — see HTMLRewriter::destroy note + // Opaque FFI handle — see HTMLRewriter::destroy note. pub fn destroy(&mut self) { auto_disable(); lol_html_attributes_iterator_free(self); @@ -1226,9 +1225,10 @@ pub trait DirectiveCallback { // Zig: pub fn DirectiveHandler(comptime Container, comptime UserDataType, comptime Callback) DirectiveFunctionType(Container) // Rust: monomorphized extern "C" trampoline per . -// TODO(port): Zig took the callback as a comptime fn-value (multiple callbacks per type possible). -// Rust trait dispatch allows one callback per (UserDataType, Container) pair. If callers need -// multiple, add a const-generic fn-pointer wrapper or distinct ZST marker types. +// Divergence from Zig: Zig took the callback as a comptime fn-value (multiple +// callbacks per type possible); Rust trait dispatch allows one callback per +// (UserDataType, Container) pair. If callers ever need multiple, add a +// const-generic fn-pointer wrapper or distinct ZST marker types. pub unsafe extern "C" fn directive_handler>( this: *mut Container, user_data: *mut c_void, diff --git a/src/md/ansi_renderer.rs b/src/md/ansi_renderer.rs index de57b6a84941..ca220b67c52b 100644 --- a/src/md/ansi_renderer.rs +++ b/src/md/ansi_renderer.rs @@ -78,7 +78,6 @@ impl ImageUrlCollector { } } -// PORT NOTE: Zig manual VTable collapsed into RendererImpl trait. impl RendererImpl for ImageUrlCollector { fn enter_block(&mut self, _: BlockType, _: u32, _: u32) -> JsResult<()> { Ok(()) @@ -374,9 +373,9 @@ impl<'a> AnsiRenderer<'a> { kind: BlockKind::Li, ..Default::default() }; - // PORT NOTE: reshaped for borrowck — find_parent_list returns - // an index instead of `&mut BlockContext` so we can call - // self.write_styled() afterwards without an aliasing borrow. + // find_parent_list returns an index instead of + // `&mut BlockContext` so we can call self.write_styled() + // afterwards without an aliasing borrow. let parent_list = self.find_parent_list(); let task_mark = types::task_mark_from_data(data); if let Some(idx) = parent_list { @@ -593,9 +592,8 @@ impl<'a> AnsiRenderer<'a> { if self.theme.colors && self.theme.hyperlinks { if let Some(href) = &self.link_href { // OSC 8 hyperlink start - // PORT NOTE: reshaped for borrowck — clone the - // bytes so write_raw_no_color(&mut self) doesn't - // alias `&self.link_href`. + // Clone the bytes so write_raw_no_color(&mut self) + // doesn't alias `&self.link_href`. let href = href.clone(); self.write_raw_no_color(b"\x1b]8;;"); self.write_raw_no_color(&href); @@ -1324,9 +1322,8 @@ impl<'a> AnsiRenderer<'a> { /// Find the nearest enclosing ul/ol in the block stack (walking /// from innermost outward, skipping the current li at the top). - // PORT NOTE: reshaped for borrowck — returns an index into - // block_stack instead of `&mut BlockContext` so callers can call - // other &mut self methods between accesses. + // Returns an index into block_stack instead of `&mut BlockContext` + // so callers can call other &mut self methods between accesses. fn find_parent_list(&self) -> Option { let len = self.block_stack.len(); if len == 0 { @@ -1354,8 +1351,8 @@ impl<'a> AnsiRenderer<'a> { // inside a blockquote the bold+color writes reach heading_buf and // may realloc its backing array, dangling the `content` slice below. self.heading_level = 0; - // PORT NOTE: reshaped for borrowck — take ownership of heading_buf - // so write_indent(&mut self) doesn't alias `content`. + // Take ownership of heading_buf so write_indent(&mut self) + // doesn't alias `content`. let content = core::mem::take(&mut self.heading_buf); self.write_indent(); if self.theme.colors { @@ -1417,8 +1414,8 @@ impl<'a> AnsiRenderer<'a> { // ======================================== fn flush_code_block(&mut self) { - // PORT NOTE: reshaped for borrowck — take ownership of code_buf so - // self.write_indent() etc. don't alias it. + // Take ownership of code_buf so self.write_indent() etc. + // don't alias it. let src = core::mem::take(&mut self.code_buf); // Strip exactly one trailing newline (parser adds one). let body: &[u8] = if !src.is_empty() && src[src.len() - 1] == b'\n' { @@ -1622,8 +1619,8 @@ impl<'a> AnsiRenderer<'a> { self.last_was_newline = true; let mut has_separated_header = false; - // PORT NOTE: reshaped for borrowck — take ownership of table_rows so - // self.write_row_cells(&mut self) doesn't alias it. + // Take ownership of table_rows so self.write_row_cells(&mut self) + // doesn't alias it. let rows = core::mem::take(&mut self.table_rows); for row in &rows { self.write_row_cells(row, &widths, &aligns); @@ -1877,8 +1874,8 @@ impl<'a> AnsiRenderer<'a> { // Snapshot alt + link fields now — emitImage drops out of the // image context before writing, so image_alt / image_depth checks // in emitInline would otherwise still divert output. - // PORT NOTE: reshaped for borrowck — take ownership of buffered - // fields so &mut self methods below don't alias. + // Take ownership of the buffered fields so &mut self methods + // below don't alias. let alt = core::mem::take(&mut self.image_alt); let src = self.image_src.take(); let title = self.image_title.take(); @@ -2698,7 +2695,6 @@ fn extract_png_data_url_base64(src: &[u8]) -> Option<&[u8]> { Some(payload) } -// PORT NOTE: Zig manual VTable collapsed into RendererImpl trait. impl RendererImpl for AnsiRenderer<'_> { fn enter_block(&mut self, block_type: BlockType, data: u32, flags: u32) -> JsResult<()> { AnsiRenderer::enter_block(self, block_type, data, flags); diff --git a/src/md/blocks.rs b/src/md/blocks.rs index f4ced5313ee7..6abfd9da2910 100644 --- a/src/md/blocks.rs +++ b/src/md/blocks.rs @@ -22,11 +22,10 @@ impl Parser<'_> { self.enter_block(BlockType::Doc, 0, 0)?; while off < self.size { - // PORT NOTE: reshaped for borrowck — index into line_buf via raw idx let line = &mut line_buf[line_idx]; self.analyze_line(off, &mut off, &pivot_line, line)?; - // PORT NOTE: reshaped for borrowck — pass whole buf + idx so process_line can swap + // Pass the whole buf + idx so process_line can swap lines. self.process_line(&mut pivot_line, line_idx, &mut line_buf, &mut line_idx)?; } @@ -694,8 +693,8 @@ impl Parser<'_> { line_buf: &mut [Line; 2], line_idx: &mut usize, ) -> Result<(), bun_alloc::AllocError> { - // PORT NOTE: reshaped for borrowck — Zig passed `line: *Line` aliasing into line_buf; - // here we index into line_buf via cur_line_idx. + // Index into line_buf via cur_line_idx instead of taking a `&mut Line` + // parameter, which would alias line_buf. let line = &mut line_buf[cur_line_idx]; // Blank line ends current leaf block. @@ -882,8 +881,8 @@ impl Parser<'_> { pub fn end_current_block(&mut self) -> Result<(), bun_alloc::AllocError> { if let Some(cb_off) = self.current_block { - // PORT NOTE: reshaped for borrowck — capture header fields, drop the &mut borrow, - // then access other &self fields. + // Capture the header fields, drop the &mut borrow, then access + // other &self fields. let (is_setext, hdr_n_lines) = { let hdr = self.get_block_header_at(cb_off); ( @@ -940,7 +939,6 @@ impl Parser<'_> { // Merge lines into buffer for ref def parsing self.buffer.clear(); - // PORT NOTE: reshaped for borrowck — index instead of borrowing items slice. for idx in 0..self.current_block_lines.len() { let vline = self.current_block_lines[idx]; if vline.beg > vline.end || vline.end > self.size { @@ -953,8 +951,8 @@ impl Parser<'_> { .extend_from_slice(&self.text[vline.beg as usize..vline.end as usize]); } - // PORT NOTE: reshaped for borrowck — move merged buffer out of self so - // parse_ref_def/normalize_label can borrow &self/&mut self. + // Move the merged buffer out of self so parse_ref_def/normalize_label + // can borrow &self/&mut self. let merged = core::mem::take(&mut self.buffer); let mut pos: usize = 0; let mut lines_consumed: u32 = 0; @@ -1004,7 +1002,6 @@ impl Parser<'_> { if lines_consumed > 0 { if let Some(cb_off) = self.current_block { - // PORT NOTE: reshaped for borrowck — capture n_lines, mutate, then write back. let hdr_n_lines = self.get_block_header_at(cb_off).n_lines; if lines_consumed >= hdr_n_lines { // All lines consumed diff --git a/src/md/containers.rs b/src/md/containers.rs index c8ee6f57a0cf..3e5b0e994936 100644 --- a/src/md/containers.rs +++ b/src/md/containers.rs @@ -53,8 +53,7 @@ impl Parser<'_> { pub fn enter_child_containers(&mut self, count: u32) -> Result<(), AllocError> { let mut i: u32 = self.n_containers - count; while i < self.n_containers { - // PORT NOTE: reshaped for borrowck — capture container fields before - // calling &mut self methods. + // Capture the container fields before calling &mut self methods. let idx = i as usize; let ch = self.containers[idx].ch; let is_task = self.containers[idx].is_task; @@ -105,8 +104,7 @@ impl Parser<'_> { pub fn leave_child_containers(&mut self, keep: u32) -> Result<(), AllocError> { while self.n_containers > keep { self.n_containers -= 1; - // PORT NOTE: reshaped for borrowck — capture container fields before - // calling &mut self methods. + // Capture the container fields before calling &mut self methods. let idx = self.n_containers as usize; let ch = self.containers[idx].ch; let is_loose = self.containers[idx].is_loose; @@ -183,9 +181,8 @@ impl Parser<'_> { pub fn process_all_blocks(&mut self) -> Result<(), parser::Error> { let mut off: usize = 0; - // PORT NOTE: reshaped for borrowck — capture raw ptr/len so we can call - // &mut self methods inside the loop. block_bytes is not mutated during - // process_all_blocks. + // Capture the raw ptr/len so we can call &mut self methods inside the + // loop. block_bytes is not mutated during process_all_blocks. let bytes_len = self.block_bytes.len(); let bytes_ptr = self.block_bytes.as_ptr(); diff --git a/src/md/entity.rs b/src/md/entity.rs index 6a461af86885..51c386892c37 100644 --- a/src/md/entity.rs +++ b/src/md/entity.rs @@ -7,7 +7,6 @@ use core::cmp::Ordering; /// Sorted array of entity names (including & and ;) mapped to Unicode codepoint(s). pub(crate) struct Entity { pub name: &'static [u8], - // PORT NOTE: Zig `u21` (Unicode codepoint) → `u32`. pub codepoints: [u32; 2], } diff --git a/src/md/helpers.rs b/src/md/helpers.rs index 852f9d78f165..e981664a0d32 100644 --- a/src/md/helpers.rs +++ b/src/md/helpers.rs @@ -430,7 +430,7 @@ pub fn generate_slug<'a>( let mut out_len: usize = 0; let mut prev_hyphen: bool = true; // true to trim leading hyphens - // PORT NOTE: reshaped for borrowck — index instead of `for &c in text_items` + // reshaped for borrowck — index instead of `for &c in text_items` for idx in 0..text_len { let c = text_buf[idx]; if c >= b'A' && c <= b'Z' { @@ -456,25 +456,27 @@ pub fn generate_slug<'a>( out_len -= 1; } - // Deduplicate via slug_counts - // TODO(port): exact bun_collections::StringHashMap entry/get_or_put API — Zig used getOrPut - // with a borrowed key then duped it on first insert. Here we look up first, then insert an - // owned key on miss; map is assumed to own its keys (so Drop frees them). - if let Some(value) = slug_counts.get_mut(&text_buf[..out_len]) { - // Already seen — append -N suffix - let count = *value + 1; - *value = count; - text_buf.truncate(out_len); - text_buf.push(b'-'); - - let mut dec_buf = bun_core::fmt::ItoaBuf::new(); - text_buf.extend_from_slice(bun_core::fmt::itoa(&mut dec_buf, count)); - return text_buf.as_slice(); + // Deduplicate via slug_counts. Zig: single `getOrPut` with the borrowed + // key, duping it on first insert; `StringHashMap::get_or_put` does the + // same in one probe (the map owns its keys, value starts at 0 on miss). + let Ok(gop) = slug_counts.get_or_put(&text_buf[..out_len]) else { + // Zig: `catch return base_slug` + return &text_buf[..out_len]; + }; + if !gop.found_existing { + // First occurrence — value initialised to 0 by `get_or_put`. + return &text_buf[..out_len]; } - // First occurrence — store an owned key so the map owns it - slug_counts.put_assume_capacity(&text_buf[..out_len], 0); - &text_buf[..out_len] + // Already seen — append -N suffix + let count = *gop.value_ptr + 1; + *gop.value_ptr = count; + text_buf.truncate(out_len); + text_buf.push(b'-'); + + let mut dec_buf = bun_core::fmt::ItoaBuf::new(); + text_buf.extend_from_slice(bun_core::fmt::itoa(&mut dec_buf, count)); + text_buf.as_slice() } /// Shared heading-ID state used by all renderers (HTML, React AST, JS callbacks). diff --git a/src/md/html_renderer.rs b/src/md/html_renderer.rs index b6076083baa9..a459527dde7e 100644 --- a/src/md/html_renderer.rs +++ b/src/md/html_renderer.rs @@ -12,7 +12,7 @@ pub(crate) struct HtmlRenderer<'src> { // allocator dropped — non-AST crate uses global mimalloc pub src_text: &'src [u8], pub image_nesting_level: u32, - // PORT NOTE: was `&'src [u8]` borrowing parser content; owned now so the + // Owned (rather than `&'src [u8]` borrowing parser content) so the // RendererImpl trait does not entangle SpanDetail's lifetime with 'src. pub saved_img_title: Box<[u8]>, pub tag_filter: bool, @@ -159,7 +159,7 @@ impl<'src> HtmlRenderer<'src> { } if lang_end > info_beg { self.write(b" class=\"language-"); - // PORT NOTE: reshaped for borrowck — capture slice before &mut self call. + // Capture the slice before the &mut self call. let src_text = self.src_text; self.write_with_entity_decoding(&src_text[info_beg..lang_end]); self.write(b"\""); @@ -340,7 +340,7 @@ impl<'src> HtmlRenderer<'src> { self.write(b"\""); if !self.saved_img_title.is_empty() { self.write(b" title=\""); - // PORT NOTE: reshaped for borrowck — take field before &mut self call. + // Take the field before the &mut self call. let title = core::mem::take(&mut self.saved_img_title); self.write_title_with_escapes(&title); self.write(b"\""); @@ -643,8 +643,6 @@ impl<'src> HtmlRenderer<'src> { // Static helpers // ======================================== -// PORT NOTE: Zig's manual `*anyopaque + VTable` is collapsed into the -// `RendererImpl` trait (see types.rs); the static VTABLE is no longer needed. impl RendererImpl for HtmlRenderer<'_> { fn enter_block(&mut self, block_type: BlockType, data: u32, flags: u32) -> JsResult<()> { HtmlRenderer::enter_block(self, block_type, data, flags); diff --git a/src/md/inlines.rs b/src/md/inlines.rs index f29d219f913e..ef3c3c9c6c6a 100644 --- a/src/md/inlines.rs +++ b/src/md/inlines.rs @@ -145,10 +145,14 @@ impl Parser<'_> { merged_len -= 1; } } - // PORT NOTE: reshaped for borrowck — Zig passes self.buffer.items directly into a + // reshaped for borrowck — Zig passes self.buffer.items directly into a // &self method; Rust take()s the Vec out so process_inline_content (and any recursive - // call via process_link) gets a fresh self.buffer to scribble on without aliasing. - // TODO(port): verify recursive calls (via process_link) do not need the parent buffer. + // call via process_link) gets a fresh self.buffer to scribble on without + // aliasing. Verified: nothing reachable from process_inline_content + // (including recursive process_link -> process_inline_content calls, which + // operate solely on the `content`/label slices) touches `self.buffer`; its + // other users (ref-def merging in blocks.rs/ref_defs.rs) run during the + // block phase, never re-entrantly from here. let merged = core::mem::take(&mut self.buffer); let ret = self.process_inline_content(&merged[..merged_len], block_lines[0].beg); self.buffer = merged; @@ -183,7 +187,7 @@ impl Parser<'_> { self.resolve_emphasis_delimiters(); // Copy resolved delimiters locally (recursive calls may modify emph_delims) - // PORT NOTE: Zig dupe() catch OOM → emit plain text fallback; Rust Vec::clone aborts on OOM. + // Zig dupe() catch OOM → emit plain text fallback; Rust Vec::clone aborts on OOM. let resolved: Vec = self.emph_delims.clone(); // Phase 2: Emit content using resolved emphasis info @@ -672,7 +676,7 @@ impl Parser<'_> { /// Resolve emphasis delimiters using the CommonMark algorithm. pub fn resolve_emphasis_delimiters(&mut self) { - // PORT NOTE: reshaped for borrowck — index directly into self.emph_delims + // reshaped for borrowck — index directly into self.emph_delims // instead of binding `delims` + `opener` aliases. let len = self.emph_delims.len(); if len == 0 { diff --git a/src/md/links.rs b/src/md/links.rs index 92a89cde4fd2..5cd78b6e65b9 100644 --- a/src/md/links.rs +++ b/src/md/links.rs @@ -3,8 +3,8 @@ use crate::inlines; use crate::parser::{self, Parser}; use crate::types::{OFF, SpanDetail, SpanType, TextType}; -// PORT NOTE: aliases for the real `SpanType` / `SpanDetail` types (the Zig -// original named them `Span` / `SpanAttrs`). +// Aliases for the real `SpanType` / `SpanDetail` types (named `Span` / +// `SpanAttrs` in the original implementation). type Span = SpanType; type SpanAttrs<'a> = SpanDetail<'a>; type Off = OFF; @@ -496,8 +496,8 @@ impl Parser<'_> { }; pos += 1; if let Some(ref_def) = self.lookup_ref_def(ref_label) { - // PORT NOTE: reshaped for borrowck — clone owned dest/title so the - // &self borrow from lookup_ref_def is dropped before &mut self calls. + // Clone the owned dest/title so the &self borrow from + // lookup_ref_def is dropped before &mut self calls. let dest: Box<[u8]> = Box::from(&ref_def.dest[..]); let title: Box<[u8]> = Box::from(&ref_def.title[..]); // Link nesting prohibition @@ -523,7 +523,8 @@ impl Parser<'_> { }; if char_after_label != b'[' { if let Some(ref_def) = self.lookup_ref_def(label) { - // PORT NOTE: reshaped for borrowck — clone owned dest/title. + // Clone the owned dest/title so the &self borrow from + // lookup_ref_def is dropped before &mut self calls. let dest: Box<[u8]> = Box::from(&ref_def.dest[..]); let title: Box<[u8]> = Box::from(&ref_def.title[..]); // Link nesting prohibition diff --git a/src/md/parser.rs b/src/md/parser.rs index a7ae8aadec29..3a90c2503d48 100644 --- a/src/md/parser.rs +++ b/src/md/parser.rs @@ -28,7 +28,7 @@ pub use super::inlines::{EmphDelim, HtmlScanMemo, MAX_EMPH_MATCHES}; pub use super::ref_defs::RefDef; /// Parser context holding all state during parsing. -// PORT NOTE: `text` is a caller-owned borrow for the parser's lifetime. +// `text` is a caller-owned borrow for the parser's lifetime. // PORTING.md's mechanical-port guidance was "no struct lifetimes", but raw-ptr // here would obscure every `ch()` call; one obvious `'a` is the honest mapping. pub struct Parser<'a> { @@ -52,10 +52,12 @@ pub struct Parser<'a> { // Dynamic arrays pub marks: Vec, pub containers: Vec, - // TODO(port): Zig uses `ArrayListAlignedUnmanaged(u8, .@"4")` — 4-byte - // alignment is load-bearing for `BlockHeader` reinterpretation via - // `getBlockHeaderAt`. Wrap in an aligned-vec newtype or store - // `Vec` and byte-view it. + // Zig used `ArrayListAlignedUnmanaged(u8, .@"4")`: 4-byte alignment is + // load-bearing for the `BlockHeader` reinterpretation in + // `get_block_header_at`. Here the invariant rests on (a) offsets being + // padded to a multiple of 4 by every writer and (b) the global allocator + // returning >=16-byte-aligned bases; `get_block_header_at` + // debug-asserts it on every access. pub block_bytes: Vec, pub buffer: Vec, pub emph_delims: Vec, @@ -128,12 +130,11 @@ impl Default for BlockHeader { /// `Parser.Error` in Zig is `bun.JSError || bun.StackOverflow`, i.e. the union /// of `{ OutOfMemory, JSError, JSTerminated }` with `{ StackOverflow }`. -// TODO(port): narrow error set — `bun_jsc::JsError` already covers the first -// three; could be `enum { Js(JsError), StackOverflow }` instead. -// TODO(port): thiserror/strum not in workspace deps — derive dropped, hand-roll if needed. +// (`bun_jsc::JsError` covers the first three, but the md crate sits below +// `bun_jsc` in the layering, so the variants stay flat here.) pub type Error = ParserError; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::IntoStaticStr)] pub enum ParserError { OutOfMemory, JSError, @@ -144,9 +145,8 @@ pub enum ParserError { bun_core::oom_from_alloc!(ParserError); impl From for bun_core::Error { - fn from(_e: ParserError) -> Self { - // TODO(port): wire IntoStaticStr → interned tag; bun_core::err! only accepts ident - bun_core::err!(ParserError) + fn from(e: ParserError) -> Self { + bun_core::err!(from e) } } @@ -156,8 +156,6 @@ impl<'a> Parser<'a> { // to a multiple of `align_of::()`, and the global allocator returns // blocks aligned to at least `align_of::()`, so the resulting pointer is // 4-byte aligned (asserted below). The buffer holds an initialized BlockHeader there. - // TODO(port): borrowck — this returns &mut into self.block_bytes while other - // &mut self borrows may be live at call sites; may need raw *mut. unsafe { let ptr = self .block_bytes diff --git a/src/md/ref_defs.rs b/src/md/ref_defs.rs index ee69dac29d9d..9c9884aac504 100644 --- a/src/md/ref_defs.rs +++ b/src/md/ref_defs.rs @@ -17,8 +17,6 @@ pub struct RefDef { pub title: Box<[u8]>, // raw title (slice of source) } -// PORT NOTE: Zig anonymous return structs `?struct { ... }` are mapped to small -// named structs borrowing from the `text` parameter (BORROW_PARAM class). pub struct ParsedRefDef<'a> { pub end_pos: usize, pub label: &'a [u8], @@ -41,8 +39,8 @@ impl Parser<'_> { /// strip leading/trailing whitespace, case-fold. pub fn normalize_label(&mut self, raw: &[u8]) -> Vec { // Collapse whitespace and apply Unicode case folding (per CommonMark §6.7) - // PORT NOTE: Zig returned `raw` on alloc failure; Rust Vec aborts on OOM, so - // the `catch return raw` paths are dropped. + // The Zig original returned `raw` on alloc failure; Rust Vec aborts on + // OOM, so those fallback paths are dropped. let mut result: Vec = Vec::new(); let mut in_ws = true; // skip leading whitespace let mut i: usize = 0; @@ -88,8 +86,8 @@ impl Parser<'_> { } /// Look up a reference definition by label (case-insensitive, whitespace-normalized). - // PORT NOTE: returns `Option<&RefDef>` instead of by-value copy; Zig RefDef was - // three borrowed slices (Copy), Rust RefDef owns its buffers. + // Returns `Option<&RefDef>` instead of a by-value copy: the Zig RefDef was + // three borrowed slices (Copy), the Rust RefDef owns its buffers. pub fn lookup_ref_def(&mut self, raw_label: &[u8]) -> Option<&RefDef> { if raw_label.is_empty() || self.ref_defs.is_empty() { return None; @@ -339,10 +337,10 @@ impl Parser<'_> { pub fn build_ref_def_hashtable(&mut self) -> Result<(), AllocError> { let mut off: usize = 0; - // PORT NOTE: reshaped for borrowck — take a raw pointer to block_bytes so we - // can call &mut self methods (normalize_label, parse_ref_def via self.buffer) - // while iterating the byte buffer. The Zig code mutates headers in-place via - // pointer casts; we preserve that with raw pointer arithmetic. + // Take a raw pointer to block_bytes so we can call &mut self methods + // (normalize_label, parse_ref_def via self.buffer) while iterating the + // byte buffer. The Zig code mutates headers in-place via pointer + // casts; we preserve that with raw pointer arithmetic. let bytes_ptr = self.block_bytes.as_mut_ptr(); let bytes_len = self.block_bytes.len(); @@ -404,8 +402,8 @@ impl Parser<'_> { .extend_from_slice(&self.text[vline.beg as usize..vline.end as usize]); } - // PORT NOTE: reshaped for borrowck — move merged buffer out of self so - // parse_ref_def/normalize_label can borrow &self/&mut self. + // Move the merged buffer out of self so parse_ref_def/normalize_label + // can borrow &self/&mut self. let merged = core::mem::take(&mut self.buffer); let mut pos: usize = 0; let mut lines_consumed: u32 = 0; diff --git a/src/md/types.rs b/src/md/types.rs index 5d082a6f6c90..41dc28e448d0 100644 --- a/src/md/types.rs +++ b/src/md/types.rs @@ -1,4 +1,5 @@ -// TODO(port): bun_jsc::JsResult missing from lower-tier stub surface — local alias. +// The md crate sits below `bun_jsc` in the layering, so `bun_jsc::JsResult` +// is unreachable here; this local alias plays the same role. pub type JsResult = Result; /// Offset into the input document. @@ -133,7 +134,7 @@ pub struct WikilinkDetail<'a> { /// Renderer interface. The parser calls these methods to produce output. // -// PORT NOTE: Zig's `*anyopaque + *const VTable` manual fat-pointer is collapsed +// Zig's `*anyopaque + *const VTable` manual fat-pointer is collapsed // into `&mut dyn RendererImpl`. LIFETIMES.tsv classified `ptr` as // `&'a mut dyn RendererImpl` (BORROW_PARAM) and `vtable` as `&'static VTable` // (STATIC); the trait object encodes both. @@ -174,8 +175,7 @@ impl<'a> Renderer<'a> { } /// Detail data for span events (links, images, wikilinks). -// TODO(port): lifetime — href/title borrow from the source text; could thread -// an arena `'bump` lifetime instead. +/// `href`/`title` borrow from the source text. #[derive(Copy, Clone)] pub struct SpanDetail<'a> { pub href: &'a [u8], @@ -227,7 +227,7 @@ impl<'a> SpanDetail<'a> { /// An attribute is a string that may contain embedded entities. /// The text is split into substrings, each with a type (normal or entity). -// TODO(port): lifetime — substr slices borrow from parser-owned buffers. +/// The substr slices borrow from parser-owned buffers. #[derive(Copy, Clone)] pub struct Attribute<'a> { /// Slices into the source text, one per substring. @@ -235,7 +235,7 @@ pub struct Attribute<'a> { pub substr_types: &'a [SubstrType], } -// PORT NOTE: Zig nests `SubstrType`/`SubstrOffset` inside `Attribute`; Rust has +// Zig nests `SubstrType`/`SubstrOffset` inside `Attribute`; Rust has // no nested type defs in structs, so they are hoisted to module scope. #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] @@ -327,7 +327,7 @@ pub struct Container { } /// Block flags stored in MD_BLOCK. -// PORT NOTE: Zig `packed struct(u32)` with bool fields + u28 padding. Not every +// Zig `packed struct(u32)` with bool fields + u28 padding. Not every // field is `bool` (padding), so per PORTING.md this is a transparent newtype // with manual shift accessors rather than `bitflags!`. #[repr(transparent)] diff --git a/src/md/unicode.rs b/src/md/unicode.rs index 2b30ff4ba9da..b48a97966dce 100644 --- a/src/md/unicode.rs +++ b/src/md/unicode.rs @@ -1,6 +1,3 @@ -// PORT NOTE: Zig `u21` (codepoint) → `u32`; Zig `u2` (count) → `u8`. -// Rust has no arbitrary-bit-width integers; the next natural width is used. - pub(crate) struct FoldInfo { pub codepoints: [u32; 3], pub n_codepoints: u8, diff --git a/src/mimalloc_sys/mimalloc.rs b/src/mimalloc_sys/mimalloc.rs index 8565dc4ee6b3..6153b4089ec3 100644 --- a/src/mimalloc_sys/mimalloc.rs +++ b/src/mimalloc_sys/mimalloc.rs @@ -2,9 +2,9 @@ use core::ffi::{c_char, c_int, c_long, c_ulong, c_ulonglong, c_ushort, c_void}; -// PORT NOTE: `Option` below is the mimalloc `mi_option_t` enum (kept verbatim -// from the Zig). Nullable fn-pointer params therefore spell out -// `core::option::Option<...>` to avoid the shadow. +// `Option` below is the mimalloc `mi_option_t` enum, which shadows +// `core::option::Option` in this module. Nullable fn-pointer params therefore +// spell out `core::option::Option<...>` to avoid the shadow. unsafe extern "C" { /// No preconditions; returns null on failure. @@ -386,7 +386,7 @@ unsafe extern "C" { pub fn mi_thread_set_in_threadpool(); } -// PORT NOTE: kept name `Option` to match Zig; shadows `core::option::Option` in +// Named `Option` after mimalloc's `mi_option_t`; shadows `core::option::Option` in // this module (callers use `mimalloc::Option`). `enum(c_uint)` → `#[repr(u32)]` // (c_uint == u32 on all Bun targets; `#[repr(C)]` would give a signed c_int discriminant). #[repr(u32)] diff --git a/src/options_types/code_coverage_options.rs b/src/options_types/code_coverage_options.rs index a20853fc2896..040bed224d8b 100644 --- a/src/options_types/code_coverage_options.rs +++ b/src/options_types/code_coverage_options.rs @@ -43,9 +43,8 @@ pub struct CodeCoverageOptions { impl Default for CodeCoverageOptions { fn default() -> Self { Self { - // TODO(port): Zig `!bun.Environment.allow_assert` (allow_assert = isDebug || is_canary || isTest); - // mapped to `!cfg!(debug_assertions)` — may want a `bun_core::Environment::ALLOW_ASSERT` const. - skip_test_files: !cfg!(debug_assertions), + // Zig: `!bun.Environment.allow_assert`. + skip_test_files: !bun_core::env::ALLOW_ASSERT, reporters: Reporters { text: true, lcov: false, diff --git a/src/options_types/compile_target.rs b/src/options_types/compile_target.rs index c7869cec0740..255fa5354e9f 100644 --- a/src/options_types/compile_target.rs +++ b/src/options_types/compile_target.rs @@ -97,7 +97,6 @@ pub enum ParseError { #[error("InvalidTarget")] InvalidTarget, } -// TODO(port): impl From for bun_core::Error impl CompileTarget { pub fn eql(&self, other: &CompileTarget) -> bool { @@ -113,17 +112,11 @@ impl CompileTarget { } pub fn to_npm_registry_url<'a>(&self, buf: &'a mut [u8]) -> Result<&'a [u8], bun_core::Error> { - // TODO(port): narrow error set if let Some(url) = env_var::BUN_COMPILE_TARGET_TARBALL_URL.get() { if strings::has_prefix(url, b"http://") || strings::has_prefix(url, b"https://") { - // TODO(port): lifetime — Zig returns the env var slice directly (`return url;`), - // which is not tied to `buf`. Could change the return type to allow returning a - // non-buf slice (e.g. Cow<'_, [u8]>). For now copy into buf without truncation. - if url.len() > buf.len() { - return Err(bun_core::err!("BufferTooSmall")); - } - buf[..url.len()].copy_from_slice(url); - return Ok(&buf[..url.len()]); + // Matches Zig (`return url;`): the env var slice is `&'static [u8]`, + // which outlives `'a`, so return it directly instead of copying into `buf`. + return Ok(url); } } @@ -135,7 +128,6 @@ impl CompileTarget { buf: &'a mut [u8], registry_url: &[u8], ) -> Result<&'a [u8], bun_core::Error> { - // TODO(port): narrow error set // Validate the target is supported before building URL if !self.is_supported() { return Err(bun_core::err!("UnsupportedTarget")); @@ -400,8 +392,6 @@ impl CompileTarget { } else if strings::contains(input, b"wasm") { bun_core::err_generic!("invalid target, WebAssembly is not supported. Sorry!"); } else if strings::contains(input, b"v") { - // PORT NOTE: Zig used a comptime-concat format string with VERSION_STRING. - // `format_args!` requires a literal; pass the version as a runtime arg. bun_core::err_generic!( "Please pass a complete version number to --target. For example, --target=bun-v{}", Environment::VERSION_STRING, diff --git a/src/options_types/context.rs b/src/options_types/context.rs index 5e7a5b25eb0e..b9509319146e 100644 --- a/src/options_types/context.rs +++ b/src/options_types/context.rs @@ -3,9 +3,7 @@ //! the parsed-options shape without importing the CLI itself. //! //! `create()` (which calls `Arguments.parse`) and the `global_cli_ctx`/ -//! `context_data` storage stay in `cli.rs`; they are forward-aliased onto -//! `ContextData` below so call sites that write `Command::ContextData::create()` -//! keep working. +//! `context_data` storage stay in `cli.rs`. use crate::schema::api; use bun_collections::ArrayHashMap; @@ -16,10 +14,8 @@ use crate::compile_target::CompileTarget; use crate::global_cache::GlobalCache; use crate::offline_mode::OfflineMode; -// TODO(port): every `[]const u8` / `[]const []const u8` struct field below is a -// proc-lifetime CLI string (no `deinit`, populated once from argv/bunfig and -// never freed). Ported as `Box<[u8]>` / `Vec>` for now; may retype to -// `&'static [u8]` once the CLI parser leaks into a bump arena. +// Every `Box<[u8]>` / `Vec>` struct field below is a proc-lifetime +// CLI string: populated once from argv/bunfig during startup and never freed. pub struct ContextData { pub start_time: i128, @@ -30,7 +26,6 @@ pub struct ContextData { // SAFETY: written exactly once in single-threaded CLI startup; thereafter // always non-null for the process lifetime. Callers deref via `ctx.log()`. pub log: *mut bun_ast::Log, - // PORT NOTE: `std.mem.Allocator param` deleted (global mimalloc). pub positionals: Vec>, pub passthrough: Vec>, pub install: Option>, @@ -183,21 +178,16 @@ impl ContextData { unsafe { &*self.log } } - /// `Arguments.parse` lives in `cli/`; forward-aliased so - /// `Command::ContextData::create(...)` keeps working. - // TODO(port): Zig was `pub const create = bun.cli.Command.createContextData;` - // — Rust cannot re-export an associated fn; TODO(port): add a thin - // delegating `pub fn create(...)` here once `bun_cli` exists, or invert the - // alias direction (cli re-exports this type). - pub const CREATE_SEE_CLI: () = (); } +// `create()` (Zig: `Command.ContextData.create`) lives in the CLI crate +// (`bun_runtime::cli::command::create_context_data`), which depends on this +// crate — a delegating fn here would invert the dependency. + pub struct BundlerOptions { pub outdir: Box<[u8]>, pub outfile: Box<[u8]>, - // TODO(port): was `[:0]const u8` (NUL-terminated); decide owned ZStr repr. pub metafile: Box<[u8]>, - // TODO(port): was `[:0]const u8` (NUL-terminated); decide owned ZStr repr. pub metafile_md: Box<[u8]>, pub root_dir: Box<[u8]>, pub public_path: Box<[u8]>, @@ -294,9 +284,6 @@ impl Default for BundlerOptions { } pub type Context<'a> = &'a mut ContextData; -// TODO(port): Zig `*ContextData` is passed everywhere as a long-lived handle; -// the borrow lifetime above may need to become `*mut ContextData` at call sites -// that re-enter the global ctx. // ────────────────────────────────────────────────────────────────────────── // Process-global CLI context handle. diff --git a/src/options_types/jsx.rs b/src/options_types/jsx.rs index 7af24aac3fd4..a7c54a7ff8d2 100644 --- a/src/options_types/jsx.rs +++ b/src/options_types/jsx.rs @@ -215,7 +215,7 @@ impl Default for Pragma { impl Pragma { pub fn hash_for_runtime_transpiler(&self, hasher: &mut bun_wyhash::Wyhash) { - // PORT NOTE: spec options.zig:1213 takes `*std.hash.Wyhash`, which is the + // The original (options.zig:1213) takes `*std.hash.Wyhash`, which is the // algorithm behind `bun.hash` — distinct from `bun.Wyhash11`. Using // `Wyhash11` would yield a different cache key than the Zig path. for factory in self.factory.iter() { diff --git a/src/parsers/json.rs b/src/parsers/json.rs index 5ff04381b52f..e1588e5b284b 100644 --- a/src/parsers/json.rs +++ b/src/parsers/json.rs @@ -23,14 +23,14 @@ const LEXER_DEBUGGER_WORKAROUND: bool = false; mod hash_map_pool { use super::*; - // Zig: std.HashMap(u64, void, IdentityContext, 80) - // TODO(port): identity-hash u64 set with 80% max-load — verify bun_collections has an - // identity-hasher variant; otherwise add one. Using HashMap for now. - pub(super) type HashMap = bun_collections::HashMap; + // Zig: std.HashMap(u64, void, IdentityContext, 80) — keys are pre-hashed + // (`EString::hash()`), so identity hashing avoids re-hashing the hash. + pub(super) type HashMap = + bun_collections::HashMap>; // Zig used a threadlocal SinglyLinkedList as a freelist. - // PORT NOTE: reshaped for borrowck — Rust thread_local + Vec freelist; - // a borrowed Node* across calls is replaced by an owned HashMap moved out/in. + // Reshaped for borrowck: Rust thread_local + Vec freelist; a + // borrowed Node* across calls is replaced by an owned HashMap moved out/in. thread_local! { static LIST: RefCell> = const { RefCell::new(Vec::new()) }; } @@ -59,15 +59,26 @@ where { // Zig had: if @typeInfo(Type) == .pointer => @compileError — Rust's type system // already prevents passing a reference where a value is expected; no runtime check needed. + // + // Zig also asserted, when Ty == E.Object, that every property has + // key != null, value != null, initializer == null. Rust has no + // specialization, so that check lives in `debug_assert_json_object_shape`, + // called at the (single) E::Object construction site below. + Expr::init(t, loc) +} - #[cfg(debug_assertions)] - { - // TODO(port): the Zig code asserted, when Ty == E.Object, that every property - // has key.is_some(), value.is_some(), initializer.is_none(). Requires - // specialization or a method on the ExprInit trait. +/// Zig `newExpr`'s `Type == E.Object` debug assertion (json.zig:45-60): JSON +/// objects must have every property fully keyed/valued with no initializer. +#[inline] +fn debug_assert_json_object_shape(properties: &[G::Property]) { + if cfg!(debug_assertions) { + for prop in properties { + // json should never have an initializer set + debug_assert!(prop.initializer.is_none()); + debug_assert!(prop.key.is_some()); + debug_assert!(prop.value.is_some()); + } } - - Expr::init(t, loc) } // ────────────────────────────────────────────────────────────────────────── @@ -94,7 +105,7 @@ where pub struct JSONLikeParser<'a, 'bump> { pub lexer: js_lexer::Lexer<'a, 'bump>, - // PORT NOTE — Stacked Borrows: the Zig spec stores a second `*logger.Log` + // Stacked Borrows: the Zig spec stores a second `*logger.Log` // here (json.zig:103). In Rust that would alias the lexer's `*mut Log`; a // live `&'a mut Log` field would invalidate the lexer's SharedReadWrite tag // on first use (UB on the next lexer error). All log writes route through @@ -119,7 +130,6 @@ where source_: &'a bun_ast::Source, log: &'a mut bun_ast::Log, ) -> Result { - // TODO(port): narrow error set Self::init_with_list_allocator(opts, bump, bump, source_, log) } @@ -130,7 +140,6 @@ where source_: &'a bun_ast::Source, log: &'a mut bun_ast::Log, ) -> Result { - // TODO(port): narrow error set Expr::data_store_assert(); Stmt::data_store_assert(); Ok(Self { @@ -204,7 +213,7 @@ where T::TOpenBracket => { self.lexer.next()?; let mut is_single_line = !self.lexer.has_newline_before; - // PORT NOTE: Zig grew an `ArrayList(Expr)` in `list_allocator` and + // Zig grew an `ArrayList(Expr)` in `list_allocator` and // `moveFromList`-ed it. The Rust `Vec` is `Vec`-backed (global // allocator), so build a `Vec` directly and hand it off. let mut exprs: Vec = Vec::new(); @@ -246,11 +255,11 @@ where T::TOpenBrace => { self.lexer.next()?; let mut is_single_line = !self.lexer.has_newline_before; - // PORT NOTE: see TOpenBracket note — `Vec` is `Vec`-backed. + // see TOpenBracket note — `Vec` is `Vec`-backed. let mut properties: Vec = Vec::new(); // errdefer properties.deinit() — dropped automatically on `?`. - // PORT NOTE: reshaped for borrowck — Zig used `void`/`*Node` when + // reshaped for borrowck — Zig used `void`/`*Node` when // json_warn_duplicate_keys is false; Rust uses Option to keep one code path. let warn_dup = self.opts.json_warn_duplicate_keys; let mut duplicates: Option = if warn_dup { @@ -263,7 +272,7 @@ where hash_map_pool::release(map); } }); - // PORT NOTE: Zig `defer` — scopeguard runs on both success and error paths. + // Zig `defer` — scopeguard runs on both success and error paths. while self.lexer.token != T::TCloseBrace { if !properties.is_empty() { @@ -328,6 +337,7 @@ where is_single_line = false; } self.lexer.expect(T::TCloseBrace)?; + debug_assert_json_object_shape(&properties); Ok(new_expr( E::Object { properties: G::PropertyList::move_from_list(properties), @@ -340,7 +350,7 @@ where } _ => { if maybe_auto_quote { - // PORT NOTE: borrowck — capture `source` (a `&'a Source`, + // borrowck — capture `source` (a `&'a Source`, // Copy) and the lexer's `*mut Log` before reassigning // `self.lexer`. The new lexer is built over the *same* raw // log pointer so there is still exactly one provenance @@ -367,7 +377,6 @@ where } pub fn parse_maybe_trailing_comma(&mut self, closer: T) -> Result { - // TODO(port): narrow error set let comma_range = self.lexer.range(); self.lexer.expect(T::TComma)?; @@ -403,7 +412,7 @@ where pub struct PackageJSONVersionChecker<'a, 'bump> { pub lexer: js_lexer::Lexer<'a, 'bump>, pub source: &'a bun_ast::Source, - // PORT NOTE — Stacked Borrows: no separate `log` field; route through + // Stacked Borrows: no separate `log` field; route through // `self.lexer.log_mut()` (single provenance chain — see `JSONLikeParser`). pub bump: &'bump Bump, pub depth: usize, @@ -412,7 +421,7 @@ pub struct PackageJSONVersionChecker<'a, 'bump> { pub found_version_buf: [u8; 1024], pub found_name_buf: [u8; 1024], - // PORT NOTE: reshaped for borrowck — Zig stored `found_name: []const u8` as a + // reshaped for borrowck — Zig stored `found_name: []const u8` as a // self-referential slice into `found_name_buf`. Rust stores the length instead; // use `.found_name()` / `.found_version()` accessors. found_name_len: usize, @@ -446,7 +455,6 @@ where source: &'a bun_ast::Source, log: &'a mut bun_ast::Log, ) -> Result { - // TODO(port): narrow error set Ok(Self { lexer: js_lexer::Lexer::init(log, source, bump, PKG_JSON_OPTS)?, bump, @@ -536,7 +544,7 @@ where T::TOpenBrace => { self.lexer.next()?; self.depth += 1; - // PORT NOTE: Zig `defer p.depth -= 1` — wrap body in a closure so `?` + // Zig `defer p.depth -= 1` — wrap body in a closure so `?` // returns here and depth is decremented exactly once on every exit path // (Ok, Err, early break). scopeguard cannot hold `&mut self.depth` while // the body re-borrows `&mut self`. @@ -608,7 +616,6 @@ where } pub fn parse_maybe_trailing_comma(&mut self, closer: T) -> Result { - // TODO(port): narrow error set let comma_range = self.lexer.range(); self.lexer.expect(T::TComma)?; @@ -660,7 +667,7 @@ macro_rules! impl_to_ast_int { } )*}; } -// PORT NOTE: `u8` is intentionally omitted so the generic `impl for [T]` +// `u8` is intentionally omitted so the generic `impl for [T]` // / `[T; N]` does NOT match byte arrays — Zig special-cases `Array.child == u8` // (json.zig:557-565) to emit `E::String`, not `E::Array`. See dedicated // `[u8]` / `[u8; N]` impls below. @@ -751,30 +758,10 @@ impl ToAst for bun_core::Error { } } -// TODO(port): proc-macro — Zig's `.@"struct"` arm iterates `@typeInfo(Type).Struct.fields` -// and emits an `E.Object` keyed by field name. Provide `#[derive(ToAst)]` that -// expands to: -// impl ToAst for Foo { -// fn to_ast(&self, bump: &Bump) -> Result { -// let mut properties = Vec::::with_capacity(bump, N); -// properties.push_assume_capacity(G::Property { -// key: Some(Expr::init(E::String { data: b"field_name" }, Loc::EMPTY)), -// value: Some(self.field_name.to_ast(bump)?), -// ..Default::default() -// }); -// ... -// Ok(Expr::init(E::Object { properties, is_single_line: N <= 1, .. }, Loc::EMPTY)) -// } -// } -// -// TODO(port): proc-macro — Zig's `.@"enum"` arm validates the discriminant via -// `intToEnum` (returns null on failure) then emits `@tagName(value)` as a string. -// Map to `#[derive(strum::IntoStaticStr)]` + `<&'static str>::from(*self).as_bytes().to_ast()`. -// -// TODO(port): proc-macro — Zig's `.@"union"` arm (tagged union) constructs a -// single-field anonymous struct `{ : payload }` and recurses. -// In Rust this is the natural shape of `enum` payloads; the derive should emit -// `match self { Variant(v) => /* { "Variant": v } */ }`. +// Zig's `toAST` `.@"struct"` / `.@"enum"` / `.@"union"` arms (field-iterating +// E.Object emission, `@tagName` strings, single-variant-object unions) have no +// Rust equivalent without a derive macro, and no current call site converts a +// struct/enum value — only the primitive/slice impls above are used. // ────────────────────────────────────────────────────────────────────────── // Parser option presets @@ -825,11 +812,9 @@ const PACKAGE_JSON_OPTS: js_lexer::JSONOptions = js_lexer::JSONOptions { // ────────────────────────────────────────────────────────────────────────── -// TODO(port): these were `var` (mutable file-scope) in Zig because Expr.Data -// stores `*E.Object` etc. Never mutated — `RacyCell` only because +// These were `var` (mutable file-scope) in Zig because Expr.Data stores +// `*E.Object` etc. Never mutated — `RacyCell` only because // `StoreRef::from_raw` wants a `*mut T` and the payload types are `!Sync`. -// Could prefer `Expr::Data` constructors that don't need a backing static -// (e.g. inline empty-object sentinel). static EMPTY_OBJECT: bun_core::RacyCell = bun_core::RacyCell::new(E::Object::EMPTY); static EMPTY_ARRAY: bun_core::RacyCell = bun_core::RacyCell::new(E::Array::EMPTY); static EMPTY_STRING: bun_core::RacyCell = bun_core::RacyCell::new(E::String::EMPTY); @@ -865,7 +850,6 @@ pub fn parse( log: &mut bun_ast::Log, bump: &Bump, ) -> Result { - // TODO(port): narrow error set let mut parser = JSONLikeParser::init(JSON_OPTS, bump, source, log)?; match source.contents.len() { // This is to be consisntent with how disabled JS files are handled @@ -910,7 +894,6 @@ pub fn parse_package_json_utf8( log: &mut bun_ast::Log, bump: &Bump, ) -> Result { - // TODO(port): narrow error set let len = source.contents.len(); match len { @@ -1001,7 +984,6 @@ pub fn parse_package_json_utf8_with_opts_rt( log: &mut bun_ast::Log, bump: &Bump, ) -> Result { - // TODO(port): narrow error set let len = source.contents.len(); match len { @@ -1071,7 +1053,6 @@ pub fn parse_utf8( log: &mut bun_ast::Log, bump: &Bump, ) -> Result { - // TODO(port): narrow error set parse_utf8_impl::(source, log, bump) } @@ -1081,7 +1062,6 @@ pub fn parse_utf8_impl( log: &mut bun_ast::Log, bump: &Bump, ) -> Result { - // TODO(port): narrow error set let len = source.contents.len(); match len { @@ -1133,7 +1113,6 @@ pub fn parse_for_macro( log: &mut bun_ast::Log, bump: &Bump, ) -> Result { - // TODO(port): narrow error set match source.contents.len() { // This is to be consisntent with how disabled JS files are handled 0 => { @@ -1187,7 +1166,6 @@ pub fn parse_for_bundling( log: &mut bun_ast::Log, bump: &Bump, ) -> Result { - // TODO(port): narrow error set match source.contents.len() { // This is to be consisntent with how disabled JS files are handled 0 => { @@ -1250,7 +1228,6 @@ pub fn parse_env_json( log: &mut bun_ast::Log, bump: &Bump, ) -> Result { - // TODO(port): narrow error set match source.contents.len() { // This is to be consisntent with how disabled JS files are handled 0 => { @@ -1319,7 +1296,6 @@ pub fn parse_ts_config( log: &mut bun_ast::Log, bump: &Bump, ) -> Result { - // TODO(port): narrow error set match source.contents.len() { // This is to be consisntent with how disabled JS files are handled 0 => { diff --git a/src/parsers/json_lexer.rs b/src/parsers/json_lexer.rs index f7d5a643f1a8..8fb962309d52 100644 --- a/src/parsers/json_lexer.rs +++ b/src/parsers/json_lexer.rs @@ -153,7 +153,7 @@ impl Default for IndentInfo { /// the JSON parser reads directly (`token`, `number`, `has_newline_before`, /// `source`, `end`, `is_ascii_only`, `identifier`, `indent_info`). /// -/// PORT NOTE — borrowck/Stacked Borrows: Zig stored `log: *logger.Log` on +/// borrowck/Stacked Borrows: Zig stored `log: *logger.Log` on /// both the lexer *and* the parser (json.zig:103,119). The Rust port keeps a /// single `*mut Log` here as the sole provenance chain; the parser does **not** /// hold its own `&mut Log` (that would alias this pointer and any parser-side @@ -161,7 +161,7 @@ impl Default for IndentInfo { /// time the lexer reports an error). Parser-side log writes go through /// `log_mut()`. Matches the toml lexer's shape. pub struct Lexer<'a, 'bump> { - // PORT NOTE: raw ptr — see struct doc. + // raw ptr — see struct doc. log: *mut bun_ast::Log, pub source: &'a bun_ast::Source, bump: &'bump Bump, @@ -384,7 +384,7 @@ where #[cold] fn expected_string(&mut self, text: &str) -> LexResult { - // PORT NOTE: the `prev_token_was_await_keyword` branch is JS-only and + // the `prev_token_was_await_keyword` branch is JS-only and // dropped here. let r = self.range(); if self.source.contents.len() != self.start { @@ -602,10 +602,12 @@ where /// Zig: `decodeEscapeSequences`. Ported for the `is_json = true` arm only — /// legacy-octal, template `\r\n` normalization and tagged-template raw /// preservation are JS-only and dropped. - // TODO(port): JSON spec only permits `\" \\ \/ \b \f \n \r \t \uXXXX`; the - // Zig path additionally accepts `\0` `\v` `\x` `\u{…}` etc. and then errors - // post-hoc via `is_json` checks. Mirror that once the surrogate-pair - // handling in `bun_str` is wired. + // Lenient-escape parity with the Zig `is_json` arms: `\v`, `\x`, `\8`/`\9` + // are accepted (Zig has no `is_json` gate there); `\0`–`\7` (legacy octal) + // and `\u{…}` hit `syntaxError` in Zig's `is_json` path and likewise reach + // `syntax_error()` here via the fallthrough/non-hex-digit arms. Astral + // codepoints in raw text are surrogate-pair encoded by `push_codepoint` + // (`strings::push_codepoint_utf16`), matching Zig's tail switch. fn decode_escape_sequences( &mut self, _start: usize, @@ -969,7 +971,7 @@ where } // Handle bigint literals after the underscore-at-end check above; - // PORT NOTE: bigint `'n'` falls through to the identifier-start check + // bigint `'n'` falls through to the identifier-start check // below (T::TBigIntegerLiteral is JS-only and not in this token enum). // An identifier can't immediately follow a number. @@ -1032,7 +1034,7 @@ where let utf8 = strings::to_utf8_alloc(&buf); let contents: &'a [u8] = self.bump.alloc_slice_copy(&utf8); - // PORT NOTE: full Unicode `isIdentifier` validation omitted — JSON only + // full Unicode `isIdentifier` validation omitted — JSON only // recognises `true`/`false`/`null`; anything else is `t_identifier` // and the parser will reject it. self.identifier = contents; @@ -1374,7 +1376,7 @@ fn is_identifier_start(cp: CodePoint) -> bool { matches!(cp, 0x24 /* $ */ | 0x5F /* _ */) || (cp >= 'a' as CodePoint && cp <= 'z' as CodePoint) || (cp >= 'A' as CodePoint && cp <= 'Z' as CodePoint) - // PORT NOTE: full Unicode ID_Start table omitted — JSON only ever + // full Unicode ID_Start table omitted — JSON only ever // recognises `true`/`false`/`null` here, all ASCII. } diff --git a/src/parsers/toml.rs b/src/parsers/toml.rs index 549ab1223c06..395e1350bd60 100644 --- a/src/parsers/toml.rs +++ b/src/parsers/toml.rs @@ -19,7 +19,7 @@ use js_ast::e::SetError; pub struct TOML<'a> { pub lexer: Lexer<'a>, - // PORT NOTE: Zig also stores `log: *logger.Log` on the parser, but it is + // Zig also stores `log: *logger.Log` on the parser, but it is // never read — all logging goes through `lexer.log`. Dropped here to avoid // a second `&mut Log` borrow overlapping `lexer.log`. pub bump: &'a Bump, @@ -33,7 +33,6 @@ impl<'a> TOML<'a> { log: &'a mut bun_ast::Log, redact_logs: bool, ) -> Result, bun_core::Error> { - // TODO(port): narrow error set Ok(TOML { lexer: Lexer::init(log, source_, bump, redact_logs)?, bump, @@ -62,7 +61,6 @@ impl<'a> TOML<'a> { bump: &'a Bump, redact_logs: bool, ) -> Result { - // TODO(port): narrow error set match source_.contents.len() { // This is to be consisntent with how disabled JS files are handled 0 => { @@ -74,7 +72,7 @@ impl<'a> TOML<'a> { _ => {} } - // PORT NOTE: Zig copies the `Source` by value (`source_.*`). The Rust + // Zig copies the `Source` by value (`source_.*`). The Rust // `Lexer` borrows it (`&'a Source`) so `identifier`/`string_literal_slice` // can point into `source.contents` for `'a` without a self-referential // struct — no copy needed. @@ -84,7 +82,6 @@ impl<'a> TOML<'a> { } pub fn parse_maybe_trailing_comma(&mut self, closer: T) -> Result { - // TODO(port): narrow error set self.lexer.expect(T::t_comma)?; if self.lexer.token == closer { @@ -131,9 +128,9 @@ impl<'a> TOML<'a> { #[allow(clippy::mut_from_ref)] pub fn parse_key(&mut self, bump: &'a Bump) -> Result<&'a mut Rope, bun_core::Error> { - // TODO(port): lifetime — Zig returns `*Rope` allocated from `allocator` - // (a stack-fallback arena reset per-iteration). Here we allocate from the - // caller-provided bump and return `&mut Rope` borrowed from it. + // Zig returns `*Rope` allocated from `allocator` (a stack-fallback arena + // reset per-iteration). Here we allocate from the caller-provided bump + // and return `&mut Rope` borrowed from it. let rope: &mut Rope = bump.alloc(Rope { head: match self.parse_key_segment()? { Some(seg) => seg, @@ -167,7 +164,6 @@ impl<'a> TOML<'a> { } // SAFETY: `rope` points into `bump` and is live for this call; we are // the sole mutator. Raw pointers used to avoid stacked &mut reborrows. - // PORT NOTE: reshaped for borrowck unsafe { rope = (*rope).append(seg, bump)?; } @@ -184,7 +180,7 @@ impl<'a> TOML<'a> { .e_object() .expect("infallible: variant checked") .as_ptr(); - // TODO(port): `head` aliases into `root.data`; using raw pointer to mirror + // SAFETY: `head` aliases into `root.data`; using a raw pointer to mirror // the Zig `*E.Object` and sidestep overlapping &mut on `root`. // PERF(port): was stack-fallback (std.heap.stackFallback(@sizeOf(Rope)*6)) — @@ -301,7 +297,7 @@ impl<'a> TOML<'a> { debug_assert!(loc.start > 0); let start: u32 = u32::try_from(loc.start).expect("int cast"); // std.ascii.whitespace = { ' ', '\t', '\n', '\r', 0x0B, 0x0C } - // PORT NOTE: reshaped for borrowck — `self.source()` returns + // Reshaped for borrowck — `self.source()` returns // `&'a Source` (independent of `&self`), so bind it before // the `&mut self.lexer` borrow below. let src: &'a bun_ast::Source = self.source(); @@ -394,7 +390,7 @@ impl<'a> TOML<'a> { .e_object() .expect("infallible: variant checked") .as_ptr(); - // TODO(port): `obj` aliases into `expr.data`; raw pointer mirrors Zig. + // SAFETY: `obj` aliases into `expr.data`; raw pointer mirrors Zig. while self.lexer.token != T::t_close_brace { // SAFETY: `obj` points into the AST store and is live here. @@ -439,7 +435,7 @@ impl<'a> TOML<'a> { .e_array() .expect("infallible: variant checked") .as_ptr(); - // TODO(port): `array` aliases into `array_.data`; raw pointer mirrors Zig. + // SAFETY: `array` aliases into `array_.data`; raw pointer mirrors Zig. let bump = self.bump; self.lexer.allow_double_bracket = false; diff --git a/src/parsers/toml/lexer.rs b/src/parsers/toml/lexer.rs index be445c66a068..3069ea041dc2 100644 --- a/src/parsers/toml/lexer.rs +++ b/src/parsers/toml/lexer.rs @@ -47,7 +47,7 @@ pub enum T { } pub struct Lexer<'a> { - // PORT NOTE: borrowed (`&'a Source`) rather than owned so + // Borrowed (`&'a Source`) rather than owned so // `identifier`/`string_literal_slice` can borrow `&'a [u8]` from // `source.contents` without a self-referential struct. The Zig original // copied `Source` by value because Zig has no borrow checker; the Rust @@ -291,7 +291,8 @@ impl<'a> Lexer<'a> { i += 1; } } - // PORT NOTE: Zig discards `bytes` here (dead store); ported faithfully. + // `bytes` is intentionally discarded here, matching the + // Zig original's dead store. } // Store bigints as text to avoid precision loss; @@ -429,7 +430,6 @@ impl<'a> Lexer<'a> { // Filter out underscores; if underscore_count > 0 { let mut i: usize = 0; - // PORT NOTE: Zig handled OOM via if/else on allocator.alloc; arena alloc here is infallible. let bytes = self .bump .alloc_slice_fill_default::(text.len() - underscore_count); @@ -710,8 +710,8 @@ impl<'a> Lexer<'a> { } } - // PORT NOTE: reshaped for borrowck — capture slice bounds as indices - // instead of laundering a `&'a [u8]` through a raw pointer. On the fast + // Capture the slice bounds as indices instead of laundering + // a `&'a [u8]` through a raw pointer. On the fast // path we reslice immediately before `return`; on the slow path we // reslice after the loop and hand it straight to // `decode_escape_sequences` without stashing in `self` first. @@ -864,9 +864,6 @@ impl<'a> Lexer<'a> { text: &[u8], buf: &mut bun_alloc::ArenaVec<'a, u8>, ) -> Result<(), Error> { - // PORT NOTE: Zig copied `*buf_` into a local and `defer`-wrote it back. - // In Rust we operate on `buf` directly via &mut. - let iterator = strings::CodepointIterator::init(text); let mut iter = strings::Cursor::default(); while iterator.next(&mut iter) { @@ -1210,7 +1207,7 @@ impl<'a> Lexer<'a> { } }; - // PORT NOTE: reshaped for borrowck — compute range before borrowing `found` from source. + // Compute the range before borrowing `found` from source. let range = self.range(); self.add_range_error(range, format_args!("Unexpected {}", bstr::BStr::new(found))) } @@ -1297,8 +1294,8 @@ pub(crate) fn is_identifier_part(code_point: CodePoint) -> bool { | '-' | ':' ) && (0..=127).contains(&code_point) - // PORT NOTE: Zig matched CodePoint directly against char ranges; Rust requires - // bounding to ASCII before the byte cast above is sound. + // The `(0..=127)` bound is required for the byte cast above to be sound; + // the Zig original matched the CodePoint directly against char ranges. } #[inline] diff --git a/src/parsers/yaml.rs b/src/parsers/yaml.rs index 9e4ccf975eef..de7115352e3c 100644 --- a/src/parsers/yaml.rs +++ b/src/parsers/yaml.rs @@ -8,7 +8,7 @@ //! NOTE ON LABELED SWITCH: Zig's `label: switch (x) { ... continue :label y; }` is a //! state-machine loop. These are ported as `let mut __c = x; loop { match __c { ... } }` //! with `__c = y; continue;` for `continue :label y`. Each is marked -//! `// PORT NOTE: labeled-switch loop`. +//! `// labeled-switch loop`. use core::cmp::Ordering; use core::fmt; @@ -83,7 +83,7 @@ pub enum YamlParseError { bun_core::oom_from_alloc!(YamlParseError); impl From for bun_core::Error { - // PORT NOTE: Zig `YAML.ParseError` is an `error{...}` set, so callers + // Zig `YAML.ParseError` is an `error{...}` set, so callers // (e.g. `try YAML.parse(...)` in `ParseTask.getAST`) coerce it into the // wider inferred error union. Mirror that by mapping each variant to its // Zig-tag string via `bun.err!`, the same shape `json5::ExternalError` @@ -101,7 +101,10 @@ impl From for bun_core::Error { // Top-level free functions // ─────────────────────────────────────────────────────────────────────────── -pub fn parse(bump: &bun_alloc::Arena, input: &[Enc::Unit]) -> ParseResult { +pub fn parse<'i, Enc: Encoding>( + bump: &'i bun_alloc::Arena, + input: &'i [Enc::Unit], +) -> ParseResult<'i, Enc> { let mut parser: Parser = Parser::init(bump, input); match parser.parse() { @@ -110,7 +113,7 @@ pub fn parse(bump: &bun_alloc::Arena, input: &[Enc::Unit]) -> Par } } -pub fn print(stream: Stream, writer: &mut W) -> fmt::Result { +pub fn print(stream: Stream<'_, Enc>, writer: &mut W) -> fmt::Result { // Zig body (yaml.zig:44-53) constructs `Parser(encoding).Printer(@TypeOf(writer))` // and calls `printer.print()`. The `Printer` type is commented out in the spec // (yaml.zig:4927-5250) and operates on the removed `Node` enum, so any Zig @@ -846,7 +849,7 @@ impl StringRange { } } -// PORT NOTE: reshaped for borrowck — Zig captured `parser.pos` lazily via a +// reshaped for borrowck — Zig captured `parser.pos` lazily via a // pointer field; in Rust that pins an immutable borrow across mutating scans. // Capture only `off` and have callers pass the end `Pos` explicitly. #[derive(Clone, Copy)] @@ -861,6 +864,7 @@ impl StringRangeStart { } } +#[derive(Clone)] pub enum YamlString { Range(StringRange), List(Vec), @@ -906,7 +910,7 @@ impl YamlString { // drives scanning). We keep a raw pointer with SAFETY notes. // TODO(port): refactor whitespace_buf out of Parser or pass &mut explicitly. pub struct StringBuilder<'a, Enc: Encoding> { - // PORT NOTE: a `&'a mut Parser<'a, Enc>` field would tie the borrow lifetime + // a `&'a mut Parser<'a, Enc>` field would tie the borrow lifetime // to Parser's input lifetime (invariant under &mut), which both fails // borrowck at `string_builder()` and is exactly the aliasing the Zig already // had. Use a raw backref (the LIFETIMES.tsv BACKREF resolution). @@ -962,7 +966,7 @@ impl<'a, Enc: Encoding> StringBuilder<'a, Enc> { } fn drain_whitespace(&mut self) -> Result<(), AllocError> { - // PORT NOTE: reshaped for borrowck — take ownership of buf, process, clear. + // reshaped for borrowck — take ownership of buf, process, clear. let buf = core::mem::take(&mut self.parser_mut().whitespace_buf); let input = self.input(); for ws in &buf { @@ -1147,7 +1151,7 @@ pub enum FirstChar { Other, } -// PORT NOTE: Zig defined this inline inside scanPlainScalar. Hoisted to module +// Zig defined this inline inside scanPlainScalar. Hoisted to module // scope so methods can be `impl`'d. `parser` is `*mut` because the outer // `&mut self` in scan_plain_scalar drives scanning concurrently — see // LIFETIMES.tsv BACKREF. @@ -1291,7 +1295,7 @@ impl<'i, Enc: Encoding> ScalarResolverCtx<'i, Enc> { self.str_builder.append_whitespace_n_times(unit, n) } - // PORT NOTE: Zig `Keywords` enum (yaml.zig:1862-1887) was unused; not ported. + // Zig `Keywords` enum (yaml.zig:1862-1887) was unused; not ported. pub fn resolve( &mut self, @@ -1503,7 +1507,7 @@ impl<'i, Enc: Encoding> ScalarResolverCtx<'i, Enc> { let mut first = true; - // PORT NOTE: labeled-switch loop + // labeled-switch loop let mut __c = Enc::wide(parser!().next()); let (end, valid): (Pos, bool) = 'end: loop { match __c { @@ -1826,6 +1830,7 @@ impl NodeTag { } } +#[derive(Clone)] pub enum NodeScalar { Null, Boolean(bool), @@ -1917,10 +1922,59 @@ pub struct Document { // impl Drop for Document — Vec auto-drops; Expr is arena-backed. -pub struct Stream { +/// Should only be used with expressions created with the YAML parser. It assumes +/// only null, boolean, number, string, array, object are possible. It also only +/// does pointer comparison with arrays and objects (so exponential merges are avoided). +/// Operates on already-built `Expr`s, so it is independent of the input encoding. +fn yaml_merge_key_expr_eql(l: &Expr, r: &Expr) -> bool { + if core::mem::discriminant(&l.data) != core::mem::discriminant(&r.data) { + return false; + } + match (&l.data, &r.data) { + (ast::ExprData::ENull(_), _) => true, + (ast::ExprData::EBoolean(lb), ast::ExprData::EBoolean(rb)) => lb.value == rb.value, + (ast::ExprData::ENumber(ln), ast::ExprData::ENumber(rn)) => ln.value == rn.value, + (ast::ExprData::EString(ls), ast::ExprData::EString(rs)) => { + // Zig: `ls.eqlEString(rs)` — inline the UTF-8/UTF-16 + slice-eq logic. + if ls.is_utf16 != rs.is_utf16 { + if ls.is_utf16 { + rs.eql_bytes(ls.data.slice()) + } else { + ls.eql_bytes(rs.data.slice()) + } + } else if ls.is_utf16 { + ls.slice16() == rs.slice16() + } else { + ls.data == rs.data + } + } + // pointer comparison + (ast::ExprData::EArray(la), ast::ExprData::EArray(ra)) => la.as_ptr() == ra.as_ptr(), + (ast::ExprData::EObject(lo), ast::ExprData::EObject(ro)) => lo.as_ptr() == ro.as_ptr(), + _ => false, + } +} + +/// Encoding-independent companion to [`yaml_merge_key_expr_eql`]. +fn yaml_merge_key_expr_hash(key: &Expr) -> u64 { + match &key.data { + ast::ExprData::ENull(_) => 0, + ast::ExprData::EBoolean(b) => 1 + b.value as u64, + ast::ExprData::ENumber(n) => { + let value = if n.value == 0.0 { 0.0 } else { n.value }; + value.to_bits() + } + ast::ExprData::EString(s) => s.hash(), + ast::ExprData::EArray(a) => a.as_ptr() as usize as u64, + ast::ExprData::EObject(o) => o.as_ptr() as usize as u64, + _ => u64::MAX, + } +} + +pub struct Stream<'i, Enc: Encoding> { pub docs: Vec, - pub input: *const [Enc::Unit], - // TODO(port): lifetime — Zig stored `[]const enc.unit()` borrowing parser input. + /// Zig: `[]const enc.unit()` — borrows the parser input. + pub input: &'i [Enc::Unit], } // ─────────────────────────────────────────────────────────────────────────── @@ -1934,6 +1988,10 @@ pub struct TokenInit { pub line: Line, } +// Zig copied tokens by value (struct copy, aliasing the scalar's backing +// store). The Rust clone deep-copies the `Vec` inside `YamlString::List` +// instead, which is semantically equivalent for the read-only uses here. +#[derive(Clone)] pub struct Token { pub start: Pos, pub indent: Indent, @@ -1941,20 +1999,7 @@ pub struct Token { pub data: TokenData, } -impl Clone for Token { - fn clone(&self) -> Self { - // TODO(port): TokenData contains NodeScalar which holds a Vec for - // String::List. Zig copied tokens by value (struct copy). TODO(port): - // make Token cheaply-copyable or store scalars by index. - Token { - start: self.start, - indent: self.indent, - line: self.line, - data: self.data.clone(), - } - } -} - +#[derive(Clone)] pub enum TokenData { Eof, /// `-` @@ -1992,61 +2037,7 @@ pub enum TokenData { Scalar(TokenScalar), } -impl Clone for TokenData { - fn clone(&self) -> Self { - // TODO(port): see Token::clone note - match self { - TokenData::Eof => TokenData::Eof, - TokenData::SequenceEntry => TokenData::SequenceEntry, - TokenData::MappingKey => TokenData::MappingKey, - TokenData::MappingValue => TokenData::MappingValue, - TokenData::CollectEntry => TokenData::CollectEntry, - TokenData::SequenceStart => TokenData::SequenceStart, - TokenData::SequenceEnd => TokenData::SequenceEnd, - TokenData::MappingStart => TokenData::MappingStart, - TokenData::MappingEnd => TokenData::MappingEnd, - TokenData::Anchor(r) => TokenData::Anchor(*r), - TokenData::Alias(r) => TokenData::Alias(*r), - TokenData::Tag(t) => TokenData::Tag(*t), - TokenData::Directive => TokenData::Directive, - TokenData::Reserved => TokenData::Reserved, - TokenData::DocumentStart => TokenData::DocumentStart, - TokenData::DocumentEnd => TokenData::DocumentEnd, - TokenData::Scalar(_) => { - // TODO(port): Scalar contains a Vec; Zig copied by value. Reshape to make it Copy. - unreachable!("Token should not be cloned") - } - } - } -} - -impl TokenData { - pub fn discriminant(&self) -> u8 { - // SAFETY: #[repr(...)] not declared; use mem::discriminant for comparisons instead. - // This helper exists only for the labeled-switch-loop ports below. - // TODO(port): replace with core::mem::discriminant comparisons. - match self { - TokenData::Eof => 0, - TokenData::SequenceEntry => 1, - TokenData::MappingKey => 2, - TokenData::MappingValue => 3, - TokenData::CollectEntry => 4, - TokenData::SequenceStart => 5, - TokenData::SequenceEnd => 6, - TokenData::MappingStart => 7, - TokenData::MappingEnd => 8, - TokenData::Anchor(_) => 9, - TokenData::Alias(_) => 10, - TokenData::Tag(_) => 11, - TokenData::Directive => 12, - TokenData::Reserved => 13, - TokenData::DocumentStart => 14, - TokenData::DocumentEnd => 15, - TokenData::Scalar(_) => 16, - } - } -} - +#[derive(Clone)] pub struct TokenScalar { pub data: NodeScalar, pub multiline: bool, @@ -2221,13 +2212,13 @@ impl Token { // ParseResult // ─────────────────────────────────────────────────────────────────────────── -pub enum ParseResult { - Result(ParseResultOk), +pub enum ParseResult<'i, Enc: Encoding> { + Result(ParseResultOk<'i, Enc>), Err(ParseResultError), } -pub struct ParseResultOk { - pub stream: Stream, +pub struct ParseResultOk<'i, Enc: Encoding> { + pub stream: Stream<'i, Enc>, // allocator dropped — global mimalloc } @@ -2314,12 +2305,12 @@ impl ParseResultError { } } -impl ParseResult { - pub fn success(stream: Stream, _parser: &Parser) -> Self { +impl<'i, Enc: Encoding> ParseResult<'i, Enc> { + pub fn success(stream: Stream<'i, Enc>, _parser: &Parser<'_, Enc>) -> Self { ParseResult::Result(ParseResultOk { stream }) } - pub fn fail(err: ParseError, parser: &Parser) -> Self { + pub fn fail(err: ParseError, parser: &Parser<'_, Enc>) -> Self { let e = match err { ParseError::OutOfMemory => ParseResultError::Oom, ParseError::StackOverflow => ParseResultError::StackOverflow, @@ -2472,7 +2463,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { ParseError::UnexpectedToken } - pub fn parse(&mut self) -> Result, ParseError> { + pub fn parse(&mut self) -> Result, ParseError> { self.scan(ScanOptions { first_scan: true, ..Default::default() @@ -2480,7 +2471,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { self.parse_stream() } - pub fn parse_stream(&mut self) -> Result, ParseError> { + pub fn parse_stream(&mut self) -> Result, ParseError> { let mut docs: Vec = Vec::new(); // we want one null document if eof, not zero documents. @@ -2493,7 +2484,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { Ok(Stream { docs, - input: std::ptr::from_ref::<[Enc::Unit]>(self.input), + input: self.input, }) } @@ -2822,7 +2813,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { self.context.set(Context::FlowIn)?; - // PORT NOTE: Zig `defer self.context.unset(.flow_in)` — capture the + // Zig `defer self.context.unset(.flow_in)` — capture the // fallible body's result and unset on EVERY exit (including `?` paths). // The post-`]` scan happens AFTER `.flow_in` is popped (yaml.zig:771), // so only the loop body lives inside the closure. @@ -2908,7 +2899,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { self.context.set(Context::FlowIn)?; - // PORT NOTE: Zig `defer self.context.unset(.flow_in)` — capture the + // Zig `defer self.context.unset(.flow_in)` — capture the // fallible body's result and unset on EVERY exit (including `?` paths). // The post-`}` scan happens AFTER `.flow_in` is popped (yaml.zig:852), // so only the loop body lives inside the closure. @@ -3037,7 +3028,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { self.block_indents.push(sequence_indent)?; - // PORT NOTE: Zig `defer self.block_indents.pop()` — capture the fallible + // Zig `defer self.block_indents.pop()` — capture the fallible // body's result and pop on EVERY exit (including `?` paths). let result: Result = (|| { let mut seq: ast::ExprNodeList = bun_alloc::AstAlloc::vec(); @@ -3090,53 +3081,6 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { result } - /// Should only be used with expressions created with the YAML parser. It assumes - /// only null, boolean, number, string, array, object are possible. It also only - /// does pointer comparison with arrays and objects (so exponential merges are avoided) - fn yaml_merge_key_expr_eql(l: &Expr, r: &Expr) -> bool { - if core::mem::discriminant(&l.data) != core::mem::discriminant(&r.data) { - return false; - } - match (&l.data, &r.data) { - (ast::ExprData::ENull(_), _) => true, - (ast::ExprData::EBoolean(lb), ast::ExprData::EBoolean(rb)) => lb.value == rb.value, - (ast::ExprData::ENumber(ln), ast::ExprData::ENumber(rn)) => ln.value == rn.value, - (ast::ExprData::EString(ls), ast::ExprData::EString(rs)) => { - // Zig: `ls.eqlEString(rs)` — inline the UTF-8/UTF-16 + slice-eq logic. - if ls.is_utf16 != rs.is_utf16 { - if ls.is_utf16 { - rs.eql_bytes(ls.data.slice()) - } else { - ls.eql_bytes(rs.data.slice()) - } - } else if ls.is_utf16 { - ls.slice16() == rs.slice16() - } else { - ls.data == rs.data - } - } - // pointer comparison - (ast::ExprData::EArray(la), ast::ExprData::EArray(ra)) => la.as_ptr() == ra.as_ptr(), - (ast::ExprData::EObject(lo), ast::ExprData::EObject(ro)) => lo.as_ptr() == ro.as_ptr(), - _ => false, - } - } - - fn yaml_merge_key_expr_hash(key: &Expr) -> u64 { - match &key.data { - ast::ExprData::ENull(_) => 0, - ast::ExprData::EBoolean(b) => 1 + b.value as u64, - ast::ExprData::ENumber(n) => { - let value = if n.value == 0.0 { 0.0 } else { n.value }; - value.to_bits() - } - ast::ExprData::EString(s) => s.hash(), - ast::ExprData::EArray(a) => a.as_ptr() as usize as u64, - ast::ExprData::EObject(o) => o.as_ptr() as usize as u64, - _ => u64::MAX, - } - } - fn parse_block_mapping( &mut self, first_key: Expr, @@ -3162,7 +3106,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { self.block_indents.push(mapping_indent)?; } - // PORT NOTE: Zig `defer self.block_indents.pop()` — capture the fallible + // Zig `defer self.block_indents.pop()` — capture the fallible // body's result and pop on EVERY exit (including `?` paths). let result: Result = (|| { let mut props = MappingProps::init(); @@ -3241,7 +3185,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { self.context.set(Context::BlockIn)?; - // PORT NOTE: Zig `defer self.context.unset(.block_in)` — same + // Zig `defer self.context.unset(.block_in)` — same // capture-then-unset pattern, nested. let inner: Result = (|| { let mut previous_line = first_entry_end_line; @@ -3419,7 +3363,7 @@ impl MappingProps { while self.merge_indexed < self.list.len() { let idx = self.merge_indexed; let key = self.list[idx].key.as_ref().unwrap(); - let hash = Parser::::yaml_merge_key_expr_hash(key); + let hash = yaml_merge_key_expr_hash(key); self.merge_index .get_or_put(hash)? .value_ptr @@ -3429,12 +3373,11 @@ impl MappingProps { 'next_merge_prop: for merge_prop in merge_props.iter().rev() { let merge_key = merge_prop.key.as_ref().unwrap(); - let merge_hash = Parser::::yaml_merge_key_expr_hash(merge_key); + let merge_hash = yaml_merge_key_expr_hash(merge_key); if let Some(candidates) = self.merge_index.get(&merge_hash) { for existing_idx in candidates.iter() { let existing_key = self.list[*existing_idx as usize].key.as_ref().unwrap(); - if Parser::::yaml_merge_key_expr_eql(existing_key, merge_key) { - // TODO(port): yaml_merge_key_expr_eql is generic-agnostic; using Utf8 monomorph here is a hack. + if yaml_merge_key_expr_eql(existing_key, merge_key) { continue 'next_merge_prop; } } @@ -3963,7 +3906,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { node_props.set_anchor(anchor)?; } - // PORT NOTE: labeled-switch loop on `self.token.data`. The Zig + // labeled-switch loop on `self.token.data`. The Zig // `continue :node self.token.data` re-enters with the new token after // scanning. We loop and re-match. let node: Expr = 'node: loop { @@ -4345,7 +4288,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { let scalar_line = self.token.line; let scalar_tab_after_indent = self.tab_after_indent; - // PORT NOTE: reshaped for borrowck — we must hold the scalar + // reshaped for borrowck — we must hold the scalar // payload across `self.scan()` which replaces self.token. // Take it out before scanning. let scalar = match core::mem::replace( @@ -4495,7 +4438,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { fn fold_lines(&mut self) -> usize { let mut total: usize = 0; - // PORT NOTE: labeled-switch loop + // labeled-switch loop let mut __c = Enc::wide(self.next()); loop { match __c { @@ -4588,7 +4531,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { multiline: false, }; - // PORT NOTE: labeled-switch loop + // labeled-switch loop let mut __c = Enc::wide(parser!().next()); loop { match __c { @@ -4933,7 +4876,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { let mut indent_indicator: Option = None; let mut chomp: Option = None; - // PORT NOTE: labeled-switch loop + // labeled-switch loop let mut __c = Enc::wide(self.next()); loop { match __c { @@ -5009,11 +4952,21 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { // ── scanAutoIndentedLiteralScalar ─────────────────────────────────────── // - // TODO(port): Another large labeled-switch state machine (yaml.zig:2703-2979) - // with an inner `LiteralScalarCtx` struct. The two-phase loop (find - // content_indent, then scan body) with `ctx.append`/`ctx.done` and chomp - // handling is preserved structurally below; verify the nested `newlines:` - // switch translation against the Zig original. + // Another large labeled-switch state machine (yaml.zig:2703-2979) with an + // inner `LiteralScalarCtx` struct. The two-phase loop (find content_indent, + // then scan body) with `ctx.append`/`ctx.done` is preserved structurally, + // but this port intentionally diverges from the Zig (verified against + // yaml.zig:2703-2979 and the official yaml-test-suite): + // - explicit `indent_indicator` support (the Zig only handled auto-indent); + // - EOF chomping normalized via `leading_newlines` in `done()` so Clip and + // Keep agree with eemeli/yaml + js-yaml (L24T/01, JEF9/02); + // - folded more-indented lines tracked with `prev/cur_more_indented` flags + // instead of the Zig's `previous_indent == content_indent` test, so + // breaks adjacent to more-indented lines are not folded (spec [73]-[74]); + // - the per-line indent guard is the precomputed `min_indent` + // (>= content_indent AND > parent block indent), where the Zig only + // checked `<= block_indent` when a block indent existed; + // - `---`/`...` document markers additionally require line start. fn scan_auto_indented_literal_scalar( &mut self, @@ -5147,7 +5100,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { }; // Phase 1: find content_indent and first non-ws char - // PORT NOTE: labeled-switch loop + // labeled-switch loop let mut consumed_indent_this_line = false; let (content_indent, first): (Indent, u32) = 'phase1: loop { let __c = Enc::wide(self.next()); @@ -5239,7 +5192,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { }; // Phase 2: scan body - // PORT NOTE: labeled-switch loop with nested `newlines:` switch + // labeled-switch loop with nested `newlines:` switch let mut __c = first; loop { match __c { @@ -5386,7 +5339,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { let mut text: Vec = Vec::new(); - // PORT NOTE: labeled-switch loop + // labeled-switch loop loop { let c = Enc::wide(self.next()); match c { @@ -5469,7 +5422,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { let scalar_indent = self.line_indent; let mut text: Vec = Vec::new(); - // PORT NOTE: labeled-switch loop + // labeled-switch loop loop { let c = Enc::wide(self.next()); match c { @@ -5820,7 +5773,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { let previous_token_line = self.token.line; - // PORT NOTE: labeled-switch loop with `inline` whitespace dispatch. + // labeled-switch loop with `inline` whitespace dispatch. // We loop on `Enc::wide(self.next())` and break with the resulting token. let token: Token = 'next: loop { let c = Enc::wide(self.next()); @@ -6073,7 +6026,7 @@ impl<'i, Enc: Encoding> Parser<'i, Enc> { self.token = Token::reserved(self.token_init(start)); return Err(Self::unexpected_token()); } - // PORT NOTE: ScanCtx.scanWhitespace inlined. + // ScanCtx.scanWhitespace inlined. // whitespace — Zig used `inline '\r','\n',' ','\t' => |ws| ctx.scanWhitespace(ws)` 0x0D /* '\r' */ => { if Enc::wide(self.peek(1)) == 0x0A { diff --git a/src/patch/lib.rs b/src/patch/lib.rs index 22bfb2e9b466..308629f64085 100644 --- a/src/patch/lib.rs +++ b/src/patch/lib.rs @@ -28,7 +28,7 @@ const PAGE_SIZE: usize = 16384; // ────────────────────────────────────────────────────────────────────────── /// All strings point to the original patch file text -// PORT NOTE: lifetime — every `&'a [u8]` in this module borrows from the +// lifetime — every `&'a [u8]` in this module borrows from the // original patch file text. The port generally avoids struct lifetimes, but // this parser's whole output is borrowed; raw `*const [u8]` everywhere would // be worse. @@ -71,7 +71,7 @@ impl ApplyState { } match sys::get_fd_path(fd, &mut self.pathbuf) { sys::Result::Ok(p) => { - // PORT NOTE: reshaped for borrowck — capture len, drop `p`, + // reshaped for borrowck — capture len, drop `p`, // then re-borrow `self.pathbuf` to write the sentinel. let len = p.len(); // Zig `state.pathbuf[0..p.len :0]` asserts a NUL sentinel; on @@ -136,7 +136,7 @@ impl<'a> PatchFile<'a> { let mode = file_creation.mode; if !filedir.is_empty() { - // PORT NOTE: Zig calls `NodeFS.mkdirRecursive` with the bare relative + // Zig calls `NodeFS.mkdirRecursive` with the bare relative // `filedir` (resolved against process CWD), then immediately `openat`s // the same path against `patch_dir`. That is internally inconsistent // when `patch_dir != cwd`. We intentionally diverge and create the @@ -442,7 +442,7 @@ fn apply_patch(patch: &FilePatch<'_>, patch_dir: Fd, state: &mut ApplyState) -> } fn read_file_alloc(dir: Fd, path: &ZStr, max: usize) -> sys::Result> { - // PORT NOTE: Zig's `std.fs.Dir.readFileAlloc` opens, fstats, allocates + // Zig's `std.fs.Dir.readFileAlloc` opens, fstats, allocates // `min(size, max)` and errors `error.FileTooBig` past `max`. Enforce the // same cap so a pathological multi-GiB target file errors instead of // allocating unboundedly. (`error.FileTooBig` would surface as `.INVAL` at @@ -940,7 +940,7 @@ pub fn parse_patch_file(file: &[u8]) -> Result, ParseErr> { } } - // PORT NOTE: reshaped for borrowck — take ownership of result vec instead of slicing. + // reshaped for borrowck — take ownership of result vec instead of slicing. let mut files = mem::take(&mut lines_parser.result); patch_file_second_pass(&mut files) } @@ -1189,7 +1189,7 @@ impl<'a> PatchLinesParser<'a> { // In Rust, Drop handles freeing; `reset()` handles the retain-capacity case. fn reset(&mut self) { - // PORT NOTE: reshaped for borrowck — take result vec, clear it, reinit self. + // reshaped for borrowck — take result vec, clear it, reinit self. let mut result = mem::take(&mut self.result); result.clear(); *self = Self { @@ -1665,7 +1665,7 @@ fn parse_diff_line_paths(line: &[u8]) -> Option<(&[u8], &[u8])> { // spawnOpts / diffPostProcess / gitDiff* // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: Zig returns `bun.spawn.sync.Options` with `argv`/`envp` allocated on +// Zig returns `bun.spawn.sync.Options` with `argv`/`envp` allocated on // `bun.default_allocator` and freed by the caller. `bun_spawn::sync::Options` owns // `argv` (`Vec>`) but borrows `envp` (`Option<*const *const c_char>`), so // the null-terminated envp array is returned alongside as the second tuple element — @@ -1719,7 +1719,7 @@ pub fn spawn_opts( envp_buf.push(s.as_ptr().cast::()); } if let Some(p) = path { - // PORT NOTE: `env_var::PATH.get()` yields a slice into the C env + // `env_var::PATH.get()` yields a slice into the C env // block (NUL byte immediately follows on POSIX — see // `bun_core::getenv_z`), matching Zig's `@ptrCast(p.ptr)`. envp_buf.push(p.as_ptr().cast::()); @@ -1761,7 +1761,7 @@ pub fn diff_post_process( mem::swap(&mut stdout, &mut result.stdout); mem::swap(&mut stderr, &mut result.stderr); - // PORT NOTE: errdefer-style flags replaced by Drop semantics; on early return + // errdefer-style flags replaced by Drop semantics; on early return // the unreturned vec is dropped automatically. if !stderr.is_empty() { @@ -1773,7 +1773,7 @@ pub fn diff_post_process( Ok(Ok(stdout)) } -// PORT NOTE: Zig signature returns `[2]if (sentinel) [:0]const u8 else []const u8` — +// Zig signature returns `[2]if (sentinel) [:0]const u8 else []const u8` — // return type depends on a comptime bool. Rust cannot express this without GAT-ish // traits, so we return owned `Vec` pairs (NUL-appended when SENTINEL). pub fn git_diff_preprocess_paths( @@ -1902,7 +1902,7 @@ pub fn git_diff_internal( stderr: bun_spawn::sync::Stdio::Buffer, envp: Some(envp_buf.as_ptr()), argv, - // PORT NOTE: Zig used `std.process.Child` (no uv loop). The Rust port + // Zig used `std.process.Child` (no uv loop). The Rust port // routes through `bun_spawn::sync::spawn`, whose Windows path // unconditionally derefs `windows.loop_` (process.rs spawn_windows_*). // `WindowsOptions::default()` is `zeroed_unchecked()`, so leaving this @@ -1966,7 +1966,6 @@ fn git_diff_postprocess( old_folder: &[u8], new_folder: &[u8], ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let old_folder_trimmed = strings::trim(old_folder, b"/"); let new_folder_trimmed = strings::trim(new_folder, b"/"); @@ -2002,7 +2001,7 @@ fn git_diff_postprocess( let mut saw_b_folder: Option = None; let mut line_idx: u32 = 0; - // PORT NOTE: reshaped for borrowck — Zig mutated `stdout` while iterating + // reshaped for borrowck — Zig mutated `stdout` while iterating // `std.mem.splitScalar` over it (relying on the iterator's by-value buffer // pointer staying valid because replaceRange only shrinks). In Rust we // re-implement the cursor manually so we can mutate `stdout` between lines. diff --git a/src/patch_jsc/testing.rs b/src/patch_jsc/testing.rs index d2c6795b6b1d..9ed6c386f23a 100644 --- a/src/patch_jsc/testing.rs +++ b/src/patch_jsc/testing.rs @@ -10,7 +10,7 @@ use bun_sys::{Fd, FdExt}; pub struct TestingAPIs; impl TestingAPIs { - // PORT NOTE: `#[bun_jsc::host_fn]` Free-kind shim emits an unqualified + // `#[bun_jsc::host_fn]` Free-kind shim emits an unqualified // `fn_name(g, f)` call, so it cannot wrap an associated fn. The C-ABI // shim is emitted at module scope below (`__jsc_host_*`). pub fn make_diff(global: &JSGlobalObject, frame: &CallFrame) -> JsResult { @@ -33,9 +33,8 @@ impl TestingAPIs { let old_folder = old_folder_bunstr.to_utf8(); let new_folder = new_folder_bunstr.to_utf8(); - // PORT NOTE: Zig `gitDiffInternal` used `std.process.Child` (no uv loop). - // Rust routes through `bun_spawn::sync`, which on Windows derefs - // `WindowsOptions.loop_` — supply the JS event loop. + // `git_diff_internal` routes through `bun_spawn::sync`, which on + // Windows derefs `WindowsOptions.loop_` — supply the JS event loop. // `global.bun_vm().event_loop()` is the live per-thread `jsc::EventLoop`. let mut loop_ = bun_jsc::AnyEventLoop::js(global.bun_vm().event_loop().cast()); let diff = match git_diff_internal(old_folder.slice(), new_folder.slice(), &mut loop_) { diff --git a/src/paths/EnvPath.rs b/src/paths/EnvPath.rs index e5e19e463ca9..0765e9a38e27 100644 --- a/src/paths/EnvPath.rs +++ b/src/paths/EnvPath.rs @@ -15,10 +15,9 @@ fn trim_path_delimiters(input: &[u8]) -> &[u8] { } // Zig: `pub fn EnvPath(comptime opts: EnvPathOptions) type { return struct { ... } }` -// TODO(port): `EnvPathOptions` currently has no fields, so the comptime `opts` -// parameter is vacuous. Re-introduce a `` const -// generic (with `#[derive(core::marker::ConstParamTy)]` on `EnvPathOptions`) -// once options are actually added. +// `EnvPathOptions` currently has no fields, so the comptime `opts` parameter is +// vacuous and is not ported. If options are ever added, reintroduce them as a +// const-generic parameter here. #[derive(Default)] pub struct EnvPath { // Zig: `std.mem.Allocator param` — dropped (non-AST crate, global mimalloc). diff --git a/src/paths/Path.rs b/src/paths/Path.rs index 0363c6d76ccd..2368752a8e47 100644 --- a/src/paths/Path.rs +++ b/src/paths/Path.rs @@ -1,10 +1,8 @@ -// PORT NOTE: Zig's comptime enum options become `const u8` generics on stable +// Zig's comptime enum options become `const u8` generics on stable // (adt_const_params is nightly-only). Each fn that branches on an option // reconstructs the enum from the u8 via `decode_opts!` so the original // `match Kind::Abs => ..` arms stay unchanged. The optimizer sees through the // `const fn from_u8` so monomorphization is preserved. -// TODO(refactor): either enable the feature crate-wide or lower the const-generic -// enums to a trait-per-option encoding if nightly is unacceptable. use core::marker::PhantomData; use core::mem::ManuallyDrop; @@ -136,12 +134,10 @@ pub mod options { // Zig: `pub fn ResultFn(comptime opts) fn(type) type` — returns `T` when // `.assume_always_less_than_max_path`, `Error!T` otherwise. // - // TODO(port): Rust cannot vary a fn's return type on a const-generic value. - // All `Result(T)` call sites below return `Result` unconditionally; + // Rust cannot vary a fn's return type on a const-generic value, so all + // `Result(T)` call sites below return `Result` unconditionally; // callers configured with `AssumeAlwaysLessThanMaxPath` should treat the - // `Err` arm as unreachable. Could split into two inherent impls via - // `where` bounds on `CHECK_LENGTH` once `generic_const_exprs` lands, or - // expose `_unchecked` variants. + // `Err` arm as unreachable (see `AssumeOk` below). pub type Result = core::result::Result; /// `Result::unwrap` for the `CheckLength::AssumeAlwaysLessThanMaxPath` @@ -150,7 +146,7 @@ pub mod options { /// In Zig, `Path(.{ .check_length = .assume_always_less_than_max_path })` /// `from`/`append`/`appendFmt` return bare `T` (not `Error!T`), so call /// sites are `path.append(x)` with no `try`. The Rust port returns - /// `Result` unconditionally (see PORT NOTE above), but the + /// `Result` unconditionally (see note above), but the /// `from`/`append` bodies *skip the length check entirely* in `ASSUME` /// mode -- `Err(MaxPathExceeded)` is dead code, and an over-long input /// instead panics inside `PooledBuf::append` slice indexing. So callers @@ -227,8 +223,8 @@ pub trait PathUnit: crate::PathChar { /// `bun.path_buffer_pool.get()` / `bun.w_path_buffer_pool.get()` // LIFETIMES.tsv classifies `Buf.pooled` as OWNED → Box; the // underlying pool hands out heap buffers and reclaims them in `deinit`. - // TODO(port): swap to `crate::path_buffer_pool()` RAII guard once the - // guard type is generic over unit; for now model as Box and put-back in Drop. + // Modeled as Box with put-back in `Path`'s Drop impl below (the pool's + // RAII guard type is not generic over unit). fn pool_get() -> Box; fn pool_put(buf: Box); @@ -436,10 +432,10 @@ impl Buf { self.len += 1; } - // TODO(port): the Zig branches on `opts.inputChildType(@TypeOf(characters))` to pick - // convertUTF8toUTF16InBuffer vs convertUTF16toUTF8InBuffer. Rust cannot match on a - // type parameter at runtime; could route through a helper trait. For now this - // dispatches via TypeId-equivalent specialization on the two concrete impls. + // The Zig branches on `opts.inputChildType(@TypeOf(characters))` to pick + // convertUTF8toUTF16InBuffer vs convertUTF16toUTF8InBuffer. Here the + // dispatch happens via `convert_into_buffer`'s per-unit specialization + // on the two concrete `PathUnit` impls. let converted_len = convert_into_buffer::(&mut buf[self.len..], characters); if SEP_OPT != PathSeparators::ANY { for off in 0..converted_len { @@ -662,8 +658,8 @@ impl }; let mut this = Self::init(); - // TODO(port): top_level_dir is &[u8]; when U == u16 this should route through - // append_other. See note on `append_input` below. + // top_level_dir is &[u8]; `_buf_append_input` routes it through + // `append_other` (transcoding) when U == u16. this._buf_append_input(trimmed, false); this } @@ -702,7 +698,7 @@ impl // `getFdPath`/`getFdPathW` are libc/kernel32-only, so the bodies live // in `bun_core::fd_path_raw[_w]` (T0). >0 = units written, <0 = error. - // PORT NOTE: Zig `fd.getFdPath(this._buf.pooled)` dispatches on the + // Zig `fd.getFdPath(this._buf.pooled)` dispatches on the // pooled buffer's element type (u8 → readlink/F_GETPATH, u16 → // GetFinalPathNameByHandleW). Rust monomorphizes eagerly, so we // TypeId-dispatch on the buffer element type. @@ -725,13 +721,13 @@ impl let n = unsafe { bun_core::fd_path_raw_w(fd, wslice.as_mut_ptr(), wslice.len()) }; if n <= 0 { // Zig `bun.windows.GetFinalPathNameByHandle` surfaces - // `error.FileNotFound` (return_length==0) or - // `error.NameTooLong` (return_length>=buf.len); - // `fd_path_raw_w` collapses both to -1, so propagate the - // dominant Zig error rather than inventing EBADF. - // TODO(port): have `fd_path_raw_w` distinguish overflow - // (e.g. -2) so callers can map ENAMETOOLONG separately. - return Err(bun_core::Error::intern("FileNotFound")); + // `error.FileNotFound` (return_length==0 → -1) or + // `error.NameTooLong` (return_length>=buf.len → -2). + return Err(if n == -2 { + bun_core::Error::intern("NameTooLong") + } else { + bun_core::Error::intern("FileNotFound") + }); } let wide = &wslice[..n as usize]; let written = strings::convert_utf16_to_utf8_in_buffer(buf, wide).len(); @@ -762,10 +758,13 @@ impl let n = unsafe { bun_core::fd_path_raw_w(fd, buf.as_mut_ptr(), buf.len()) }; if n <= 0 { // Zig `bun.windows.GetFinalPathNameByHandle` surfaces - // `error.FileNotFound` / `error.NameTooLong`; `fd_path_raw_w` - // collapses both to -1, so propagate the dominant Zig error - // rather than inventing EBADF. - return Err(bun_core::Error::intern("FileNotFound")); + // `error.FileNotFound` (return_length==0 → -1) or + // `error.NameTooLong` (return_length>=buf.len → -2). + return Err(if n == -2 { + bun_core::Error::intern("NameTooLong") + } else { + bun_core::Error::intern("FileNotFound") + }); } let raw = &buf[..n as usize]; let trimmed = trim_input(TrimInputKind::Abs, raw); @@ -857,7 +856,7 @@ impl } pub fn basename_z(&mut self) -> &U::ZSlice { - // PORT NOTE: reshaped for borrowck (Zig took *const and wrote NUL via @constCast). + // Reshaped for borrowck (Zig took *const and wrote NUL via @constCast). let len = self._buf.len; let buf = U::buffer_as_mut_slice(&mut self._buf.pooled); buf[len] = U::from_u8(0); @@ -910,7 +909,7 @@ impl pub fn slice_z(&mut self) -> &U::ZSlice { // match BufType::Pool - // PORT NOTE: reshaped for borrowck (Zig took *const and wrote NUL via @constCast). + // Reshaped for borrowck (Zig took *const and wrote NUL via @constCast). let len = self._buf.len; let buf = U::buffer_as_mut_slice(&mut self._buf.pooled); buf[len] = U::from_u8(0); @@ -920,7 +919,7 @@ impl pub fn buf(&mut self) -> &mut [U] { // match BufType::Pool - // PORT NOTE: reshaped for borrowck (Zig took *const and handed out a mutable slice). + // Reshaped for borrowck (Zig took *const and handed out a mutable slice). U::buffer_as_mut_slice(&mut self._buf.pooled) } @@ -1092,7 +1091,8 @@ impl } pub fn join(&mut self, parts: &[&[U]]) -> options::Result<()> { - // TODO(port): Zig @compileError when unit == u16; enforced here at runtime. + // Zig: @compileError when unit == u16 — Rust can't compile-error on a + // type parameter without specialization; enforced here at runtime. if core::any::TypeId::of::() == core::any::TypeId::of::() { panic!("unsupported unit type"); } @@ -1132,11 +1132,10 @@ impl } } - // TODO(port): the Zig dispatches on `@TypeOf(part)` × `opts.pathUnit()` to pick - // joinStringBuf vs joinStringBufW vs a transcode-then-recurse path. Rust cannot - // match on type identity in a fn body without specialization. The four arms are - // reproduced below via TypeId checks; could be replaced with a sealed-trait - // dispatch on (C, U). + // The Zig dispatches on `@TypeOf(part)` × `opts.pathUnit()` to pick + // joinStringBuf vs joinStringBufW vs a transcode-then-recurse path. + // The four (C, U) arms are reproduced below via TypeId checks, which + // const-fold to a single arm per monomorphization. use core::any::TypeId; let c_is_u8 = TypeId::of::() == TypeId::of::(); let u_is_u8 = TypeId::of::() == TypeId::of::(); @@ -1196,13 +1195,12 @@ impl &self, to: &Path, ) -> RelPath { - // PORT NOTE: `resolve_path::relative_buf_z` is `&[u8]`-only and the Zig + // `resolve_path::relative_buf_z` is `&[u8]`-only and the Zig // had no u16 variant either — Path.zig `relative` calls `relativeBufZ` // unconditionally, which would compile-error on a u16 instantiation // (Zig's lazy eval hides that). Rust monomorphizes eagerly, so we // TypeId-dispatch: u8 → identity-cast and call; u16 → transcode through - // temp u8 buffers and back. TODO(port): width-generic - // `relative_buf_z_t` if u16 callers turn out to be hot. + // temp u8 buffers and back. use core::any::TypeId; let mut output: RelPath = Path::init(); @@ -1326,9 +1324,6 @@ impl Drop // SAFETY: `pooled` is initialized in `init()` and never taken before this; Drop runs once. let pooled = unsafe { ManuallyDrop::take(&mut self._buf.pooled) }; U::pool_put(pooled); - // TODO(port): replace Box + manual put-back with the - // `crate::path_buffer_pool()` RAII guard once it is generic over - // unit, then delete this Drop impl entirely. } } diff --git a/src/paths/lib.rs b/src/paths/lib.rs index ab93d95081ec..ad1cd30c691b 100644 --- a/src/paths/lib.rs +++ b/src/paths/lib.rs @@ -381,7 +381,7 @@ pub trait PathLike { fn append(&mut self, bytes: &[u8]); fn append_fmt(&mut self, args: core::fmt::Arguments<'_>); } -// PORT NOTE: Bound to `CheckLength::ASSUME` only. In Zig the helpers call +// Bound to `CheckLength::ASSUME` only. In Zig the helpers call // `buf.append(x)` with no `try`, so passing a `.check_for_greater_than_max_path` // Path is a *compile error* (`Error!void` is not `void`). Mirroring that here // prevents check-mode callers from silently swallowing `MaxPathExceeded` through @@ -930,7 +930,7 @@ pub mod fs { } /// Zig: `Path.initWithNamespaceVirtual(comptime text, namespace, package)`. - /// PORT NOTE: Zig formed `pretty = namespace ++ ":" ++ package` at comptime; + /// Zig formed `pretty = namespace ++ ":" ++ package` at comptime; /// `const_format::concatcp!` can't accept fn-param `&str`, so callers pass /// the precomputed `concatcp!` result as `pretty`. #[inline] @@ -949,7 +949,8 @@ pub mod fs { } /// Zig: `Path.initForKitBuiltIn`. - /// PORT NOTE: same comptime-concat caveat as `init_with_namespace_virtual`. + /// Same comptime-concat caveat as `init_with_namespace_virtual`: + /// callers pass the precomputed `concatcp!` result as `pretty`. #[inline] pub const fn init_for_kit_built_in( namespace: &'static [u8], diff --git a/src/paths/path_buffer_pool.rs b/src/paths/path_buffer_pool.rs index f8153bdb5352..c666b58e1275 100644 --- a/src/paths/path_buffer_pool.rs +++ b/src/paths/path_buffer_pool.rs @@ -4,11 +4,9 @@ //! make the stack memory usage more predictable. We keep up to 4 path buffers //! alive per thread at a time. //! -//! PORT NOTE: Zig used `bun.ObjectPool` (a thread-safe -//! 4-slot freelist). Rewritten over `thread_local!` + `RefCell>>` -//! per PORTING.md §Concurrency (init-once / per-thread → no lock needed). -//! Same observable behavior: at most 4 buffers cached per thread; excess `put`s -//! drop. RAII guard replaces the manual `get`/`put` pairing. +//! Implemented over `thread_local!` + `RefCell>>` (per-thread, so no +//! lock needed): at most 4 buffers cached per thread; excess `put`s drop. An +//! RAII guard replaces manual `get`/`put` pairing. use core::cell::RefCell; use core::marker::PhantomData; diff --git a/src/paths/resolve_path.rs b/src/paths/resolve_path.rs index 237c0a84da48..48befc094ab6 100644 --- a/src/paths/resolve_path.rs +++ b/src/paths/resolve_path.rs @@ -4,7 +4,7 @@ use crate::fs as Fs; use crate::{MAX_PATH_BYTES, PathBuffer, SEP, SEP_POSIX, SEP_WINDOWS}; use bun_core::{WStr, ZStr, strings}; -// PORT NOTE: Zig `threadlocal var` buffers. Stored in `UnsafeCell` (not `RefCell`) +// Zig `threadlocal var` buffers. Stored in `UnsafeCell` (not `RefCell`) // because callers must receive a raw `&mut` slice that outlives the `.with` closure // to match Zig's "valid until next call on this thread" pointer semantics. RefCell's // runtime borrow tracking cannot express that contract and would force an @@ -23,7 +23,7 @@ thread_local! { /// The `'static` output lifetime is the honest contract: the buffer is /// thread-local storage that lives for the thread's lifetime, and the returned /// slice is "valid until the next call on this thread" (Zig threadlocal-var -/// pointer semantics — see module PORT NOTE above). Callers uphold the +/// pointer semantics — see module comment above). Callers uphold the /// single-live-borrow-per-thread invariant. #[inline] fn tl_buf_mut(b: &UnsafeCell<[u8; N]>) -> &'static mut [u8; N] { @@ -48,14 +48,15 @@ pub fn z<'a>(input: &[u8], output: &'a mut PathBuffer) -> &'a ZStr { } type IsSeparatorFunc = fn(char: u8) -> bool; -// TODO(port): IsSeparatorFuncT/LastSeparatorFunctionT take `comptime T: type` — -// represented here as generic-over-PathChar fn pointers; Rust cannot express -// "fn(T) -> bool" as a value, so callers dispatch via Platform methods. +// Zig's IsSeparatorFuncT/LastSeparatorFunctionT took `comptime T: type`; Rust +// cannot express "fn(T) -> bool" as a value, so the generic-`T` callers +// dispatch via Platform methods instead of fn pointers. type LastSeparatorFunction = fn(slice: &[u8]) -> Option; #[inline(always)] fn is_dotdot_with_type(slice: &[T]) -> bool { - // TODO(port): specialization for T==u8 used @bitCast; generic path checks bytewise + // PERF(port): Zig specialized T==u8 with a u16 @bitCast compare; the + // generic path checks elementwise — profile if it shows up on a hot path. slice.len() >= 2 && slice[0] == T::from_u8(b'.') && slice[1] == T::from_u8(b'.') } @@ -92,7 +93,6 @@ pub fn is_parent_or_equal(parent_: &[u8], child: &[u8]) -> ParentEqual { pub(crate) fn get_if_exists_longest_common_path_generic<'a, P: PlatformT>( input: &[&'a [u8]], ) -> Option<&'a [u8]> { - // TODO(port): return lifetime — borrows from `input` strings; caller must ensure outlives let is_path_separator = P::P.get_separator_func(); let nql_at_index_fn: fn(usize, usize, &[&[u8]]) -> bool = match P::P { @@ -326,7 +326,7 @@ pub fn get_if_exists_longest_common_path<'a>(input: &[&'a [u8]]) -> Option<&'a [ get_if_exists_longest_common_path_generic::(input) } -// PORT NOTE: bun.ThreadlocalBuffers(struct {...}) heap-allocates on first use and +// bun.ThreadlocalBuffers(struct {...}) heap-allocates on first use and // stores only a pointer in TLS. Represented as three independent lazily-boxed // thread-locals so that `relative_platform_buf` can hold disjoint `&mut` to // from/to buffers while `relative_to_common_path_buf()` is borrowed elsewhere @@ -494,7 +494,7 @@ pub(crate) fn relative_to_common_path<'a, const ALWAYS_COPY: bool, P: PlatformT> // Generate the relative path based on the path difference between `to` // and `from`. - // PORT NOTE: reshaped for borrowck — Zig used a growing slice `out_slice` + // reshaped for borrowck — Zig used a growing slice `out_slice` // pointing into `buf`; we track length and re-slice at end. let mut out_len: usize = 0; @@ -572,7 +572,7 @@ pub fn relative_normalized_buf<'a, P: PlatformT, const ALWAYS_COPY: bool>( relative_to_common_path::(common_path, from, to, buf) } -// PORT NOTE: result borrows either the thread-local common-path buf ('static) +// result borrows either the thread-local common-path buf ('static) // or `to` (when !ALWAYS_COPY and result==to). Return lifetime is `'a` (=to's), // since 'static: 'a. Zig's "valid until next call" semantics still applies for // the buf-backed case. @@ -642,7 +642,7 @@ pub fn relative(from: &[u8], to: &[u8]) -> &'static [u8] { pub fn relative_buf_z<'a>(buf: &'a mut [u8], from: &[u8], to: &[u8]) -> &'a ZStr { let rel = relative_platform_buf::(buf, from, to); let len = rel.len(); - // PORT NOTE: reshaped for borrowck — drop `rel` borrow before mutating buf + // reshaped for borrowck — drop `rel` borrow before mutating buf buf[len] = 0; // SAFETY: buf[len] == 0 written above ZStr::from_buf(&buf[..], len) @@ -670,7 +670,7 @@ pub fn relative_platform_buf<'a, P: PlatformT, const ALWAYS_COPY: bool>( platform_to_posix_in_place::(normalized); break 'brk &*normalized; } - // PORT NOTE: reshaped for borrowck — capture len, drop inner &mut, re-slice + // reshaped for borrowck — capture len, drop inner &mut, re-slice let path_len = normalize_string_buf::(from, &mut relative_from_buf[1..]).len(); if P::P == Platform::Windows { @@ -680,7 +680,7 @@ pub fn relative_platform_buf<'a, P: PlatformT, const ALWAYS_COPY: bool>( break 'brk &relative_from_buf[0..path_len + 1]; } } else { - // PORT NOTE: Zig aliased relative_from_buf as both input (normalize result) + // Zig aliased relative_from_buf as both input (normalize result) // and output (join target). Reshape: normalize into relative_to_buf scratch, // then join into relative_from_buf. Safe because normalized_to is computed // afterwards (overwrites relative_to_buf anyway). @@ -702,7 +702,7 @@ pub fn relative_platform_buf<'a, P: PlatformT, const ALWAYS_COPY: bool>( platform_to_posix_in_place::(normalized); break 'brk &*normalized; } - // PORT NOTE: reshaped for borrowck — capture len, drop inner &mut, re-slice + // reshaped for borrowck — capture len, drop inner &mut, re-slice let path_len = normalize_string_buf::(to, &mut relative_to_buf[1..]).len(); if P::P == Platform::Windows { @@ -712,7 +712,7 @@ pub fn relative_platform_buf<'a, P: PlatformT, const ALWAYS_COPY: bool>( break 'brk &relative_to_buf[0..path_len + 1]; } } else { - // PORT NOTE: Zig aliased relative_to_buf as both input (normalize result) + // Zig aliased relative_to_buf as both input (normalize result) // and output (join target). Reshape: normalize into `buf` scratch (caller // output buffer, untouched until the final relative_normalized_buf call // and disjoint from both threadlocals), then join into relative_to_buf. @@ -769,8 +769,8 @@ pub(crate) fn windows_volume_name_len_t(path: &[T]) -> (usize, usiz && !Platform::Windows.is_separator_t::(path[2]) && path[2] != T::from_u8(b'.') { - // TODO(port): Zig branched on T==u8 to use SIMD index_of_any vs generic; - // collapse to a single generic helper here. PERF(port): profile if hot. + // PERF(port): Zig branched on T==u8 to use SIMD index_of_any; the + // single generic helper checks elementwise — profile if hot. if let Some(idx) = strings::index_of_any_t::(&path[3..], T::lit(b"/\\")) { // TODO: handle input "//abc//def" should be picked up as a unc path if path.len() > idx + 4 && !Platform::Windows.is_separator_t::(path[idx + 4]) { @@ -951,8 +951,8 @@ impl Default for NormalizeOptions { } } -// TODO(port): return type was `if (options.zero_terminate) [:0]T else []T`. -// Rust cannot vary the return type on a const-generic bool without specialization; +// Zig's return type was `if (options.zero_terminate) [:0]T else []T`. Rust +// cannot vary the return type on a const-generic bool without specialization; // we always return `&mut [T]` and write the NUL when `ZERO_TERMINATE`. Callers // that need `&ZStr`/`&WStr` re-wrap with `from_raw`. pub fn normalize_string_generic_tz< @@ -1164,7 +1164,7 @@ pub enum Platform { Nt, } -// PORT NOTE: Zig used `comptime _platform: Platform` const-generics. Nightly +// Zig used `comptime _platform: Platform` const-generics. Nightly // `adt_const_params` is now enabled crate-wide (see lib.rs), so `Platform` // derives `ConstParamTy` and `` is the preferred // form for new code. The `PlatformT` sealed-trait shim below is kept for @@ -1215,7 +1215,7 @@ impl Platform { self.is_absolute_t::(path) } - // PORT NOTE: dropped `const` — PathChar trait methods aren't const-callable + // dropped `const` — PathChar trait methods aren't const-callable // on stable. Zig's `comptime` here was for monomorphization, not const-eval. pub fn is_absolute_t(self, path: &[T]) -> bool { match self { @@ -1331,7 +1331,7 @@ impl Platform { } pub fn normalize_string(str: &[u8]) -> &mut [u8] { - // PORT NOTE: returns slice into thread-local PARSER_BUFFER; valid until the + // returns slice into thread-local PARSER_BUFFER; valid until the // next call on this thread (Zig threadlocal-var semantics). PARSER_BUFFER.with(|b| normalize_string_buf::(str, tl_buf_mut(b))) } @@ -1356,7 +1356,6 @@ pub fn normalize_buf_t<'a, T: PathChar, P: PlatformT>(str: &[T], buf: &'a mut [T let is_absolute = P::P.is_absolute_t::(str); - // TODO(port): platform.getLastSeparatorFuncT()(T, str) — dispatched manually let trailing_separator = match P::P { Platform::Loose => last_index_of_separator_loose_t::(str), Platform::Nt | Platform::Windows => last_index_of_separator_windows_t::(str), @@ -1420,7 +1419,7 @@ pub fn join_abs<'a, P: PlatformT>(cwd: &'a [u8], part: &[u8]) -> &'a [u8] { /// This is the equivalent of path.resolve /// /// Returned path is stored in a temporary buffer. It must be copied if it needs to be stored. -// PORT NOTE: result borrows the thread-local buffer ('static) OR returns `cwd` +// result borrows the thread-local buffer ('static) OR returns `cwd` // directly when `parts.is_empty()`. Return tied to `cwd`'s lifetime ('static: 'a). pub fn join_abs_string<'a, P: PlatformT>(cwd: &'a [u8], parts: &[&[u8]]) -> &'a [u8] { PARSER_JOIN_INPUT_BUFFER.with(|b| join_abs_string_buf::

(cwd, tl_buf_mut(b), parts)) @@ -1448,7 +1447,7 @@ pub fn join_z(parts: &[&[u8]]) -> &'static ZStr { } pub fn join_z_buf<'a, P: PlatformT>(buf: &'a mut [u8], parts: &[&[u8]]) -> &'a ZStr { - // PORT NOTE: reshaped for borrowck — capture buf base ptr before sub-borrow + // reshaped for borrowck — capture buf base ptr before sub-borrow let buf_base = buf.as_mut_ptr(); let buf_len = buf.len(); let (start_offset, len) = { @@ -1475,7 +1474,7 @@ pub fn join_string_buf_w_same<'a, P: PlatformT>(buf: &'a mut [u16], parts: &[&[u } /// Same-width `joinStringBufT`: parts already match `T`, so no UTF-8→16 transcode. -/// PORT NOTE: split out of `join_string_buf_t` because Rust can't monomorphize on +/// split out of `join_string_buf_t` because Rust can't monomorphize on /// `parts: anytype` element types like Zig — callers pick the overload. pub(crate) fn join_string_buf_t_same<'a, T: PathChar, P: PlatformT>( buf: &'a mut [T], @@ -1525,7 +1524,7 @@ pub(crate) fn join_string_buf_t_same<'a, T: PathChar, P: PlatformT>( } pub fn join_string_buf_wz<'a, P: PlatformT>(buf: &'a mut [u16], parts: &[&[u8]]) -> &'a WStr { - // PORT NOTE: reshaped for borrowck — capture buf base ptr before sub-borrow + // reshaped for borrowck — capture buf base ptr before sub-borrow let buf_base = buf.as_mut_ptr(); let buf_len = buf.len(); let (start_offset, len) = { @@ -1542,7 +1541,7 @@ pub fn join_string_buf_wz<'a, P: PlatformT>(buf: &'a mut [u16], parts: &[&[u8]]) } pub fn join_string_buf_z<'a, P: PlatformT>(buf: &'a mut [u8], parts: &[&[u8]]) -> &'a ZStr { - // PORT NOTE: reshaped for borrowck — capture buf base ptr before sub-borrow + // reshaped for borrowck — capture buf base ptr before sub-borrow let buf_base = buf.as_mut_ptr(); let buf_len = buf.len(); let (start_offset, len) = { @@ -1562,8 +1561,9 @@ pub(crate) fn join_string_buf_t<'a, T: PathChar, P: PlatformT>( buf: &'a mut [T], parts: &[&[u8]], ) -> &'a [T] { - // TODO(port): Zig used `parts: anytype` (tuple of slices, possibly mixed - // element types). Rust takes `&[&[u8]]`; transcoding to u16 handled below. + // Zig accepted `parts: anytype` (a tuple of slices, possibly with mixed + // element types). Rust takes `&[&[u8]]` — every in-tree caller passes u8 + // parts — and transcodes to u16 below when `T == u16`. let mut written: usize = 0; let mut temp_buf_: [T; 4096] = [T::from_u8(0); 4096]; let mut temp_buf: &mut [T] = &mut temp_buf_; @@ -1595,8 +1595,7 @@ pub(crate) fn join_string_buf_t<'a, T: PathChar, P: PlatformT>( written += 1; } - // TODO(port): Zig inspected std.meta.Elem(@TypeOf(part)); we always - // receive u8 parts, so transcode iff T == u16. + // Parts are always u8 (see fn-level comment); transcode iff T == u16. written += T::write_u8_part(&mut temp_buf[written..], part); } @@ -1694,9 +1693,9 @@ pub fn join_abs_string_buf_z<'a, P: PlatformT>( unsafe { ZStr::from_raw(r.as_ptr(), r.len()) } } -// TODO(port): Zig used `comptime ReturnType: type` to vary `[:0]const u8` vs +// Zig used `comptime ReturnType: type` to vary `[:0]const u8` vs // `[]const u8`. We always return `&[u8]`; when `IS_SENTINEL` a NUL is written -// at `result.len()` and callers re-wrap as `ZStr`. +// at `result.len()` and callers (e.g. `join_abs_string_buf_z`) re-wrap as `ZStr`. fn _join_abs_string_buf<'a, const IS_SENTINEL: bool, P: PlatformT>( _cwd: &'a [u8], buf: &'a mut [u8], @@ -1729,7 +1728,7 @@ fn _join_abs_string_buf<'a, const IS_SENTINEL: bool, P: PlatformT>( && parts[0].len() == 1 && parts[0][0] == SEP_POSIX { - // PORT NOTE: Zig returned the literal `"/"` (`[:0]const u8` — NUL-backed). + // Zig returned the literal `"/"` (`[:0]const u8` — NUL-backed). // Rust `b"/"` is NOT NUL-terminated and not in `buf`, breaking callers // that assume buf-backing (`ZStr::from_raw`, trailing-slash check). // Write into `buf` so the result is always buf-backed and sentinel-safe. @@ -1785,7 +1784,7 @@ fn _join_abs_string_buf<'a, const IS_SENTINEL: bool, P: PlatformT>( out += part.len(); } - // PORT NOTE: reshaped for borrowck — stash leading separator into a local + // reshaped for borrowck — stash leading separator into a local // [u8; 8] (max len: NT prefix `\\?\` = 4) so we don't hold a borrow into // temp_buf across the normalize call below. let mut leading_buf = [0u8; 8]; @@ -2017,7 +2016,7 @@ pub(crate) fn normalize_string_node_t<'a, T: PathChar, P: PlatformT>( // `normalize_string_generic` handles absolute path cases for windows // we should not prefix with / - // PORT NOTE: reshaped for borrowck — track an offset instead of reslicing. + // reshaped for borrowck — track an offset instead of reslicing. let buf_off: usize = if P::P == Platform::Windows { 0 } else { 1 }; let separator_t = T::from_u8(P::P.separator()); @@ -2187,8 +2186,8 @@ impl PosixToWinNormalizer { ) -> Result<&'a mut ZStr, bun_core::Error> { Self::resolve_cwd_with_external_buf_z(&mut self._raw_bytes, maybe_posix_path) } - // TODO(windows): on posix `_raw_bytes` is `()`; the Zig version still - // null-terminates into a buffer. Callers on posix should use + // On posix `_raw_bytes` is `()` so `resolve_cwd_z` is windows-only; the + // Zig version still null-terminated into a buffer. Callers on posix use // `resolve_cwd_with_external_buf_z` with an explicit PathBuffer. // underlying implementation: @@ -2288,7 +2287,7 @@ impl PosixToWinNormalizer { if root.len() == 1 { debug_assert!(is_sep_any(root[0])); if strings::is_windows_absolute_path_missing_drive_letter::(maybe_posix_path) { - // PORT NOTE: reshaped for borrowck — `getcwd` writes into + // reshaped for borrowck — `getcwd` writes into // `buf` and returns a borrow of it; capture the lengths we // need, drop the borrow, then re-slice `buf`. let sr_len = { @@ -2336,7 +2335,7 @@ impl PosixToWinNormalizer { if root.len() == 1 { debug_assert!(is_sep_any(root[0])); if strings::is_windows_absolute_path_missing_drive_letter::(maybe_posix_path) { - // PORT NOTE: reshaped for borrowck — see resolve_cwd above. + // reshaped for borrowck — see resolve_cwd above. let sr_len = { let cwd = bun_core::getcwd(buf)?; windows_filesystem_root(cwd.as_bytes()).len() diff --git a/src/paths/string_paths.rs b/src/paths/string_paths.rs index 0cde5490a943..11bd464b4165 100644 --- a/src/paths/string_paths.rs +++ b/src/paths/string_paths.rs @@ -79,7 +79,7 @@ pub fn without_nt_prefix(path: &[T]) -> &[T] { if !cfg!(windows) { return path; } - // PORT NOTE: Zig dispatched hasPrefixComptime vs hasPrefixComptimeUTF16 on T; + // Zig dispatched hasPrefixComptime vs hasPrefixComptimeUTF16 on T; // collapsed to a local `has_prefix_ascii_t` (widens each ASCII byte via T::from_u8). if has_prefix_ascii_t(path, &windows::NT_OBJECT_PREFIX_U8) { return &path[windows::NT_OBJECT_PREFIX.len()..]; @@ -234,7 +234,7 @@ pub(crate) fn to_w_path_normalized16<'a>(wbuf: &'a mut [u16], path: &[u16]) -> & return wstr_in_buf(wbuf, 0); } - // PORT NOTE: reshaped for borrowck — Zig wrote into wbuf and then re-sliced wbuf; + // Reshaped for borrowck — Zig wrote into wbuf and then re-sliced wbuf; // here we capture the length and re-derive the mutable slice. let len = { let mut path_to_use = normalize_slashes_only_t::(wbuf, path); @@ -262,7 +262,7 @@ pub(crate) fn normalize_slashes_only_t< buf: &'a mut [T], path: &'a [T], ) -> &'a [T] { - // PORT NOTE: was `const _: () = assert!(..)` but Rust forbids const items + // Was `const _: () = assert!(..)` but Rust forbids const items // referencing outer const-generic params (E0401). Debug-assert instead. debug_assert!(DESIRED_SLASH == b'/' || DESIRED_SLASH == b'\\'); let undesired_slash: u8 = if DESIRED_SLASH == b'/' { b'\\' } else { b'/' }; @@ -284,9 +284,9 @@ pub(crate) fn normalize_slashes_only_t< path } -// TODO(port): `desired_slash` was `comptime u8` in Zig; kept as runtime arg here since +// `desired_slash` was `comptime u8` in Zig; kept as a runtime arg here since a // const-generic value can't be forwarded from a runtime call site without duplication. -// PERF(port): was comptime monomorphization. +// PERF(port): was comptime monomorphization — profile if it shows up on a hot path. pub fn normalize_slashes_only<'a>( buf: &'a mut [u8], utf8: &'a [u8], @@ -371,7 +371,7 @@ pub(crate) fn to_w_path_maybe_dir<'a, const ADD_TRAILING_LASH: bool>( debug_assert!(!wbuf.is_empty()); let cap = wbuf.len().saturating_sub(1 + (ADD_TRAILING_LASH as usize)); - // PORT NOTE: Zig used `bun.simdutf.convert.utf8.to.utf16.le.with_errors`; + // Zig used `bun.simdutf.convert.utf8.to.utf16.le.with_errors`; // route through the checked `try_convert_utf8_to_utf16_in_buffer` (same // simdutf primitive + WTF-8 fallback) to avoid a `bun_simdutf` crate dep. // @@ -421,7 +421,7 @@ pub fn clone_normalizing_separators(input: &[u8]) -> Vec { if base[0] == crate::SEP { buf[0] = crate::SEP; } - // PORT NOTE: reshaped for borrowck — track index instead of moving slice ptr. + // Reshaped for borrowck — track index instead of moving slice ptr. let mut i: usize = (base[0] == crate::SEP) as usize; for token in base.split(|b| *b == crate::SEP).filter(|s| !s.is_empty()) { diff --git a/src/perf/generated_perf_trace_events.rs b/src/perf/generated_perf_trace_events.rs index e0fe0b990063..948f6211233c 100644 --- a/src/perf/generated_perf_trace_events.rs +++ b/src/perf/generated_perf_trace_events.rs @@ -1,29 +1,36 @@ -// GENERATED: re-run scripts/generate-perf-trace-events.sh with .rs output -// (source: src/perf/generated_perf_trace_events.zig — defines #[repr(i32)] enum PerfEvent) -// TODO(port): teach the generator to emit Rust; do not hand-maintain this file. - -// TODO(port): stub until generator emits real variants — variants are added -// piecemeal as call sites un-gate. +// Hand-maintained: scripts/generate-perf-trace-events.sh scans Zig sources and +// emits only the .h and .zig outputs, so this file mirrors +// src/perf/generated_perf_trace_events.zig manually until the generator learns +// to emit Rust. Variants are added piecemeal as call sites un-gate; the +// discriminants are assigned EXPLICITLY to the canonical ids from +// src/jsc/bindings/generated_perf_trace_events.h (the Darwin signpost path +// passes `event as i32`, so the numeric id must match the generated header). #[repr(i32)] #[derive(Clone, Copy, Debug)] pub enum PerfEvent { - _Stub = 0, - FolderResolverReadPackageJSONFromDiskWorkspace, - FolderResolverReadPackageJSONFromDiskFolder, - ModuleResolverResolve, + /// Placeholder for call sites whose real event variant has not been + /// mirrored from the generated header yet. Not a header id. + _Stub = -1, + FolderResolverReadPackageJSONFromDiskFolder = 34, + FolderResolverReadPackageJSONFromDiskWorkspace = 35, + ModuleResolverResolve = 47, + StandaloneModuleGraphSerialize = 54, + SymbolsFollowAll = 55, } impl From for &'static str { fn from(e: PerfEvent) -> &'static str { match e { PerfEvent::_Stub => "_Stub", - PerfEvent::FolderResolverReadPackageJSONFromDiskWorkspace => { - "FolderResolver.readPackageJSONFromDisk.workspace" - } PerfEvent::FolderResolverReadPackageJSONFromDiskFolder => { "FolderResolver.readPackageJSONFromDisk.folder" } + PerfEvent::FolderResolverReadPackageJSONFromDiskWorkspace => { + "FolderResolver.readPackageJSONFromDisk.workspace" + } PerfEvent::ModuleResolverResolve => "ModuleResolver.resolve", + PerfEvent::StandaloneModuleGraphSerialize => "StandaloneModuleGraph.serialize", + PerfEvent::SymbolsFollowAll => "Symbols.followAll", } } } @@ -34,13 +41,15 @@ impl PerfEvent { pub fn as_cstr(self) -> &'static core::ffi::CStr { match self { PerfEvent::_Stub => c"_Stub", - PerfEvent::FolderResolverReadPackageJSONFromDiskWorkspace => { - c"FolderResolver.readPackageJSONFromDisk.workspace" - } PerfEvent::FolderResolverReadPackageJSONFromDiskFolder => { c"FolderResolver.readPackageJSONFromDisk.folder" } + PerfEvent::FolderResolverReadPackageJSONFromDiskWorkspace => { + c"FolderResolver.readPackageJSONFromDisk.workspace" + } PerfEvent::ModuleResolverResolve => c"ModuleResolver.resolve", + PerfEvent::StandaloneModuleGraphSerialize => c"StandaloneModuleGraph.serialize", + PerfEvent::SymbolsFollowAll => c"Symbols.followAll", } } } diff --git a/src/perf/lib.rs b/src/perf/lib.rs index b78e6053ddb6..2285c7553f76 100644 --- a/src/perf/lib.rs +++ b/src/perf/lib.rs @@ -103,15 +103,10 @@ pub(crate) fn is_enabled() -> bool { /// /// When instruments is not connected, this is a no-op. /// -/// When adding a new event, you must run `scripts/generate-perf-trace-events.sh` to update the list of trace events. -/// -/// Tip: Make sure you write bun.perf.trace() with a string literal exactly instead of passing a variable. -/// -/// It has to be compile-time known this way because they need to become string literals in C. -// PORT NOTE: Zig took `comptime name: [:0]const u8` and used `@hasField(PerfEvent, name)` + -// `@compileError` to validate membership at compile time, then `@field(PerfEvent, name)` to get -// the enum value. In Rust, taking `PerfEvent` directly gives the same compile-time guarantee via -// the type system — the @hasField/@compileError block is dropped. +/// Pass a `PerfEvent` variant; the type system guarantees the event is a +/// compile-time-known member of the generated set. Event names must become +/// string literals in C, so when adding a new event you must run +/// `scripts/generate-perf-trace-events.sh` to regenerate the list. pub fn trace(event: PerfEvent) -> Ctx { if !is_enabled() { // PERF(port): @branchHint(.likely) — profile if it shows up on a hot path. diff --git a/src/perf/system_timer.rs b/src/perf/system_timer.rs index 8b0b157a02da..8711f5e5df74 100644 --- a/src/perf/system_timer.rs +++ b/src/perf/system_timer.rs @@ -12,9 +12,9 @@ impl Timer { Ok(Self) } - // TODO(port): Zig used `@compileError` here, which fires lazily only if the - // fn is referenced. Rust's `compile_error!` fires unconditionally, so we - // keep the fn signatures for structural parity and trap at runtime instead. + // Zig used `@compileError` here, which fires lazily only if the fn is + // referenced. Rust's `compile_error!` fires unconditionally, so we keep + // the fn signatures for structural parity and trap at runtime instead. pub fn read(&self) -> u64 { unreachable!("FeatureFlags.tracing should be disabled in WASM"); } @@ -39,7 +39,9 @@ pub struct Timer { #[cfg(not(target_family = "wasm"))] impl Timer { pub fn start() -> Result { - // TODO(port): narrow error set + // Infallible here, but kept fallible to mirror Zig's + // `std.time.Timer.start()` (`error{TimerUnsupported}!Timer`) signature + // that callers already handle. Ok(Self { started: std::time::Instant::now(), }) diff --git a/src/perf/tracy.rs b/src/perf/tracy.rs index 39eb1674eb36..d6876dbbb8eb 100644 --- a/src/perf/tracy.rs +++ b/src/perf/tracy.rs @@ -14,7 +14,7 @@ use bun_core::env_var; pub const ENABLE_ALLOCATION: bool = false; pub const ENABLE_CALLSTACK: bool = false; -// PORT NOTE: Zig `pub var enable = false;` — use AtomicBool so reads are safe +// Zig used a plain `pub var enable = false;`; an AtomicBool keeps reads safe // from any thread without `unsafe`. All loads/stores are Relaxed (matches Zig's // unsynchronized plain global). static ENABLE: AtomicBool = AtomicBool::new(false); @@ -80,16 +80,12 @@ impl ___tracy_c_zone_context { pub type Ctx = ___tracy_c_zone_context; +/// Begin a Tracy zone. Zig's `trace(comptime src: std.builtin.SourceLocation)` +/// synthesized a per-monomorphization static source location; in Rust the +/// per-callsite static is emitted by the `tracy_trace!` macro, which calls +/// this with the already-built static. #[inline] pub fn trace(srcloc: &'static ___tracy_source_location_data) -> Ctx { - // TODO(port): Zig signature is `trace(comptime src: std.builtin.SourceLocation)` - // and synthesizes a per-monomorphization `static holder.srcloc`. Rust cannot - // create a fresh static per generic-value instantiation without a macro, so this - // fn takes the already-built static directly. TODO(port): provide a `tracy_trace!()` - // macro that emits a per-callsite - // `static SRCLOC: ___tracy_source_location_data { function: concat!(module_path!(),"\0"), - // file: concat!(file!(),"\0"), line: line!(), .. }` and calls this — do NOT - // reintroduce an intermediate SourceLocation struct. if !enable() { return Ctx::default(); } @@ -101,12 +97,13 @@ pub fn trace(srcloc: &'static ___tracy_source_location_data) -> Ctx { } } +/// Begin a named Tracy zone. Zig's `traceNamed(comptime src, comptime name)` +/// folded the name into a per-monomorphization static; in Rust the +/// per-callsite static (with `name` set) is emitted by the +/// `tracy_trace_named!` macro, which calls this with the already-built +/// static. #[inline] pub fn trace_named(srcloc: &'static ___tracy_source_location_data) -> Ctx { - // TODO(port): Zig `traceNamed(comptime src, comptime name)` — same - // per-callsite-static issue as `trace`. The `name` is folded into the - // caller-provided static `srcloc.name`. TODO(port): add a - // `tracy_trace_named!("name")` macro. if !enable() { return Ctx::default(); } @@ -118,6 +115,48 @@ pub fn trace_named(srcloc: &'static ___tracy_source_location_data) -> Ctx { } } +/// Begin a Tracy zone with a per-callsite static source location, mirroring +/// Zig's `tracy.trace(@src())`. Expands to a fresh `static SRCLOC` per +/// callsite (no intermediate `SourceLocation` struct) and calls +/// `tracy::trace`. +#[macro_export] +macro_rules! tracy_trace { + () => {{ + static SRCLOC: $crate::tracy::___tracy_source_location_data = + $crate::tracy::___tracy_source_location_data { + name: ::core::ptr::null(), + function: concat!(module_path!(), "\0") + .as_ptr() + .cast::<::core::ffi::c_char>(), + file: concat!(file!(), "\0").as_ptr().cast::<::core::ffi::c_char>(), + line: line!(), + color: 0, + }; + $crate::tracy::trace(&SRCLOC) + }}; +} + +/// Begin a named Tracy zone with a per-callsite static source location, +/// mirroring Zig's `tracy.traceNamed(@src(), name)`. The name must be a +/// string literal; it is NUL-terminated and stored in the per-callsite +/// static, then passed to `tracy::trace_named`. +#[macro_export] +macro_rules! tracy_trace_named { + ($name:literal) => {{ + static SRCLOC: $crate::tracy::___tracy_source_location_data = + $crate::tracy::___tracy_source_location_data { + name: concat!($name, "\0").as_ptr().cast::<::core::ffi::c_char>(), + function: concat!(module_path!(), "\0") + .as_ptr() + .cast::<::core::ffi::c_char>(), + file: concat!(file!(), "\0").as_ptr().cast::<::core::ffi::c_char>(), + line: line!(), + color: 0, + }; + $crate::tracy::trace_named(&SRCLOC) + }}; +} + pub fn tracy_allocator() -> TracyAllocator { TracyAllocator::init() } @@ -128,10 +167,11 @@ pub fn tracy_allocator() -> TracyAllocator { /// Per PORTING.md §Allocators, `src/perf/` is not an AST crate, so the /// `std.mem.Allocator` parameter is deleted and the parent is implicitly the /// global mimalloc (`#[global_allocator]`). -// TODO(port): re-express as a `core::alloc::GlobalAlloc` shim over -// `bun_alloc::Mimalloc` that emits tracy alloc/free hooks (alloc/alloc_named/ -// free/free_named below). Only relevant if `ENABLE_ALLOCATION` is flipped on — -// it is `false` today, so this is dead in practice. +// The tracy alloc/free hooks (`___tracy_emit_memory_alloc` etc.) are not +// wired up: `ENABLE_ALLOCATION` is `false` (matching Zig's +// `enable_allocation = false`), so allocation tracking is intentionally dead. +// If it is ever flipped on, express it as a `core::alloc::GlobalAlloc` shim +// over the global mimalloc that emits the tracy hooks. pub struct TracyAllocator {} impl TracyAllocator { @@ -433,7 +473,6 @@ fn ___tracy_emit_frame_mark(name: *const c_char) { pub fn init() -> bool { #[cfg(target_family = "wasm")] { - // TODO(port): bun.Environment.isNative — assuming "native" == not-wasm return false; } @@ -505,7 +544,7 @@ impl Default for ___tracy_source_location_data { } } -// PORT NOTE: Zig defined `Handle` as a per-instantiation local struct inside +// Zig defined `Handle` as a per-instantiation local struct inside // `dlsym`, giving each (Type, symbol) pair its own static handle. That is // wasteful (re-dlopens libtracy per symbol) and not expressible in Rust without // const-generic strings. Use a single shared handle instead — dlopen on the @@ -585,9 +624,9 @@ fn dlsym(symbol: &'static core::ffi::CStr) -> Option { )))] const PATHS_TO_TRY: &[&core::ffi::CStr] = &[]; - // TODO(port): RTLD flags — Zig used `@bitCast(@as(i32, -2))` on - // macOS (RTLD_DEFAULT semantics for dlopen?) and default `.{}` on - // Linux. Map to bun_sys::dlopen flag type once defined. + // RTLD flags passed straight through to dlopen: Zig used + // `@bitCast(@as(i32, -2))` on macOS and the default `.{}` (0) + // elsewhere; the same raw values are kept here. #[cfg(target_os = "macos")] let rtld: i32 = -2; #[cfg(not(target_os = "macos"))] @@ -615,7 +654,7 @@ fn dlsym(symbol: &'static core::ffi::CStr) -> Option { } } - // PORT NOTE: Zig `bun.C.dlsymWithHandle` cached per-(Type,symbol) via a comptime + // Zig `bun.C.dlsymWithHandle` cached per-(Type,symbol) via a comptime // local static. Rust has no const-generic-string statics; do an uncached lookup // through the shared handle. PERF(port): per-symbol OnceLock cache — profile if it shows up on a hot path. let p = bun_sys::dlsym_impl(handle_getter(), sym_z)?; @@ -625,8 +664,24 @@ fn dlsym(symbol: &'static core::ffi::CStr) -> Option { } } -// TODO(port): Zig pulls this from `@import("build_options").tracy_callstack_depth`. -// Wire to build-time config (env! / cfg-set const). +// Zig pulled this from `build_options.tracy_callstack_depth`; that build +// option no longer exists anywhere in the tree, so the value is fixed here. +// Only consulted when `ENABLE_CALLSTACK` is true. const CALLSTACK_DEPTH: c_int = 10; +#[cfg(test)] +mod tests { + // Expand both macros so the per-callsite statics (and their const + // initializers) are compile-checked even though no runtime caller exists + // yet. With `enable()` false (the default), `trace`/`trace_named` return + // `Ctx::default()` and `end()` is a no-op, so this is safe to run. + #[test] + fn trace_macros_expand() { + let ctx = crate::tracy_trace!(); + ctx.end(); + let named = crate::tracy_trace_named!("test"); + named.end(); + } +} + // ported from: src/perf/tracy.zig diff --git a/src/picohttp/lib.rs b/src/picohttp/lib.rs index 71d726aa50d8..784f17690090 100644 --- a/src/picohttp/lib.rs +++ b/src/picohttp/lib.rs @@ -7,7 +7,7 @@ use bstr::BStr; use bun_core::output::enable_ansi_colors_stderr; use bun_core::pretty_fmt; -// PORT NOTE: `Header::clone` / `Request::clone` / `Response::clone` need the +// `Header::clone` / `Request::clone` / `Response::clone` need the // unbound-lifetime `append_raw` so they can interleave appends and stash the // raw ptr/len pairs — the Zig original returns aliasing `[]const u8` with no // lifetime tracking. The buffer is heap-owned; callers keep the builder (or @@ -163,7 +163,7 @@ impl Header { pub fn clone(&self, builder: &mut StringBuilder) -> Header { // SAFETY: returned slices alias `builder`'s heap buffer; caller of the // outer `clone` keeps the builder (or its moved-out buffer) alive for - // the lifetime of the cloned `Header` (see PORT NOTE on `StringBuilder`). + // the lifetime of the cloned `Header` (see the comment on `StringBuilder`). let name = unsafe { builder.append_raw(self.name()) }; // SAFETY: same buffer-lifetime invariant as `name` above. let value = unsafe { builder.append_raw(self.value()) }; @@ -244,9 +244,8 @@ impl fmt::Display for HeaderCurlFormatter<'_> { #[derive(Clone, Copy, Default)] pub struct HeaderList<'a> { pub list: &'a [Header], - // TODO(port): Zig field is `[]Header` (mutable slice) but only ever read - // through `*const List`; using `&'a [Header]` here. Revisit if a caller - // mutates through it. + // Zig's field was `[]Header` (mutable slice) but it is only ever read + // through `*const List`, so a shared `&'a [Header]` is sufficient here. } impl<'a> HeaderList<'a> { diff --git a/src/platform/darwin.rs b/src/platform/darwin.rs index a66e49adc761..950fb6f27d5c 100644 --- a/src/platform/darwin.rs +++ b/src/platform/darwin.rs @@ -12,7 +12,6 @@ pub mod nocancel { use core::ffi::{c_char, c_int, c_uint, c_void}; use libc::{iovec, off_t, pollfd, sigset_t, sockaddr, socklen_t, timespec}; - // TODO(port): move to platform_sys unsafe extern "C" { #[link_name = "recvfrom$NOCANCEL"] pub fn recvfrom_nocancel( diff --git a/src/ptr/CowSlice.rs b/src/ptr/CowSlice.rs index 5330df6fc611..91e8d396b846 100644 --- a/src/ptr/CowSlice.rs +++ b/src/ptr/CowSlice.rs @@ -28,11 +28,14 @@ pub type CowSlice = CowSliceZ; /// "Copy on write" slice with optional sentinel termination. See module docs. /// /// `Z = true` means the backing storage has a sentinel element at `[len]` -/// (the sentinel value is assumed to be the zero value of `T`). -// TODO(port): Zig's `comptime sentinel: ?T` allowed an arbitrary sentinel value; -// Rust const generics cannot express `Option` for generic `T`, so this port -// uses a `bool` and assumes sentinel == 0 when `Z`. Revisit if a non-zero -// sentinel is ever needed. +/// (the sentinel value is assumed to be the zero value of `T`, produced via +/// `T::default()`). +/// +/// Zig's `comptime sentinel: ?T` allowed an arbitrary sentinel value; Rust +/// const generics cannot express `Option` for generic `T`, so this port +/// uses a `bool` and assumes sentinel == 0 when `Z`. For `Z = true`, owned +/// allocations are physically `len + 1` elements (logical `len` plus the +/// sentinel), matching Zig's `dupeZ`/sentinel-aware `free`. pub struct CowSliceZ { /// Pointer to the underlying data. Do not access this directly. /// @@ -106,19 +109,36 @@ impl CowSliceZ { self.debug.map(|d| unsafe { d.as_ref() }) } - // TODO(port): Zig exposed `pub const Slice` / `SliceMut` associated type - // aliases that switched on `sentinel` (`[:z]const T` vs `[]const T`). Rust - // has no inherent associated type aliases; callers use `&[T]` / `&mut [T]` - // directly. For `Z = true` the NUL is at `slice()[len]` in backing storage. + // Zig exposed `Slice` / `SliceMut` type aliases (`[:z]const T` vs + // `[]const T`); callers here use `&[T]` / `&mut [T]` directly. For + // `Z = true` the sentinel is at `slice()[len]` in the backing storage. + + /// Number of elements physically backing an owned allocation of logical + /// length `len`: for `Z = true` the trailing sentinel adds one element + /// (Zig's `[:z]T` slices carried this in the type; `allocator.free` of a + /// sentinel slice freed `len + 1`). + #[inline] + const fn physical_len(len: usize) -> usize { + len + Z as usize + } /// Create a new Cow that owns its allocation. /// /// `data` is transferred into the returned string, and must be freed with /// `Drop` when the string and its borrows are done being used. + /// + /// For `Z = true`, `data` must include the trailing sentinel as its final + /// element; the logical length is `data.len() - 1`. pub fn init_owned(data: Box<[T]>) -> Self { - // PORT NOTE: Zig asserted ownership at runtime via a debug allocator - // wrapper. In Rust the `Box<[T]>` type already proves unique ownership. - let len = data.len(); + // Zig asserted ownership at runtime via a debug allocator wrapper; in + // Rust the `Box<[T]>` type already proves unique ownership. The + // `checked_sub` enforces the sentinel contract even in release builds: + // an empty box for `Z = true` would otherwise underflow to a near-max + // length and make `slice()` instant UB. + let len = data + .len() + .checked_sub(Z as usize) + .expect("CowSliceZ<_, true>::init_owned requires a trailing sentinel element"); let ptr = bun_core::heap::into_raw(data).cast::(); Self { ptr, @@ -129,18 +149,30 @@ impl CowSliceZ { } /// Create a new Cow that copies `data` into a new allocation. + /// + /// For `Z = true` this is Zig's `allocator.dupeZ(T, data)`: the new + /// allocation holds `data.len() + 1` elements with a trailing zero + /// (`T::default()`) sentinel. pub fn init_dupe(data: &[T]) -> Result where T: Clone + Default, { - // TODO(port): `allocator.dupeZ(T, data)` for `Z = true` — must allocate - // len+1 with a trailing zero sentinel. `Vec::into_boxed_slice` shrinks - // capacity to len, so the sentinel cannot live in spare capacity; the - // Box must hold len+1 and `Flags::len`/`Drop`/`take_slice` must be - // Z-aware (free len+1, expose len). Stubbed to plain dupe for now — - // sentinel is NOT preserved. - let bytes: Box<[T]> = Box::<[T]>::from(data); - Ok(Self::init_owned(bytes)) + Ok(Self::init_owned(Self::dupe_maybe_z(data))) + } + + /// Copy `data` into a fresh boxed slice, appending the zero sentinel when + /// `Z = true`. `Vec::into_boxed_slice` shrinks capacity to length, so the + /// sentinel is part of the box (physical `len + 1`), never spare capacity. + fn dupe_maybe_z(data: &[T]) -> Box<[T]> + where + T: Clone + Default, + { + let mut vec = Vec::with_capacity(Self::physical_len(data.len())); + vec.extend_from_slice(data); + if Z { + vec.push(T::default()); + } + vec.into_boxed_slice() } /// Create a Cow that wraps a static slice. @@ -205,6 +237,9 @@ impl CowSliceZ { /// Caller owns the returned memory and must deinitialize it when done. /// `self` may be re-used. An allocation will be incurred if and only if /// `self` is not owned. + /// + /// For `Z = true` the returned box includes the trailing sentinel element + /// (its length is the logical length plus one). pub fn take_slice(&mut self) -> Result, AllocError> where T: Clone + Default, @@ -226,8 +261,14 @@ impl CowSliceZ { // free the very `ptr[..len]` allocation we are about to hand back. // `ManuallyDrop::new(mem::replace(..))` resets `self` without dropping. let _ = core::mem::ManuallyDrop::new(core::mem::replace(self, Self::EMPTY)); - // SAFETY: owned ⇒ `ptr[..len]` was produced by `heap::alloc`. - Ok(unsafe { bun_core::heap::take(core::ptr::slice_from_raw_parts_mut(ptr, len)) }) + // SAFETY: owned ⇒ `ptr[..physical_len(len)]` was produced by `heap::alloc` + // (owned Z-cows always physically hold `len + 1` elements). + Ok(unsafe { + bun_core::heap::take(core::ptr::slice_from_raw_parts_mut( + ptr, + Self::physical_len(len), + )) + }) } /// Returns a new string that borrows this string's data. @@ -258,8 +299,10 @@ impl CowSliceZ { /// assertions get performed. pub fn borrow_subslice(&self, start: usize, end: Option) -> Self { let end_ = end.unwrap_or(self.flags.len()); - // TODO(port): Zig's sentinel-aware `str.ptr[start..end_ :s]` asserted - // the sentinel is present at `end_`. No equivalent check here. + // Zig's sentinel-aware re-slice `str.ptr[start..end_ :s]` asserted the + // sentinel is present at `end_`; checking that here would force a + // `PartialEq + Default` bound on every caller, so the `Z = true` path + // skips that debug assertion. let mut result = self.borrow(); // SAFETY: const semantics are enforced by is_owned flag; `start <= end_ <= len`. result.ptr = unsafe { self.ptr.add(start) }; @@ -288,9 +331,9 @@ impl CowSliceZ { { debug_assert!(!self.is_owned()); - // TODO(port): `allocator.dupeZ` for `Z = true` — see `init_dupe`. - // Sentinel is NOT preserved in this stub. - let bytes: Box<[T]> = Box::<[T]>::from(self.slice()); + // Zig `allocator.dupeZ` for `Z = true`: the duplicate gets a fresh + // trailing zero sentinel (see `dupe_maybe_z`). + let bytes: Box<[T]> = Self::dupe_maybe_z(self.slice()); self.ptr = bun_core::heap::into_raw(bytes).cast::(); // flags.len already correct (unchanged) self.flags.set_is_owned(true); @@ -311,6 +354,10 @@ impl CowSliceZ { } /// Does not include debug safety checks. + /// + /// `data` is the logical slice. For `Z = true` with `is_owned = true`, the + /// backing allocation must physically hold `data.len() + 1` elements (the + /// sentinel beyond the slice), as `Drop` frees the physical length. pub fn init_unchecked(data: &[T], is_owned: bool) -> Self { Self { // SAFETY: const semantics are enforced by is_owned flag @@ -330,7 +377,7 @@ impl Drop for CowSliceZ { fn drop(&mut self) { #[cfg(debug_assertions)] if let Some(dbg) = self.debug_data() { - // PORT NOTE: Zig asserted `debug.allocator.vtable == allocator.vtable` + // Zig asserted `debug.allocator.vtable == allocator.vtable` // here. With a single global allocator that check is moot. if self.is_owned() { let borrows = dbg.mutex.lock(); @@ -349,11 +396,12 @@ impl Drop for CowSliceZ { } } if self.flags.is_owned() { - // SAFETY: owned ⇒ `ptr[..len]` came from `heap::alloc`. + // SAFETY: owned ⇒ `ptr[..physical_len(len)]` came from `heap::alloc` + // (owned Z-cows always physically hold `len + 1` elements). drop(unsafe { bun_core::heap::take(core::ptr::slice_from_raw_parts_mut( self.ptr, - self.flags.len(), + Self::physical_len(self.flags.len()), )) }); } @@ -362,7 +410,7 @@ impl Drop for CowSliceZ { impl core::fmt::Display for CowSliceZ { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - // PORT NOTE: Zig `writer.writeAll(str.slice())` wrote raw bytes. + // Zig `writer.writeAll(str.slice())` wrote raw bytes. // `BStr` gives lossy Display over `[u8]` without UTF-8 validation. core::fmt::Display::fmt(bstr::BStr::new(self.slice()), f) } @@ -371,7 +419,7 @@ impl core::fmt::Display for CowSliceZ { #[cfg(debug_assertions)] struct DebugData { /// Guards `borrows` (number of active borrows). - // PORT NOTE: Zig used `bun.Mutex` with `borrows` as a separate field; + // Zig used `bun.Mutex` with `borrows` as a separate field; // folded into the mutex payload here. `bun_core::Mutex` (poison-free // `std::sync` wrapper) is used because `bun_ptr` sits below `bun_threading`. mutex: bun_core::Mutex, @@ -423,6 +471,47 @@ mod tests { // borrow is unaffected by str being dropped assert_eq!(borrow.slice(), b"hello"); } + + /// Exercises every `Z = true` path so the sentinel-aware alloc/free + /// symmetry (physical `len + 1` everywhere) is monomorphized, compiled, + /// and run — there are no other in-tree instantiations of `Z = true`. + #[test] + fn cow_slice_z_sentinel() { + // init_dupe = Zig dupeZ: physical len + 1 with trailing 0 sentinel. + let mut str = CowSliceZ::::init_dupe(b"hello").unwrap(); + assert!(str.is_owned()); + assert_eq!(str.length(), 5); + assert_eq!(str.slice(), b"hello"); + // SAFETY: owned Z-cows physically hold `len + 1` elements; index + // `len` is the sentinel. + assert_eq!(unsafe { *str.slice().as_ptr().add(str.length()) }, 0); + + // borrow → to_owned re-dupes through the sentinel-aware path. + let mut borrow = str.borrow(); + assert!(!borrow.is_owned()); + assert_eq!(borrow.slice(), b"hello"); + borrow.to_owned().unwrap(); + assert!(borrow.is_owned()); + assert_eq!(borrow.slice(), b"hello"); + // SAFETY: owned Z-cow ⇒ sentinel at index `len`. + assert_eq!(unsafe { *borrow.slice().as_ptr().add(borrow.length()) }, 0); + drop(borrow); + + // take_slice hands back the physical allocation: logical len + sentinel. + let boxed = str.take_slice().unwrap(); + assert_eq!(boxed.len(), 6); + assert_eq!(&boxed[..5], b"hello"); + assert_eq!(boxed[5], 0); + // `str` was reset to empty and is safe to reuse/drop. + assert_eq!(str.slice(), b""); + + // init_owned round-trips the sentinel-carrying box; Drop frees len + 1 + // (an alloc/free length mismatch here would abort under ASAN/debug heap). + let owned = CowSliceZ::::init_owned(boxed); + assert_eq!(owned.length(), 5); + assert_eq!(owned.slice(), b"hello"); + drop(owned); + } } // ported from: src/ptr/CowSlice.zig diff --git a/src/ptr/lib.rs b/src/ptr/lib.rs index db1588fff1a3..a376a95fdfe2 100644 --- a/src/ptr/lib.rs +++ b/src/ptr/lib.rs @@ -85,8 +85,6 @@ pub use bun_core::{ // runtime crates spell `bun_ptr::callback_ctx::(ctx)`. pub use bun_core::callback_ctx; -pub mod meta; // small, used by other crates - // ported from: src/ptr/ptr.zig // ───────────────────────────────────────────────────────────────────────────── diff --git a/src/ptr/meta.rs b/src/ptr/meta.rs deleted file mode 100644 index 7a11a00f27d7..000000000000 --- a/src/ptr/meta.rs +++ /dev/null @@ -1,103 +0,0 @@ -//! Private utilities used in smart pointer implementations. -//! -//! TODO(port): This module is Zig comptime type-reflection machinery that lets -//! `Owned(P)` / `Shared(P)` accept `*T`, `[]T`, `?*T`, `?[]T` as a single -//! generic `P` and then introspect optionality / constness / element type via -//! `@typeInfo`. Per PORTING.md §Pointers, `bun.ptr.Owned/Shared/AtomicShared` -//! map directly to `Box` / `Rc` / `Arc` (and `Box<[T]>`, -//! `Option>` etc. are spelled at the use site), so the reflection layer -//! has no Rust counterpart. The skeleton below preserves shape for diffing; -//! TODO(refactor): delete this module once the `bun_ptr` callers are migrated -//! to std smart pointers. - -use core::marker::PhantomData; - -#[derive(Copy, Clone, Eq, PartialEq)] -pub enum Kind { - Single, - Slice, -} - -/// A possibly optional slice or single-item pointer type descriptor. -/// E.g., `*u8`, `[]u8`, `?*u8`, `?[]u8` in Zig. -/// -/// TODO(port): In Zig this carries three `type` fields (`Pointer`, -/// `NonOptionalPointer`, `Child`) populated by `@typeInfo`. Rust cannot store -/// types as values; the generics here are a best-effort placeholder so -/// downstream signatures that mention `PointerInfo` have something to name. -pub struct PointerInfo { - /// A possibly optional slice or single-item pointer type. - /// E.g., `*u8`, `[]u8`, `?*u8`, `?[]u8`. - _pointer: PhantomData, - - /// If `Pointer` is an optional pointer, this is the non-optional equivalent. Otherwise, this - /// is the same as `Pointer`. - /// - /// For example, if `Pointer` is `?[]u8`, this is `[]u8`. - _non_optional_pointer: PhantomData, - - /// The type of data stored by the pointer, i.e., the type obtained by dereferencing a - /// single-item pointer or accessing an element of a slice. - /// - /// For example, if `Pointer` is `?[]u8`, this is `u8`. - _child: PhantomData, -} - -impl PointerInfo { - pub fn kind(&self) -> Kind { - // TODO(port): Zig: `switch (@typeInfo(self.NonOptionalPointer).pointer.size)`. - // Rust has no `@typeInfo`; in the std-smart-pointer mapping the caller - // already knows whether it has `Box` vs `Box<[T]>`. - unreachable!("comptime reflection — resolved at type level in Rust") - } - - pub fn is_optional(&self) -> bool { - // TODO(port): Zig: `@typeInfo(self.Pointer) == .optional`. - unreachable!("comptime reflection — resolved at type level in Rust") - } - - pub fn is_const(&self) -> bool { - // TODO(port): Zig: `@typeInfo(self.NonOptionalPointer).pointer.is_const`. - unreachable!("comptime reflection — resolved at type level in Rust") - } - - pub fn parse(_options: ParseOptions) -> Self { - // TODO(port): Zig body walks `@typeInfo(Pointer)`: - // - unwrap `.optional` to get `NonOptionalPointer` - // - assert `.pointer`, extract `Child` - // - reject `.many` / `.c` sizes, `is_volatile`, non-default - // alignment, `is_allowzero`, sentinel-terminated - // - reject `.slice` if `!options.allow_slices` - // - reject `is_const` if `!options.allow_const` - // None of these checks are expressible in Rust's type system as a - // value-level function; they are subsumed by choosing `Box` / - // `Box<[T]>` / `Option>` directly at the call site. - unreachable!("comptime reflection — resolved at type level in Rust") - } -} - -#[derive(Copy, Clone)] -pub struct ParseOptions { - pub allow_const: bool, - pub allow_slices: bool, -} - -impl Default for ParseOptions { - fn default() -> Self { - Self { - allow_const: true, - allow_slices: true, - } - } -} - -// TODO(port): `pub fn AddConst(Pointer: type) type` mutates `@typeInfo` to set -// `.pointer.is_const = true` (recursing through `.optional`) and rebuilds the -// type via `@Type`. Rust has no type-level function for this; the moral -// equivalent is an associated type on a trait. Kept as a marker so callers -// (`Owned::asConst` etc.) have a name to reference. -pub trait AddConst { - type Output; -} - -// ported from: src/ptr/meta.zig diff --git a/src/ptr/owned.rs b/src/ptr/owned.rs index 4af47cb371d8..e5427712bfe0 100644 --- a/src/ptr/owned.rs +++ b/src/ptr/owned.rs @@ -1,6 +1,6 @@ //! Owned pointer abstractions. //! -//! PORT NOTE: The Zig `Owned(comptime Pointer: type)` is a single type-returning function that +//! The Zig `Owned(comptime Pointer: type)` is a single type-returning function that //! dispatches on `@typeInfo(Pointer)` (single-item vs slice, optional vs non-optional). Rust has //! no `@typeInfo`, so the four shapes become four distinct std types per the crate map: //! @@ -29,21 +29,17 @@ /// This type is an alias of `OwnedIn(Pointer, bun.DefaultAllocator)`, and thus has no overhead /// because `bun.DefaultAllocator` is a zero-sized type. /// -/// PORT NOTE: in Rust this is `Box` / `Box<[T]>` / `Option>`. The alias below covers +/// In Rust this is `Box` / `Box<[T]>` / `Option>`. The alias below covers /// only the `*T` (single, non-optional) case, which is the overwhelmingly common one. Slice and -/// optional callers use `Box<[T]>` / `Option>` directly. +/// optional callers use `Box<[T]>` / `Option>` directly (PORTING.md §Pointers). pub type Owned = Box; -// TODO(port): Zig `Owned` accepts a *pointer type* (`*T`, `[]T`, `?*T`, `?[]T`) and branches on -// kind via @typeInfo. Rust generics cannot inspect "is T a slice / is T optional", so a single -// alias cannot cover all four. Audit call sites; they should already be `Box` / -// `Box<[T]>` / `Option>` per PORTING.md §Pointers and LIFETIMES.tsv. /// `std.mem.Allocator` param/field is deleted entirely outside AST crates. `Dynamic` collapses /// to `Box`. pub type Dynamic = Box; -// TODO(port): if any caller genuinely needs a runtime-chosen allocator (e.g. arena vs heap at -// runtime), that caller is in an AST crate and should use `bumpalo::boxed::Box<'bump, T>` or a -// bespoke enum — not this type. Audit call sites if one appears. +// A caller that genuinely needs a runtime-chosen allocator (e.g. arena vs heap at runtime) +// belongs in an AST crate and should use `bumpalo::boxed::Box<'bump, T>` or a bespoke enum — +// not this type. /// An owned pointer or slice, allocated using an instance of `Allocator`. /// @@ -56,10 +52,10 @@ pub type Dynamic = Box; /// If `Allocator` is a zero-sized type, the owned pointer has no overhead compared to a raw /// pointer. /// -/// PORT NOTE: the `Allocator` type parameter is dropped — global mimalloc. See module doc. +/// The `Allocator` type parameter is dropped — global mimalloc. See module doc. pub type OwnedIn = Box; -// TODO(port): nightly `allocator_api` (`Box`) would be the literal translation, but -// PORTING.md forbids it (delete allocator params). Keeping the alias single-param. +// Nightly `allocator_api` (`Box`) would be the literal translation, but PORTING.md +// forbids it (delete allocator params), so the alias stays single-param. // ────────────────────────────────────────────────────────────────────────────────────────────── // The block below mirrors the body of `fn OwnedIn(...) type { return struct { ... } }` so that @@ -86,12 +82,12 @@ pub type OwnedIn = Box; // // .single: alloc(value: Child) AllocError!Self // → Box::new(value) (infallible — aborts on OOM, same as bun.handleOom) -// → Box::try_new(value) // TODO(port): nightly; use if AllocError must propagate +// → Box::try_new(value) // nightly-only; would apply if AllocError must propagate // // .slice: alloc(count: usize, elem: Child) AllocError!Self (shallow copies of `elem`) // → vec![elem; count].into_boxed_slice() where Child: Clone // -// PORT NOTE: Zig returns `AllocError!Self`; Rust `Box::new` aborts on OOM. PORTING.md says +// Zig returns `AllocError!Self`; Rust `Box::new` aborts on OOM. PORTING.md says // `bun.handleOom(expr)` → `expr`, so the fallible form is not needed at most call sites. // ── allocIn ────────────────────────────────────────────────────────────────────────────────── @@ -128,7 +124,7 @@ pub type OwnedIn = Box; // unsafe { Vec::from_raw_parts(ptr, len, cap) }.into_boxed_slice() // optional: → if data.is_null() { None } else { Some(unsafe { bun_core::heap::take(data) }) } // -// PORT NOTE: the Zig doc's caveat about `bun.new` vs `bun.default_allocator.create` is the +// The Zig doc's caveat about `bun.new` vs `bun.default_allocator.create` is the // typed-mimalloc-heap distinction; in Rust both paths go through the same `#[global_allocator]`, // so the caveat does not apply. @@ -146,7 +142,7 @@ pub type OwnedIn = Box; // Frees the memory without calling `deinit` on the underlying data. // deinitShallow(self: *Self) void // → let _ = bun_core::heap::into_raw(ManuallyDrop::into_inner(/* ... */)); -// PORT NOTE: "free the box allocation but don't drop T" is unusual in Rust. The two real uses: +// "Free the box allocation but don't drop T" is unusual in Rust. The two real uses: // (a) T has no Drop → plain `drop(boxed)` is already shallow. // (b) caller moved the payload out first → use `*boxed` to move out, then `drop(boxed)`. // If a literal "dealloc without dropping" is needed: @@ -174,7 +170,6 @@ pub type OwnedIn = Box; // ── PointerAndAllocator / intoRawWithAllocator ─────────────────────────────────────────────── // intoRawWithAllocator(self: *Self) (Pointer, Allocator) | ?(NonOptionalPointer, Allocator) // → bun_core::heap::into_raw(boxed) (allocator dropped from tuple) -// // TODO(port): if any caller actually inspects the returned allocator, it needs rethinking. // ── initNull ───────────────────────────────────────────────────────────────────────────────── // Returns a null owned pointer (only when `Pointer` is optional). @@ -212,7 +207,6 @@ pub type OwnedIn = Box; // Returns a borrowed version of the allocator. // allocator(self: Self) MaybeAllocator // → () (no allocator stored) -// // TODO(port): callers should be deleted along with the allocator threading. // ── getStdAllocator (private) ──────────────────────────────────────────────────────────────── // → deleted. diff --git a/src/ptr/raw_ref_count.rs b/src/ptr/raw_ref_count.rs index 2a464e1819f6..11335ef143e3 100644 --- a/src/ptr/raw_ref_count.rs +++ b/src/ptr/raw_ref_count.rs @@ -3,13 +3,11 @@ //! //! May be useful for implementing the interface required by `ExternalShared`. //! -//! PORT NOTE: Zig's `RawRefCount(Int, thread_safety)` is a comptime type -//! function selecting field types from an enum. Stable Rust cannot vary a -//! field's type from a const generic, and there is no generic `Atomic`. -//! Split into two concrete structs (the only `Int` ever used is `u32`): +//! Two concrete structs rather than one generic (stable Rust cannot vary a +//! field's type from a const generic, and there is no generic `Atomic`): //! `RawRefCount` — single-threaded, plain `u32`, debug `ThreadLock` //! `RawAtomicRefCount` — thread-safe, `AtomicU32` -//! and a `const ATOMIC: bool` alias for callers that want the Zig spelling. +//! plus a `const ATOMIC: bool` alias for callers that select by flag. use core::sync::atomic::{AtomicU32, Ordering}; diff --git a/src/ptr/ref_count.rs b/src/ptr/ref_count.rs index 2eee27c476da..9371da782629 100644 --- a/src/ptr/ref_count.rs +++ b/src/ptr/ref_count.rs @@ -16,7 +16,7 @@ use core::sync::atomic::{AtomicU32, Ordering}; use bun_core::StoredTrace; use bun_core::ThreadLock; -// PORT NOTE(b0): was `bun_collections::{ArrayHashMap, HashMap}` (T1 → upward). +// was `bun_collections::{ArrayHashMap, HashMap}` (T1 → upward). // Debug-only diagnostic storage; std HashMap drops insertion order for `frees` // which is acceptable for leak reports. #[cfg(debug_assertions)] @@ -49,7 +49,7 @@ fn dump_stack_hook(trace: Option<&StoredTrace>, ret_addr: usize) { /// Options for `RefCount` / `ThreadSafeRefCount`. /// -/// PORT NOTE: Zig's `Options` also carried `destructor_ctx: ?type`. A *type* +/// Zig's `Options` also carried `destructor_ctx: ?type`. A *type* /// cannot be a struct field in Rust; it becomes the associated type /// `RefCounted::DestructorCtx` instead. #[derive(Default)] @@ -63,6 +63,18 @@ pub struct Options { // Host-type traits (replace Zig's comptime `field_name` / `destructor` params) // ────────────────────────────────────────────────────────────────────────── +/// `bun.meta.typeBaseName` — strip the module path (and the path inside any +/// leading generic segment) from a `core::any::type_name` string, returning a +/// subslice so the result stays `&'static str`. +/// `"a::b::Foo"` → `"Foo"`. +fn type_base_name(name: &'static str) -> &'static str { + let end = name.find('<').unwrap_or(name.len()); + match name[..end].rfind("::") { + Some(i) => &name[i + 2..], + None => name, + } +} + /// Implemented by types that embed a [`RefCount`] field. /// /// Replaces the Zig comptime parameters `field_name: []const u8`, @@ -74,14 +86,21 @@ pub trait RefCounted: Sized { /// Zig: `options.debug_name orelse bun.meta.typeBaseName(@typeName(T))`. fn debug_name() -> &'static str { - // TODO(port): bun.meta.typeBaseName — strip module path from type_name - core::any::type_name::() + type_base_name(core::any::type_name::()) } /// Zig: `&@field(self, field_name)` — locate the embedded `RefCount` field. /// /// # Safety - /// `this` must point to a live `Self`. + /// `this` must be non-null, properly aligned, and in-bounds of an + /// allocation large enough for `Self` — but it may point to + /// **uninitialized** memory: `offset_of_ref_count` probes this method + /// with a `MaybeUninit` to recover the field offset. + /// + /// Consequently, implementations MUST be pure raw-pointer field + /// projections (`&raw mut (*this).field`): no reads through `this`, no + /// creation of references (`&`/`&mut`) to `*this` or any of its fields, + /// and no other side effects. unsafe fn get_ref_count(this: *mut Self) -> *mut RefCount; /// Zig: the `destructor` fn pointer passed to `RefCount(...)`. @@ -95,13 +114,21 @@ pub trait RefCounted: Sized { pub trait ThreadSafeRefCounted: Sized { /// Zig: `options.debug_name orelse bun.meta.typeBaseName(@typeName(T))`. fn debug_name() -> &'static str { - core::any::type_name::() + type_base_name(core::any::type_name::()) } /// Zig: `&@field(self, field_name)`. /// /// # Safety - /// `this` must point to a live `Self`. + /// `this` must be non-null, properly aligned, and in-bounds of an + /// allocation large enough for `Self` — but it may point to + /// **uninitialized** memory: `offset_of_ref_count_ts` probes this + /// method with a `MaybeUninit` to recover the field offset. + /// + /// Consequently, implementations MUST be pure raw-pointer field + /// projections (`&raw mut (*this).field`): no reads through `this`, no + /// creation of references (`&`/`&mut`) to `*this` or any of its fields, + /// and no other side effects. unsafe fn get_ref_count(this: *mut Self) -> *mut ThreadSafeRefCount; /// Zig: the `destructor: fn (*T) void` parameter. @@ -119,7 +146,7 @@ pub trait ThreadSafeRefCounted: Sized { /// Unifying trait so `RefPtr` works with either ref-count flavor. /// -/// PORT NOTE: Zig's `RefPtr` reflected on `@FieldType(T, "ref_count")` and the +/// Zig's `RefPtr` reflected on `@FieldType(T, "ref_count")` and the /// private `is_ref_count == unique_symbol` marker to accept either mixin. In /// Rust the trait bound IS that check. pub trait AnyRefCounted: Sized { @@ -261,7 +288,7 @@ impl RefCount { // interface implementation - // PORT NOTE: `ref` is a Rust keyword; renamed to `ref_`. + // `ref` is a Rust keyword; renamed to `ref_`. /// # Safety /// `self_` must point to a live `T`. pub unsafe fn ref_(self_: *mut T) { @@ -271,9 +298,9 @@ impl RefCount { { count.debug.assert_valid(); } - // TODO(port): per-type scoped logging — Zig used - // `bun.Output.Scoped(debug_name, .hidden)` keyed on T's name. The - // `bun_core::scoped_log!` macro expects a static scope ident. + // Zig used per-type scoped logging (`bun.Output.Scoped(debug_name, .hidden)` + // keyed on T's name); `bun_core::scoped_log!` requires a static scope + // ident, so all types share the single `ref_count` scope. bun_core::scoped_log!( ref_count, "0x{:x} ref {} -> {}:", @@ -282,7 +309,8 @@ impl RefCount { count.raw_count.get() + 1, ); if DEBUG_STACK_TRACE { - // TODO(port): was dump_current_stack_trace(ret, {frame_count:2, skip:[ref_count.zig]}) + // Zig: dump_current_stack_trace(ret, {frame_count:2, skip:[ref_count.zig]}); + // the frame_count/skip options are not carried over. dump_stack_hook(None, return_address()); } count.assert_single_threaded(); @@ -316,7 +344,8 @@ impl RefCount { count.raw_count.get() - 1, ); if DEBUG_STACK_TRACE { - // TODO(port): was dump_current_stack_trace(ret, {frame_count:2, skip:[ref_count.zig]}) + // Zig: dump_current_stack_trace(ret, {frame_count:2, skip:[ref_count.zig]}); + // the frame_count/skip options are not carried over. dump_stack_hook(None, return_address()); } count.assert_single_threaded(); @@ -358,7 +387,7 @@ impl RefCount { { // SAFETY: self is the `ref_count` field of a live T let ptr: *mut T = unsafe { - bun_core::container_of::(std::ptr::from_mut(self), offset_of_ref_count()) + bun_core::container_of::(std::ptr::from_mut(self), offset_of_ref_count::()) }; self.debug.dump( Some(core::any::type_name::().as_bytes()), @@ -388,10 +417,10 @@ impl RefCount { self.thread.lock_or_assert(); } - // PORT NOTE: `getRefCount(self: *T) *@This()` is the trait method + // `getRefCount(self: *T) *@This()` is the trait method // `T::get_ref_count` above; not duplicated here. - // PORT NOTE: `is_ref_count = unique_symbol` and `ref_count_options` were + // `is_ref_count = unique_symbol` and `ref_count_options` were // comptime-reflection markers for `RefPtr`; replaced by the `AnyRefCounted` // trait bound. } @@ -575,7 +604,7 @@ impl ThreadSafeRefCount { let ptr: *mut T = unsafe { bun_core::container_of::( std::ptr::from_mut(self), - offset_of_ref_count_ts(), + offset_of_ref_count_ts::(), ) }; self.debug.dump( @@ -614,7 +643,7 @@ impl ThreadSafeRefCount { &raw mut self.debug } - // PORT NOTE: `getRefCount` / `is_ref_count` / `ref_count_options` — see + // `getRefCount` / `is_ref_count` / `ref_count_options` — see // notes on RefCount above. } @@ -795,7 +824,7 @@ impl RefPtr { pub unsafe fn init_ref(raw_ptr: *mut T) -> Self { // SAFETY: caller contract unsafe { T::rc_ref(raw_ptr) }; - // PORT NOTE: Zig re-asserted `is_ref_count == unique_symbol` here; + // Zig re-asserted `is_ref_count == unique_symbol` here; // the `T: AnyRefCounted` bound is that assertion. // SAFETY: caller contract unsafe { Self::unchecked_and_unsafe_init(raw_ptr, return_address()) } @@ -821,11 +850,11 @@ impl RefPtr { } // SAFETY: data is live (we hold a ref) unsafe { T::rc_deref_with_context(self.as_ptr(), ctx) }; - // make UAF fail faster (ideally integrate this with ASAN) - // PORT NOTE: Zig did `@constCast(self).data = undefined`. In Rust we - // cannot mutate through `&self` without UnsafeCell; and `RefPtr` is - // consumed-by-value in idiomatic use anyway. - // TODO(port): consider taking `self` by value and dropping. + // Zig poisoned the handle (`@constCast(self).data = undefined`) to make + // UAF fail faster. Rust cannot mutate through `&self` without + // UnsafeCell, and `&self` is load-bearing here: hundreds of call sites + // (including Drop impls and ScopedRef) deref through a borrow they + // cannot move out of, so a by-value signature is not an option. } pub fn dupe_ref(&self) -> Self { @@ -940,7 +969,7 @@ impl RefPtr { // SAFETY: data is live (we hold a ref) unsafe { (*T::rc_debug_data(ptr)).release(self.debug, return_address()) }; } - // PORT NOTE: Zig set `self.data = undefined`; taking `self` by value + // Zig set `self.data = undefined`; taking `self` by value // here makes the RefPtr unusable, which is the same intent. ptr } @@ -1045,7 +1074,7 @@ struct TrackedRef { } /// Not an index, just a unique identifier for the debug data. -// PORT NOTE: Zig used `bun.GenericIndex(u32, TrackedRef)` (opaque type-tag). +// Zig used `bun.GenericIndex(u32, TrackedRef)` (opaque type-tag). #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct TrackedRefId(u32); @@ -1078,15 +1107,16 @@ const MAGIC_VALID: u128 = 0x2f84_e51d; /// Provides Ref tracking. This is not generic over the pointer T to reduce /// analysis complexity. -// PORT NOTE: Zig parameterized on `comptime thread_safe: bool` and selected +// Zig parameterized on `comptime thread_safe: bool` and selected // `Count`/`Lock` types accordingly. Rust cannot select a type from a const // bool without a helper trait; instead we parameterize on the `Count` storage // type directly (`Cell` or `AtomicU32`). The lock and `next_id` are made // uniformly thread-safe (debug-only — perf irrelevant). #[cfg(debug_assertions)] pub struct DebugData { - // TODO(port): Zig used `align(@alignOf(u32))` to reduce u128 alignment to - // 4. Rust cannot under-align a field; consider `[u32; 4]` if layout matters. + // Zig used `align(@alignOf(u32))` to reduce the u128's alignment to 4. + // Rust cannot under-align a field; this is debug-only, so the extra + // padding is irrelevant. magic: u128, lock: bun_core::Mutex<()>, next_id: AtomicU32, @@ -1097,7 +1127,7 @@ pub struct DebugData { #[cfg(debug_assertions)] impl DebugData { - // PORT NOTE(b0): was `pub const EMPTY` — std HashMap::new() is non-const. + // was `pub const EMPTY` — std HashMap::new() is non-const. pub fn empty() -> Self { Self { magic: MAGIC_VALID, @@ -1119,7 +1149,7 @@ impl DebugData { } fn alloc_id(&self) -> TrackedRefId { - // PORT NOTE: Zig branched on `thread_safe` (atomic RMW vs plain ++). + // Zig branched on `thread_safe` (atomic RMW vs plain ++). // Debug-only path; always atomic here. TrackedRefId::new(self.next_id.fetch_add(1, Ordering::SeqCst)) } @@ -1134,7 +1164,7 @@ impl DebugData { self.frees.insert(id, TrackedDeref); } - // PORT NOTE: Zig `deinit` took `std.mem.asBytes(self)` as `data: []const u8` + // Zig `deinit` took `std.mem.asBytes(self)` as `data: []const u8` // but never read it; the Rust port dropped the parameter to avoid forming a // `&[u8]` over `T` (which may contain padding/uninit bytes — UB to read). fn deinit(&mut self, ret_addr: usize) { @@ -1143,8 +1173,9 @@ impl DebugData { let _guard = self.lock.lock(); self.map.clear(); self.map.shrink_to_fit(); + // Zig `clearAndFree` — clear and release the allocation. self.frees.clear(); - // TODO(port): ArrayHashMap shrink — Zig clearAndFree + self.frees.shrink_to_fit(); let _ = ret_addr; } } @@ -1156,13 +1187,14 @@ impl DebugDataOps for DebugData { } fn acquire(&mut self, return_address: usize) -> TrackedRefId { - // PORT NOTE: Zig's `acquire` took `count_pointer: *Count` because + // Zig's `acquire` took `count_pointer: *Count` because // `RefPtr.uncheckedAndUnsafeInit` reached into `raw_ptr.ref_count.raw_count`. // The dyn surface cannot name `Count`; the typed call site // (RefCount::ref_/ThreadSafeRefCount::ref_) wires the count_pointer - // separately. Here we acquire without updating count_pointer. - // TODO(port): plumb count_pointer through AnyRefCounted if leak-dump - // needs it for RefPtr-created refs. + // separately. Here we acquire without a count_pointer; that is fine + // because the only leak-dump path (`dump_active_refs`) receives the + // current count explicitly as a parameter, so nothing reads a stored + // count_pointer in the Rust shape. let _guard = self.lock.lock(); let id = self.alloc_id(); self.map.insert( @@ -1246,8 +1278,8 @@ fn generic_dump( /// Zig used `@hasField(T, "ref_count")` + `@hasDecl(Rc, "assertNoRefs")` /// reflection to make this a no-op for non-ref-counted types. In Rust the -/// "has ref_count" check IS the trait bound. -// TODO(port): callers that passed non-ref-counted T must simply not call this. +/// "has ref_count" check IS the trait bound: callers that passed a +/// non-ref-counted T must simply not call this. pub fn maybe_assert_no_refs(ptr: &T) { // SAFETY: `ptr` is a live reference to T unsafe { T::rc_assert_no_refs(std::ptr::from_ref::(ptr)) } @@ -1262,23 +1294,34 @@ fn return_address() -> usize { bun_core::return_address() } +// Zig used `@fieldParentPtr(field_name, count)`. The field name is not known +// here as a const, but `T::get_ref_count` is a pure raw-pointer field +// projection (`&raw mut (*this).field` — no read), so probing an +// uninitialized stack value yields the field offset soundly. + #[cfg(debug_assertions)] #[inline(always)] -fn offset_of_ref_count() -> usize { - // TODO(port): Zig used `@fieldParentPtr(field_name, count)`. Without the - // field name as a const, compute via the trait: feed a dangling aligned T - // and diff pointers. The derive macro could emit `offset_of!(T, ref_count)`. - 0 +fn offset_of_ref_count() -> usize { + let mut uninit = core::mem::MaybeUninit::::uninit(); + let base = uninit.as_mut_ptr(); + // SAFETY: `get_ref_count` only projects a field within `base`'s + // allocation; it never reads `*base`. + let field = unsafe { T::get_ref_count(base) }; + field as usize - base as usize } #[cfg(debug_assertions)] #[inline(always)] -fn offset_of_ref_count_ts() -> usize { - // TODO(port): see offset_of_ref_count. - 0 +fn offset_of_ref_count_ts() -> usize { + let mut uninit = core::mem::MaybeUninit::::uninit(); + let base = uninit.as_mut_ptr(); + // SAFETY: `get_ref_count` only projects a field within `base`'s + // allocation; it never reads `*base`. + let field = unsafe { T::get_ref_count(base) }; + field as usize - base as usize } -// PORT NOTE: `const unique_symbol = opaque {};` — type-identity marker for +// `const unique_symbol = opaque {};` — type-identity marker for // comptime assertion in `RefPtr`. Replaced by `AnyRefCounted` trait bound. bun_core::declare_scope!(ref_count, hidden); diff --git a/src/ptr/shared.rs b/src/ptr/shared.rs index 3d1acf3c9047..109b987f8d65 100644 --- a/src/ptr/shared.rs +++ b/src/ptr/shared.rs @@ -16,54 +16,18 @@ use std::rc::Rc; // Options // ─────────────────────────────────────────────────────────────────────────────── -/// Options for `WithOptions`. -/// -/// In the Rust port these collapse to the std `Rc`/`Arc` knobs: -/// -/// * `Allocator` — std `Rc`/`Arc` always use the global allocator (mimalloc via -/// `#[global_allocator]`). `Rc::new_in`/`Arc::new_in` are nightly-only -/// (`feature(allocator_api)`); see `// TODO(port)` on `SharedIn` below. -/// * `atomic` — picks `Rc` vs `Arc`. -/// * `allow_weak` — `Rc`/`Arc` always carry a weak count, so this is always -/// effectively `true`. The Zig flag existed only to save 4 bytes when weak -/// pointers were not needed. -/// * `deinit` — `Rc`/`Arc` always run `Drop` on the inner `T`. To suppress -/// `Drop`, wrap the payload in `ManuallyDrop` at the call site. +// Zig's `Options` struct (the parameter of `WithOptions`) has no Rust value: +// each knob collapses to a std `Rc`/`Arc` choice made at the use site. // -// TODO(port): this struct is kept only as documentation of the Zig surface; no -// Rust code should construct it. Remove once all `WithOptions` call sites are -// migrated to plain `Rc`/`Arc`. -pub struct Options { - // If non-null, the shared pointer will always use the provided allocator. This saves a small - // amount of memory, but it means the shared pointer will be a different type from shared - // pointers that use different allocators. - // (Rust: global mimalloc; allocator_api is unstable.) - // - /// Whether to use an atomic type to store the ref count. This makes the shared pointer - /// thread-safe, assuming the underlying data is also thread-safe. - pub atomic: bool, - - /// Whether to allow weak pointers to be created. This uses slightly more memory but is often - /// negligible due to padding. - /// - /// There is no point in enabling this if `deinit` is false, or if your data type doesn't have - /// a `deinit` method, since the sole purpose of weak pointers is to allow `deinit` to be called - /// before the memory is freed. - pub allow_weak: bool, - - /// Whether to call `deinit` on the data before freeing it, if such a method exists. - pub deinit: bool, -} - -impl Default for Options { - fn default() -> Self { - Self { - atomic: false, - allow_weak: false, - deinit: true, - } - } -} +// * `Allocator` — std `Rc`/`Arc` always use the global allocator (mimalloc via +// `#[global_allocator]`). `Rc::new_in`/`Arc::new_in` are nightly-only +// (`feature(allocator_api)`). +// * `atomic` — picks `Rc` vs `Arc`. +// * `allow_weak` — `Rc`/`Arc` always carry a weak count, so this is always +// effectively `true`. The Zig flag existed only to save 4 bytes when weak +// pointers were not needed. +// * `deinit` — `Rc`/`Arc` always run `Drop` on the inner `T`. To suppress +// `Drop`, wrap the payload in `ManuallyDrop` at the call site. // ─────────────────────────────────────────────────────────────────────────────── // Shared / AtomicShared @@ -79,7 +43,7 @@ impl Default for Options { /// | Zig | Rust | /// |-----------------------------|-----------------------------------------------| /// | `Shared(*T).alloc(v)` | `Rc::new(v)` (infallible; aborts on OOM) | -/// | `Shared(*T).allocIn(v, a)` | — (allocator_api unstable; `// TODO(port)`) | +/// | `Shared(*T).allocIn(v, a)` | — (allocator_api unstable; allocator deleted) | /// | `Shared(*T).new(v)` | `Rc::new(v)` | /// | `s.get()` | `&*s` / `Rc::as_ptr(&s)` | /// | `s.clone()` | `Rc::clone(&s)` | @@ -151,10 +115,6 @@ pub type Weak = std::rc::Weak; // // The `fromValuePtr` (`@fieldParentPtr("value", ptr)`) recovery is provided by // `Rc::from_raw` / `Arc::from_raw`, which subtract the header offset internally. -// -// TODO(port): if profiling shows the std weak-count word is measurable in a -// hot array, revisit with a `#[repr(C)]` hand-rolled inner — but per -// PORTING.md this is explicitly deprioritized. // `RawCount` was `u32` in Zig; std uses `usize`. The overflow assertion // (`old != maxInt(RawCount)`) is replaced by std's own abort-on-overflow check diff --git a/src/ptr/tagged_pointer.rs b/src/ptr/tagged_pointer.rs index df6281afa2a9..47eb76da459e 100644 --- a/src/ptr/tagged_pointer.rs +++ b/src/ptr/tagged_pointer.rs @@ -141,10 +141,11 @@ pub trait UnionMember { /// Generates `TypeList` for `($($T,)*)` and `UnionMember<($($T,)*)>` for each /// `$T`, assigning tags `1024 - i` to match Zig's `TagTypeEnumWithTypeMap`. -// TODO(port): proc-macro — Zig uses `@typeName` for both the tag enum field -// name and the `name` string. `stringify!($T)` is the closest analogue but -// won't match Zig's fully-qualified `@typeName` output; confirm no caller -// depends on the exact string. +// Zig uses `@typeName` for both the tag enum field name and the `name` +// string; `stringify!($T)` is the closest analogue but doesn't match Zig's +// fully-qualified `@typeName` output. That is fine: no caller consumes the +// strings (`type_name`/`type_name_from_tag` have no users outside debug +// output), so the exact spelling is not load-bearing. #[macro_export] macro_rules! impl_tagged_ptr_union { ($($T:ty),+ $(,)?) => { @@ -204,8 +205,8 @@ impl TaggedPtrUnion { } // `typeFromTag(comptime the_tag) type` has no Rust equivalent (cannot - // return a type from a const value). Callers must name the type directly. - // TODO(port): if any callsite needs this, it becomes a trait associated type. + // return a type from a const value). Callers must name the type directly; + // no callsite needs it. pub fn type_name_from_tag(the_tag: TagType) -> Option<&'static str> { Ts::type_name_from_tag(the_tag) @@ -240,7 +241,7 @@ impl TaggedPtrUnion { } /// unsafely cast a tagged pointer to a specific type, without checking that it's really that type - // PORT NOTE: Zig name is `as`, which is a Rust keyword. + // Zig name is `as`, which is a Rust keyword. #[inline] pub fn as_unchecked>(self) -> *mut Type { self.repr.get::() @@ -318,12 +319,12 @@ impl TaggedPtrUnion { self.repr.ptr_bits() == 0 } - // TODO(port): `call(comptime fn_name, args, comptime Ret)` dispatches by - // tag and invokes `@field(entry.ty, fn_name)` reflectively. Rust cannot - // look up a method by string at compile time. Port pattern: define a trait - // with the target method, bound every `Ti: TheTrait`, and have callers - // `match self.tag()` (or use a per-instantiation `dispatch!` macro). Each - // callsite of `.call(...)` needs to be rewritten by hand. + // Zig's `call(comptime fn_name, args, comptime Ret)` dispatches by tag and + // invokes `@field(entry.ty, fn_name)` reflectively. Rust cannot look up a + // method by string at compile time, so it is intentionally not provided. + // Equivalent pattern at a callsite: define a trait with the target method, + // bound every `Ti: TheTrait`, and `match self.tag()` (or use a + // per-instantiation `dispatch!` macro). } // ported from: src/ptr/tagged_pointer.zig diff --git a/src/ptr/weak_ptr.rs b/src/ptr/weak_ptr.rs index 3f72fb04b91e..b2336bb4ccc8 100644 --- a/src/ptr/weak_ptr.rs +++ b/src/ptr/weak_ptr.rs @@ -75,10 +75,9 @@ pub trait HasWeakPtrData { /// `WeakPtr`s are released. Even if the allocation is present, `WeakPtr::get` /// will return `None` after the inner contents are freed. pub struct WeakPtr { - // PORT NOTE: LIFETIMES.tsv classifies this field as SHARED → `Weak`, - // but this file *is* the definition of the intrusive weak pointer; per - // PORTING.md §Pointers ("keep as `*mut T` + manual ref/deref over an - // embedded `WeakPtrData`"), the field stays a raw pointer. + // Intentionally a raw pointer, not `std::sync::Weak`: this file *is* + // the definition of the intrusive weak pointer, with liveness tracked by + // the embedded `WeakPtrData` and manual ref/deref. raw_ptr: Option>, } diff --git a/src/resolve_builtins/HardcodedModule.rs b/src/resolve_builtins/HardcodedModule.rs index 71e6d151a371..0488a20fdbe8 100644 --- a/src/resolve_builtins/HardcodedModule.rs +++ b/src/resolve_builtins/HardcodedModule.rs @@ -365,12 +365,9 @@ macro_rules! entry { // Zig builds three `ComptimeStringMap`s by concatenating these const arrays // (`common ++ bun_extra ++ bun_test_extra`). `phf::phf_map!` only accepts -// inline literal entries, so it cannot consume const slices directly. -// -// TODO(perf): generate `NODE_ALIASES` / `BUN_ALIASES` / -// `BUN_TEST_ALIASES` as `phf::Map<&'static [u8], Alias>` via `phf_codegen` in -// `build.rs`, fed from these const slices. The `get()` lookups below are the -// only public surface, so swapping the backing store is mechanical. +// inline literal entries, so it cannot consume const slices directly; instead +// the const tables stay the source of truth (callers like `ModuleLoader` +// iterate them) and `lookup` goes through lazily-built hash maps. type AliasKv = (&'static [u8], Alias); @@ -776,9 +773,10 @@ const BUN_TEST_EXTRA_ALIAS_KVS: &[AliasKv] = &[ ), ]; -// TODO(port): replace with `phf::Map<&'static [u8], Alias>` generated by -// `phf_codegen` in build.rs (Zig: `bun.ComptimeStringMap(Alias, common_alias_kvs)`). -// PERF(port): linear scan placeholder — Zig used a comptime perfect-hash map. +// Zig: `bun.ComptimeStringMap(Alias, common_alias_kvs)` etc. — a comptime +// perfect-hash map. The Rust equivalent here is a lazily-built `HashMap` per +// alias table (see `lookup`); the const slice-of-tables form is kept public +// because `ModuleLoader` iterates the raw entries. const NODE_ALIASES: &[&[AliasKv]] = &[COMMON_ALIAS_KVS]; pub const BUN_ALIASES: &[&[AliasKv]] = &[COMMON_ALIAS_KVS, BUN_EXTRA_ALIAS_KVS]; const BUN_TEST_ALIASES: &[&[AliasKv]] = &[ @@ -787,17 +785,34 @@ const BUN_TEST_ALIASES: &[&[AliasKv]] = &[ BUN_TEST_EXTRA_ALIAS_KVS, ]; -#[inline] -fn lookup(tables: &[&[AliasKv]], name: &[u8]) -> Option { - // PERF(port): O(n) scan; could replace with a phf perfect-hash lookup. +fn build_alias_map( + tables: &[&[AliasKv]], +) -> std::collections::HashMap<&'static [u8], Alias> { + let mut map = + std::collections::HashMap::with_capacity(tables.iter().map(|t| t.len()).sum()); for table in tables { for (k, v) in *table { - if *k == name { - return Some(*v); - } + // First table wins, matching the previous in-order scan (and Zig's + // concatenated ComptimeStringMap, whose keys are unique anyway). + map.entry(*k).or_insert(*v); } } - None + map +} + +static NODE_ALIAS_MAP: std::sync::LazyLock> = + std::sync::LazyLock::new(|| build_alias_map(NODE_ALIASES)); +static BUN_ALIAS_MAP: std::sync::LazyLock> = + std::sync::LazyLock::new(|| build_alias_map(BUN_ALIASES)); +static BUN_TEST_ALIAS_MAP: std::sync::LazyLock> = + std::sync::LazyLock::new(|| build_alias_map(BUN_TEST_ALIASES)); + +#[inline] +fn lookup( + map: &std::sync::LazyLock>, + name: &[u8], +) -> Option { + map.get(name).copied() } #[derive(Copy, Clone, Default)] @@ -813,12 +828,12 @@ impl Alias { pub fn get(name: &[u8], target: Target, cfg: Cfg) -> Option { if target.is_bun() { if cfg.rewrite_jest_for_tests { - return lookup(BUN_TEST_ALIASES, name); + return lookup(&BUN_TEST_ALIAS_MAP, name); } else { - return lookup(BUN_ALIASES, name); + return lookup(&BUN_ALIAS_MAP, name); } } else if target.is_node() { - return lookup(NODE_ALIASES, name); + return lookup(&NODE_ALIAS_MAP, name); } None } diff --git a/src/resolver/data_url.rs b/src/resolver/data_url.rs index 583e53525818..4313278fa3f6 100644 --- a/src/resolver/data_url.rs +++ b/src/resolver/data_url.rs @@ -14,6 +14,58 @@ pub enum EncodeError { bun_core::named_error_set!(EncodeError); +impl EncodeError { + /// Zig: `@errorName(e)`. + pub fn name(self) -> &'static str { + self.into() + } +} + +/// Error set of [`DataURL::parse`] / [`DataURL::parse_without_check`]. +#[derive(thiserror::Error, strum::IntoStaticStr, Debug, Clone, Copy, PartialEq, Eq)] +pub enum ParseDataURLError { + #[error("InvalidDataURL")] + InvalidDataURL, +} + +bun_core::named_error_set!(ParseDataURLError); + +impl ParseDataURLError { + /// Zig: `@errorName(e)`. + pub fn name(self) -> &'static str { + self.into() + } +} + +/// Error set of [`DataURL::decode_data`]: `EncodeError || error{Base64DecodeError}`. +#[derive(thiserror::Error, strum::IntoStaticStr, Debug, Clone, Copy, PartialEq, Eq)] +pub enum DecodeDataError { + #[error("InvalidCharacter")] + InvalidCharacter, + #[error("OutOfMemory")] + OutOfMemory, + #[error("Base64DecodeError")] + Base64DecodeError, +} + +bun_core::named_error_set!(DecodeDataError); + +impl DecodeDataError { + /// Zig: `@errorName(e)`. + pub fn name(self) -> &'static str { + self.into() + } +} + +impl From for DecodeDataError { + fn from(e: EncodeError) -> Self { + match e { + EncodeError::InvalidCharacter => Self::InvalidCharacter, + EncodeError::OutOfMemory => Self::OutOfMemory, + } + } +} + impl PercentEncoding { /// returns true if str starts with a valid path character or a percent encoded octet pub(crate) fn is_pchar(str: &[u8]) -> bool { @@ -93,7 +145,7 @@ impl PercentEncoding { } } -// PORT NOTE: `mime_type`/`data` are slices into the caller-provided `url` string. +// `mime_type`/`data` are slices into the caller-provided `url` string. // Classified as BORROW_PARAM — struct gets a lifetime parameter. pub struct DataURL<'a> { pub url: bun_core::String, @@ -103,8 +155,7 @@ pub struct DataURL<'a> { } impl<'a> DataURL<'a> { - pub fn parse(url: &'a [u8]) -> Result>, bun_core::Error> { - // TODO(port): narrow error set + pub fn parse(url: &'a [u8]) -> Result>, ParseDataURLError> { if !url.starts_with(b"data:") { return Ok(None); } @@ -112,10 +163,9 @@ impl<'a> DataURL<'a> { Ok(Some(Self::parse_without_check(url)?)) } - pub fn parse_without_check(url: &'a [u8]) -> Result, bun_core::Error> { - // TODO(port): narrow error set + pub fn parse_without_check(url: &'a [u8]) -> Result, ParseDataURLError> { let comma = strings::index_of_char(url, b',') - .ok_or_else(|| bun_core::err!("InvalidDataURL"))? as usize; + .ok_or(ParseDataURLError::InvalidDataURL)? as usize; let mut parsed = DataURL { url: bun_core::String::empty(), @@ -137,8 +187,7 @@ impl<'a> DataURL<'a> { } /// Decodes the data from the data URL. Always returns an owned slice. - pub fn decode_data(&self) -> Result, bun_core::Error> { - // TODO(port): narrow error set + pub fn decode_data(&self) -> Result, DecodeDataError> { let percent_decoded_owned: Option> = PercentEncoding::decode_unstrict(self.data)?; // defer: `percent_decoded_owned` drops at scope exit let percent_decoded: &[u8] = percent_decoded_owned.as_deref().unwrap_or(self.data); @@ -149,7 +198,7 @@ impl<'a> DataURL<'a> { // errdefer: `buf` drops automatically on error path let result = bun_base64::decode(&mut buf, percent_decoded); if !result.is_successful() || result.count != len { - return Err(bun_core::err!("Base64DecodeError")); + return Err(DecodeDataError::Base64DecodeError); } return Ok(buf); } @@ -186,13 +235,17 @@ impl<'a> DataURL<'a> { return buf; } - // TODO(port): Zig source's `bufPrint` writes `text` raw with `{s}` here, not the - // base64-encoded form — ported faithfully; verify upstream intent. - let mut base64buf = Vec::with_capacity(total_base64_encode_len); - base64buf.extend_from_slice(b"data:"); - base64buf.extend_from_slice(mime_type); - base64buf.extend_from_slice(b";base64,"); - base64buf.extend_from_slice(text); + // The Zig source has a bug here: `bufPrint("data:{s};base64,{s}", ...)` + // wrote `text` raw instead of base64-encoding it (despite sizing the + // buffer for the encoded form), producing a corrupt data URL whenever + // the percent-escape path bails. Encode for real. + let mut base64buf = vec![0u8; total_base64_encode_len]; + let prefix_len = b"data:".len() + mime_type.len() + b";base64,".len(); + base64buf[..b"data:".len()].copy_from_slice(b"data:"); + base64buf[b"data:".len()..b"data:".len() + mime_type.len()].copy_from_slice(mime_type); + base64buf[b"data:".len() + mime_type.len()..prefix_len].copy_from_slice(b";base64,"); + let encoded_len = bun_base64::encode(&mut base64buf[prefix_len..], text); + base64buf.truncate(prefix_len + encoded_len); base64buf } @@ -294,4 +347,56 @@ impl DataUrlBuf for CountingBuf { } } +#[cfg(test)] +mod tests { + use super::*; + + /// Round-trips a data URL produced by `encode_string_as_shortest_data_url` + /// back through `parse` + `decode_data` and asserts the original bytes. + fn round_trip(mime_type: &[u8], text: &[u8]) -> DataURL<'static> { + let url = DataURL::encode_string_as_shortest_data_url(mime_type, text); + let url: &'static [u8] = Vec::leak(url); + let parsed = DataURL::parse(url) + .expect("emitted data URL must parse") + .expect("emitted data URL must start with data:"); + assert_eq!(parsed.mime_type, mime_type); + let decoded = parsed.decode_data().expect("emitted data URL must decode"); + assert_eq!(decoded, text); + parsed + } + + #[test] + fn shortest_data_url_percent_path() { + // Plain ASCII: percent-escaped form is shorter than base64. + let url = DataURL::encode_string_as_shortest_data_url(b"text/plain", b"hello"); + assert_eq!(url, b"data:text/plain,hello"); + round_trip(b"text/plain", b"hello"); + } + + #[test] + fn shortest_data_url_base64_fallback_invalid_utf8() { + // Non-UTF-8 input makes the percent-escape path bail; the fallback + // must emit a real base64 payload (the Zig source wrote `text` raw + // here, producing a corrupt URL). + let text: &[u8] = &[0xff, 0xfe, 0x00, 0x01, b'a', 0x80]; + let url = DataURL::encode_string_as_shortest_data_url(b"application/octet-stream", text); + assert!(url.starts_with(b"data:application/octet-stream;base64,")); + let parsed = round_trip(b"application/octet-stream", text); + assert!(parsed.is_base64); + } + + #[test] + fn shortest_data_url_base64_fallback_when_shorter() { + // Every byte needs escaping (3 bytes each) so base64 (4/3 per byte) + // wins and the fallback path is taken. + let text = vec![b'\n'; 96]; + let url = DataURL::encode_string_as_shortest_data_url(b"text/plain", &text); + assert!(url.starts_with(b"data:text/plain;base64,")); + // 96 bytes -> 128 base64 chars, no padding. + assert_eq!(url.len(), b"data:text/plain;base64,".len() + 128); + let parsed = round_trip(b"text/plain", &text); + assert!(parsed.is_base64); + } +} + // ported from: src/resolver/data_url.zig diff --git a/src/resolver/dir_info.rs b/src/resolver/dir_info.rs index 2db61822cbfa..cc27dd4079cc 100644 --- a/src/resolver/dir_info.rs +++ b/src/resolver/dir_info.rs @@ -106,7 +106,7 @@ pub struct DirInfo { // A pointer to the enclosing dirInfo with a valid "browser" field in // package.json. We need this to remap paths after they have been resolved. pub enclosing_browser_scope: Index, - // PORT NOTE: lifetime — `&'static` borrows below are ARENA-backed (the + // lifetime — `&'static` borrows below are ARENA-backed (the // resolver-owned PackageJSON/TSConfigJSON caches outlive every DirInfo). // Fields Zig typed `?*const T` (`package_json_for_browser_field`, // `enclosing_tsconfig_json` — dir_info.zig:12-13) are `Option<&'static T>`. @@ -122,32 +122,32 @@ pub struct DirInfo { /// it's the deepest one in the hierarchy with a "name" field /// or, if using `bun run`, the name field is optional /// https://github.com/oven-sh/bun/issues/229 - // PORT NOTE: Zig `?*PackageJSON` (mutable, dir_info.zig:19) but no write + // Zig `?*PackageJSON` (mutable, dir_info.zig:19) but no write // site exists in resolver.zig or any caller — kept `Option<&'static>` for // ergonomics. If a write is ever ported, retype to `Option>`. pub enclosing_package_json: Option<&'static PackageJSON>, - // PORT NOTE: Zig `?*PackageJSON` (mutable, dir_info.zig:21). `NonNull` (not + // Zig `?*PackageJSON` (mutable, dir_info.zig:21). `NonNull` (not // `&'static`) so `enqueue_dependency_to_resolve` can write // `package_manager_package_id` back through it (resolver.zig:2394) without // a const→mut provenance cast. Read via `.package_json_for_dependencies()`. pub package_json_for_dependencies: Option>, - // TODO(port): lifetime — slice into BSS-backed path storage; never individually freed + // lifetime — slice into BSS-backed path storage; never individually freed pub abs_path: &'static [u8], pub entries: Index, /// Is there a "package.json" file? - // PORT NOTE: Zig `?*PackageJSON` (mutable). `NonNull` (not `&'static`) so + // Zig `?*PackageJSON` (mutable). `NonNull` (not `&'static`) so // `reset()` can `drop_in_place` without a const→mut provenance cast. Read // via `.package_json()`. pub package_json: Option>, /// Is there a "tsconfig.json" file in this directory or a parent directory? - // PORT NOTE: Zig `?*TSConfigJSON` (mutable). `NonNull` (not `&'static`) so + // Zig `?*TSConfigJSON` (mutable). `NonNull` (not `&'static`) so // `reset()` can `drop_in_place` without a const→mut provenance cast. Read // via `.tsconfig_json()`. pub tsconfig_json: Option>, /// If non-empty, this is the real absolute path resolving any symlinks - // TODO(port): lifetime — slice into BSS-backed path storage; never individually freed + // lifetime — slice into BSS-backed path storage; never individually freed pub abs_real_path: &'static [u8], pub flags: Flags, @@ -163,7 +163,7 @@ impl Default for DirInfo { enclosing_package_json: None, package_json_for_dependencies: None, abs_path: b"", - // PORT NOTE: Zig left this `= undefined`; using a zero-value placeholder. + // Zig left this `= undefined`; using a zero-value placeholder. entries: Index::default(), package_json: None, tsconfig_json: None, @@ -299,7 +299,7 @@ impl DirInfo { } } -// PORT NOTE: Zig `BSSMap` is a per-monomorphization singleton (`var instance` inside +// Zig `BSSMap` is a per-monomorphization singleton (`var instance` inside // the comptime-returned struct). Rust `BSSMapInner` cannot host a // per-generic-instantiation static on stable, so the singleton pointer lives here at // the use site and `bun_alloc::BSSMapInner::init()` hands back the storage. @@ -392,7 +392,7 @@ impl DirInfo { // 3. Store whether a directory has been queried and whether that query was successful. // 4. Allocate onto the https://en.wikipedia.org/wiki/.bss#BSS_in_C instead of the heap, so we can avoid memory leaks // -// PORT NOTE: Zig `BSSMap(DirInfo, COUNT, store_keys=false, est_key_len=128, rm_slash=true)`; +// Zig `BSSMap(DirInfo, COUNT, store_keys=false, est_key_len=128, rm_slash=true)`; // Rust splits the comptime branch — `store_keys=false` → `BSSMapInner`. // `est_key_len` is unused on the inner shape. COUNT mirrors `fs::preallocate::counts::DIR_ENTRY`. pub type HashMap = allocators::BSSMapInner; diff --git a/src/resolver/fs.rs b/src/resolver/fs.rs index 1c749fa5dae5..6146afddf72c 100644 --- a/src/resolver/fs.rs +++ b/src/resolver/fs.rs @@ -16,7 +16,7 @@ use bun_ptr::Interned; use bun_sys::{self, Fd}; use bun_threading::Mutex; -// PORT NOTE: scope tag renamed `fs` → `Fs` so it doesn't collide with `fs:` fn +// scope tag renamed `fs` → `Fs` so it doesn't collide with `fs:` fn // params (the `declare_scope!` macro emits a `static` with the tag name, and // edition-2024 forbids fn params shadowing statics). bun_core::define_scoped_log!(debug, Fs, hidden); @@ -120,7 +120,7 @@ impl BOM { let n = Self::UTF8_BYTES.len(); let len = list.len(); list.copy_within(n.., 0); - // PORT NOTE: Zig returned a subslice without truncating; mirror by slicing. + // Zig returned a subslice without truncating; mirror by slicing. &list[..len - n] } BOM::Utf16Le => { @@ -154,14 +154,14 @@ pub(crate) mod preallocate { } } -// PORT NOTE: Zig `BSSStringList(_COUNT, _ITEM_LENGTH)` internally remaps to +// Zig `BSSStringList(_COUNT, _ITEM_LENGTH)` internally remaps to // `<_COUNT * 2, _ITEM_LENGTH + 1>`; the Rust port took the post-transform // const params, so apply the arithmetic at the type-alias / declare site. pub(crate) type DirnameStoreBacking = allocators::BSSStringList<{ preallocate::counts::DIR_ENTRY * 2 }, { 128 + 1 }>; pub(crate) type FilenameStoreBacking = allocators::BSSStringList<{ preallocate::counts::FILES * 2 }, { 64 + 1 }>; -// PORT NOTE: Zig `BSSList(_COUNT)` → Rust `BSSList<{_COUNT * 2}>`. +// Zig `BSSList(_COUNT)` → Rust `BSSList<{_COUNT * 2}>`. pub(crate) type EntryStoreBacking = allocators::BSSList; // Per-monomorphization singleton storage — Zig kept `var instance` inside the @@ -179,7 +179,7 @@ pub struct FilenameStore(()); static DIRNAME_STORE_ZST: DirnameStore = DirnameStore(()); static FILENAME_STORE_ZST: FilenameStore = FilenameStore(()); -// PORT NOTE: `BSSStringList::append`/`append_lower_case`/`print` now take a raw +// `BSSStringList::append`/`append_lower_case`/`print` now take a raw // `*mut Self` receiver (matching `BSSList::append` and Zig's `*Self`), so the // inner `self.mutex` is the sole serialization point and no `&mut` is ever // materialized before the lock is held. The previous outer `LazyLock` @@ -197,7 +197,7 @@ macro_rules! string_store_impl { } #[inline] fn backing() -> *mut $bty { - // PORT NOTE: returns the raw `*mut` singleton (Zig `*Self`). + // returns the raw `*mut` singleton (Zig `*Self`). // `BSSStringList`'s mutating methods take `*mut Self` and lock // internally, so callers may pass this directly without ever // forming a `&mut`. @@ -330,7 +330,7 @@ impl FileSystem { } } -// PORT NOTE: Zig `FileSystem.deinit()` only called .deinit() on dirname_store/filename_store, +// Zig `FileSystem.deinit()` only called .deinit() on dirname_store/filename_store, // which are &'static singletons here — nothing owned to free, so no `impl Drop`. // ══════════════════════════════════════════════════════════════════════════ @@ -389,7 +389,7 @@ impl Default for EntryCache { } /// Port of `FileSystem.Entry` in `fs.zig`. -// PORT NOTE: `cache` / `need_stat` are lazily populated by `Entry::kind` / +// `cache` / `need_stat` are lazily populated by `Entry::kind` / // `Entry::symlink` while callers hold a shared `&Entry` (Zig used a freely- // aliasing-mutable `*Entry`). `EntryCache` is `Copy`, so `Cell` gives us safe // `.get()/.set()` through `&self` — `RealFS.entries_mutex` serializes access @@ -483,7 +483,7 @@ impl Entry { /// `RealFS` singleton in practice) and the caller must hold /// `RealFS.entries_mutex` so the `&mut *fs` reborrow is exclusive for the /// duration of the call. - // PORT NOTE: `Entry` lives in the EntryStore BSSMap singleton; all access is + // `Entry` lives in the EntryStore BSSMap singleton; all access is // serialized through `RealFS.entries_mutex`. Zig used `*Entry` (freely // aliasing-mutable) and `*Fs.FileSystem.RealFS` (raw). `fs` is `*mut` so the // call site does not require a second exclusive `&mut RealFS` borrow while a @@ -522,7 +522,7 @@ impl Entry { self.need_stat.set(false); // This error can happen if the file was deleted between the time the directory // was scanned and the time it was read - // SAFETY: see `Entry::kind` PORT NOTE. + // SAFETY: see the note on `Entry::kind`. match unsafe { &mut *fs }.resolve_kind(self.dir, self.base(), self.cache().fd, store_fd) { Ok(c) => self.cache.set(c), @@ -533,7 +533,7 @@ impl Entry { } } -// PORT NOTE: `BSSList::append` requires `ValueType: Clone` (its overflow path +// `BSSList::append` requires `ValueType: Clone` (its overflow path // retries with a copy). `Mutex`/`StringOrTinyString` aren't `Clone`, but for a // freshly-constructed `Entry` (the only thing ever appended) a field-wise copy // with a fresh `Mutex` is semantically equivalent to Zig's by-value move. @@ -566,8 +566,9 @@ impl Default for Entry { } /// Port of `FileSystem.DirEntry.DifferentCase` in `fs.zig`. -// PORT NOTE: lifetime-generic, but resolver storage requires `'static` (all -// three slices borrow DirnameStore/EntryStore-interned data in practice). +// lifetime-generic, but resolver storage requires `'static`; in practice all +// three slices borrow process-lifetime interned data (`dir` → DirnameStore, +// `query` → FilenameStore copy made in `DirEntry::get`, `actual` → EntryStore). #[derive(Clone, Copy)] pub struct DifferentCase<'a> { pub dir: &'a [u8], @@ -576,7 +577,7 @@ pub struct DifferentCase<'a> { } /// Port of `FileSystem.DirEntry.Lookup` in `fs.zig`. -// PORT NOTE: `entry` is a RAW `*mut Entry` (matching Zig `*Entry`). A safe +// `entry` is a RAW `*mut Entry` (matching Zig `*Entry`). A safe // `&self → &mut Entry` accessor would let two `get()` calls produce coexisting // aliased `&mut Entry` (PORTING.md §Forbidden). Callers `unsafe { &mut *entry }` // at each write site under `entries_mutex`. @@ -603,7 +604,7 @@ impl<'a> EntryLookup<'a> { unsafe { &*self.entry } } - // PORT NOTE: former `entry_mut() -> &'a mut Entry` accessor removed + // former `entry_mut() -> &'a mut Entry` accessor removed // (zero callers). `Entry`'s only mutable state (`cache`) is `Cell`-backed, // so all mutation goes through `entry().set_cache*()` on a shared borrow; // no `&mut Entry` escape hatch is needed. Write sites that bypass the @@ -625,7 +626,7 @@ pub mod dir_entry { impl EntryStore { #[inline] pub(crate) fn instance() -> *mut EntryStoreBacking { - // PORT NOTE: returns the raw `*mut` singleton (Zig `*Self`). Do NOT + // returns the raw `*mut` singleton (Zig `*Self`). Do NOT // materialize a `&'static mut` here — concurrent callers would alias. super::entry_store_backing() } @@ -680,8 +681,8 @@ impl DirEntryIterator for &T { /// Port of `FileSystem.DirEntry` in `fs.zig`. pub struct DirEntry { - // TODO(port): rule deviation — Zig deinit calls allocator.free(d.dir) so guide says Box<[u8]>, - // but this is interned in DirnameStore (a &'static BSSList). Keeping &'static. + // Zig deinit calls allocator.free(d.dir), but here `dir` is interned in + // DirnameStore (a process-lifetime BSSList), so `&'static` is correct. pub dir: &'static [u8], pub fd: Fd, pub generation: Generation, @@ -702,7 +703,7 @@ impl DirEntry { } /// Port of `DirEntry.addEntry` in `fs.zig`. - // PORT NOTE: Zig signature was `(prev_map, *entry, allocator, comptime Iterator, iterator)`. + // Zig signature was `(prev_map, *entry, allocator, comptime Iterator, iterator)`. // The Zig `allocator` param is dropped (everything routes through the global stores). // Compatibility wrapper for callers outside the `readdir` hot loop — // resolves the `FilenameStore` singleton on demand. Hot-loop callers @@ -887,7 +888,7 @@ impl DirEntry { } if FeatureFlags::VERBOSE_FS { - // PORT NOTE: re-borrow `base()` after the `iterator.next` mutable borrow ends. + // re-borrow `base()` after the `iterator.next` mutable borrow ends. let stored_name = stored_ref.base(); if found_kind == Some(EntryKind::Dir) { bun_core::prettyln!(" + {}/", BStr::new(stored_name)); @@ -900,9 +901,18 @@ impl DirEntry { } /// Port of `DirEntry.get` in `fs.zig`. - // PORT NOTE: `query_` borrow detached from the returned Entry lifetime so + // `query_` borrow detached from the returned Entry lifetime so // callers can pass a slice into the same threadlocal buffer they then - // mutate; `DifferentCase` widens to 'static (DirnameStore-backed). + // mutate; on a case mismatch the query bytes are interned into the + // process-lifetime `FilenameStore` so `DifferentCase<'static>` holds a + // genuinely `'static` slice (Zig stored the caller's raw slice and relied + // on use-before-overwrite discipline). The store does not dedup, so + // repeated lookups of the same case-mismatched specifier (e.g. watch-mode + // rebuilds with a busted resolution cache) each intern a fresh copy that + // is never freed; accepted because the mismatch arm is a warning/error + // path and each copy is small. The intern goes through `handle_oom` + // (abort) — a new abort point the allocation-free Zig `get` did not have, + // consistent with the `bun.handleOom` idiom. pub fn get<'a>(&'a self, query_: &[u8]) -> Option> { if query_.is_empty() || query_.len() > MAX_PATH_BYTES { return None; @@ -919,10 +929,10 @@ impl DirEntry { entry: result_ptr, diff_case: Some(DifferentCase { dir: self.dir, - // TODO(port): lifetime — Zig stored caller's slice; widened to 'static. - // SAFETY: extended for borrowck reshape; consumed before caller's buffer - // is overwritten (see resolver call sites). - query: unsafe { &*core::ptr::from_ref::<[u8]>(query_) }, + // Zig stored the caller's (possibly threadlocal-buffer-backed) + // slice; intern a copy instead so the `'static` in + // `DifferentCase<'static>` is real, not discipline-based. + query: bun_core::handle_oom(FilenameStore::instance().append(query_)), // SAFETY: `basename` borrows EntryStore (process-lifetime). actual: unsafe { &*core::ptr::from_ref::<[u8]>(basename) }, }), @@ -938,7 +948,7 @@ impl DirEntry { } /// Port of `DirEntry.getComptimeQuery` in `fs.zig`. - // PORT NOTE: Zig used comptime string lowering + comptime hash; Rust port + // Zig used comptime string lowering + comptime hash; Rust port // takes a &'static [u8] that is already lowercase. pub fn get_comptime_query<'a>(&'a self, query_lower: &'static [u8]) -> Option> { // PERF(port): was comptime hash precompute — profile if hot @@ -990,8 +1000,8 @@ impl DirEntry { } } -// PORT NOTE: Zig `DirEntry.deinit(allocator)` freed `data` (now drops itself) and `dir` -// (interned in DirnameStore — see field TODO). Body would be empty, so no `impl Drop`. +// Zig `DirEntry.deinit(allocator)` freed `data` (now drops itself) and `dir` +// (interned in DirnameStore — see the comment on `DirEntry::dir`). Body would be empty, so no `impl Drop`. impl bun_dotenv::DirEntryProbe for DirEntry { #[inline] @@ -1103,7 +1113,7 @@ impl EntriesGuard { pub struct RealFS { pub entries_mutex: Mutex, pub entries: EntriesMap, - pub cwd: &'static [u8], // TODO(port): lifetime — interned + pub cwd: &'static [u8], // interned (process-lifetime) pub file_limit: usize, pub file_quota: usize, } @@ -1250,7 +1260,7 @@ impl RealFS { index: allocators::IndexType, generation: Generation, ) -> Option<*mut EntriesOption> { - // PORT NOTE: Zig fs.zig:613 does not lock here; in Rust we must, because every + // Zig fs.zig:613 does not lock here; in Rust we must, because every // `EntriesMap` method auto-refs the global `BSSMapInner` to `&mut self`, and a // concurrent `read_directory_with_iterator` (which *does* lock) would otherwise // alias that `&mut` — UB under Stacked Borrows. `EntriesGuard` holds @@ -1259,7 +1269,7 @@ impl RealFS { // (`readdir`, `read_directory_error`) remain unconstrained while held. let map = self.entries_locked(); - // PORT NOTE: `at_index` returns a raw `*mut EntriesOption` (matching Zig's + // `at_index` returns a raw `*mut EntriesOption` (matching Zig's // `*EntriesOption`). Form short-lived `&mut` only at each use site below; // never hand a `&'static mut` back to the caller. let existing_ptr = map.at_index(index)?; @@ -1267,7 +1277,7 @@ impl RealFS { if let EntriesOption::Entries(entries) = unsafe { &mut *existing_ptr } { if entries.generation < generation { let dir_path = entries.dir; - // PORT NOTE: capture raw ptrs to the in-place `DirEntry` fields, then + // capture raw ptrs to the in-place `DirEntry` fields, then // drop the short-lived `&mut` before re-borrowing `self` for // `readdir` / `read_directory_error` (Zig used `*DirEntry` directly). let entries_ptr: *mut DirEntry = &raw mut **entries; @@ -1365,7 +1375,7 @@ impl RealFS { /// Returns `true` if an entry was removed pub fn bust_entries_cache(&mut self, file_path: &[u8]) -> bool { - // PORT NOTE: Zig fs.zig:778 does not lock here; in Rust we must, because + // Zig fs.zig:778 does not lock here; in Rust we must, because // `EntriesMap::remove` auto-refs the global `BSSMapInner` to `&mut self`, and a // concurrent `read_directory_with_iterator` (which *does* lock) would otherwise // alias that `&mut` — UB. `&mut self` alone proves nothing cross-thread since @@ -1438,38 +1448,38 @@ impl RealFS { #[derive(Default, Clone, Copy)] pub struct ModKey { - pub inode: u64, // TODO(port): std.fs.File.INode equivalent + pub inode: u64, // Zig: std.fs.File.INode — u64 covers libc stat `ino_t` pub size: u64, pub mtime: i128, - pub mode: u32, // TODO(port): std.fs.File.Mode equivalent -} - -thread_local! { - static HASH_NAME_BUF: RefCell<[u8; 1024]> = const { RefCell::new([0u8; 1024]) }; + pub mode: u32, // Zig: std.fs.File.Mode — u32 covers libc stat `mode_t` } impl ModKey { - pub fn hash_name(&self, basename: &[u8]) -> Result<&'static [u8], bun_core::Error> { - // TODO(port): returns slice into threadlocal buffer; lifetime is unsound — should take a caller-supplied buffer + /// Writes `basename` + `-` + the hex hash into `out` and returns the + /// written prefix. Zig wrote into a threadlocal `hash_name_buf` and + /// returned a slice into it; a caller-supplied buffer keeps the borrow + /// sound (the threadlocal version was invalidated by the next call). + pub fn hash_name<'out>( + &self, + basename: &[u8], + out: &'out mut [u8], + ) -> Result<&'out [u8], bun_core::Error> { let hex_int = self.hash(); - HASH_NAME_BUF.with_borrow_mut(|buf| { - let len = buf.len(); - let mut cursor = &mut buf[..]; - // Zig `{s}` writes `[]const u8` verbatim — `BStr`'s `Display` would - // lossily emit U+FFFD for non-UTF-8 bytes (and 1→3 expand), so use - // raw `io::Write` for the basename. - cursor - .write_all(basename) - .map_err(|_| bun_core::err!("NoSpaceLeft"))?; - cursor - .write_all(b"-") - .map_err(|_| bun_core::err!("NoSpaceLeft"))?; - write!(&mut cursor, "{:x}", hex_int).map_err(|_| bun_core::err!("NoSpaceLeft"))?; - let written = len - cursor.len(); - // SAFETY: threadlocal buffer outlives caller's use (matches Zig pattern) - Ok(unsafe { bun_ptr::detach_lifetime(&buf[..written]) }) - }) + let len = out.len(); + let mut cursor = &mut out[..]; + // Zig `{s}` writes `[]const u8` verbatim — `BStr`'s `Display` would + // lossily emit U+FFFD for non-UTF-8 bytes (and 1→3 expand), so use + // raw `io::Write` for the basename. + cursor + .write_all(basename) + .map_err(|_| bun_core::err!("NoSpaceLeft"))?; + cursor + .write_all(b"-") + .map_err(|_| bun_core::err!("NoSpaceLeft"))?; + write!(&mut cursor, "{:x}", hex_int).map_err(|_| bun_core::err!("NoSpaceLeft"))?; + let written = len - cursor.len(); + Ok(&out[..written]) } pub fn hash(&self) -> u64 { @@ -1494,7 +1504,7 @@ impl ModKey { let stat = file.stat()?; const NS_PER_S: i128 = 1_000_000_000; - // PORT NOTE: `bun_sys::Stat` is `libc::stat`; Zig's `std.fs.File.stat()` returned a + // `bun_sys::Stat` is `libc::stat`; Zig's `std.fs.File.stat()` returned a // normalized struct with `mtime: i128` ns. Reconstruct from `st_mtime` (sec) + // `st_mtime_nsec` (ns). The `libc` crate flattens BSD/Darwin `st_mtimespec` into // `st_mtime`/`st_mtime_nsec`, so the access is uniform on all `unix`. @@ -1512,7 +1522,7 @@ impl ModKey { // Don't generate a modification key if the file is too new let now = bun_core::time::nano_timestamp(); let now_seconds = now / NS_PER_S; - // PORT NOTE: Zig had `seconds > seconds` (always false) — preserved + // Zig had `seconds > seconds` (always false) — preserved #[allow(clippy::eq_op)] if seconds > seconds || (seconds == now_seconds && mtime > now) { return Err(bun_core::err!("Unusable")); @@ -1680,7 +1690,7 @@ impl RealFS { original_err: err, canonical_error: err, })); - // PORT NOTE: threadlocal storage outlives caller (matches Zig); + // threadlocal storage outlives caller (matches Zig); // return raw `*mut` — caller forms a short-lived `&mut` at use site. slot.as_mut_ptr() })); @@ -1701,7 +1711,7 @@ impl RealFS { original_err: err, canonical_error: err, })); - // PORT NOTE: threadlocal storage outlives caller (matches Zig); + // threadlocal storage outlives caller (matches Zig); // return raw `*mut` — caller forms a short-lived `&mut` at use site. slot.as_mut_ptr() })) @@ -1728,7 +1738,7 @@ impl RealFS { /// Caller borrows the returned EntriesOption. When `FeatureFlags::ENABLE_ENTRY_CACHE` is `false`, /// it is not safe to store this pointer past the current function call. /// - /// PORT NOTE: returns a raw `*mut EntriesOption` (matching Zig's `*EntriesOption`), not + /// returns a raw `*mut EntriesOption` (matching Zig's `*EntriesOption`), not /// `&'static mut`. Handing out `&'static mut` from a safe `pub fn` would let two callers /// (sequential or across the resolver thread pool) each hold a live `&mut` aliasing the /// same BSSMap slot — instant UB under Rust's `&mut` noalias model. Callers form a @@ -1745,7 +1755,7 @@ impl RealFS { crate::Resolver::assert_valid_cache_key(dir); let mut cache_result: Option = None; - // PORT NOTE: Zig `defer self.entries_mutex.unlock()`. `EntriesGuard` holds the + // Zig `defer self.entries_mutex.unlock()`. `EntriesGuard` holds the // mutex by raw pointer (no borrow of `self`), so the `&mut self` calls below // (`open_dir`, `readdir`, `read_directory_error`) remain unconstrained while // the lock is held. @@ -1780,7 +1790,7 @@ impl RealFS { original_err: bun_core::err!("ENOENT"), canonical_error: bun_core::err!("ENOENT"), })); - // PORT NOTE: threadlocal storage outlives caller; return raw `*mut`. + // threadlocal storage outlives caller; return raw `*mut`. slot.as_mut_ptr() })); } @@ -1811,7 +1821,7 @@ impl RealFS { DirnameStore::instance().append(dir_maybe_trail_slash)? } } else { - // PORT NOTE: Zig stored the caller-provided slice directly (no lifetime system). + // Zig stored the caller-provided slice directly (no lifetime system). // Intern into DirnameStore so the cache entry never dangles — `append` is a // bump-pointer copy and dedups against the singleton, so cost is bounded. DirnameStore::instance().append(dir)? @@ -1853,7 +1863,7 @@ impl RealFS { entries.fd = handle_fd; } - // PORT NOTE: Zig stores `EntriesOption{ .entries: *DirEntry }` (raw pointer), so + // Zig stores `EntriesOption{ .entries: *DirEntry }` (raw pointer), so // `put` is a plain pointer overwrite with no drop glue. In Rust the slot owns a // `Box`; calling `put` again on an already-populated slot would drop the // old Box (use-after-free if `in_place` aliases it) or leak it. Split the two cases: @@ -1884,7 +1894,7 @@ impl RealFS { Ok(TEMP_ENTRIES_OPTION.with_borrow_mut(|slot| { slot.write(EntriesOption::Entries(Box::new(entries))); - // PORT NOTE: threadlocal storage outlives caller; return raw `*mut`. + // threadlocal storage outlives caller; return raw `*mut`. slot.as_mut_ptr() })) } @@ -2087,10 +2097,10 @@ pub fn read_file_with_handle_impl<'p, 'buf, const USE_SHARED_BUFFER: bool, const file: &bun_sys::File, shared_buffer: &'buf mut MutableString, ) -> Result, bun_core::Error> { - // PORT NOTE: allocator param dropped (global mimalloc) + // allocator param dropped (global mimalloc) FileSystem::set_max_fd(file.handle().native()); - // PORT NOTE: in the `USE_SHARED_BUFFER` branch, `file_contents` borrows + // in the `USE_SHARED_BUFFER` branch, `file_contents` borrows // `shared_buffer.list`; tracked as a raw (ptr, len) pair so borrowck doesn't tie the // slice to `&mut shared_buffer` across the read/truncate/grow loop. The final slice is // reconstituted with `from_raw_parts` (matches Zig's `[]const u8` return). The @@ -2185,7 +2195,7 @@ pub fn read_file_with_handle_impl<'p, 'buf, const USE_SHARED_BUFFER: bool, const // `&mut shared_buffer.list` reborrow inside the BOM branch. if let Some(bom) = BOM::detect(&shared_buffer.list[..file_contents_len]) { debug!("Convert {} BOM", bom.tag_name()); - // PORT NOTE: Zig passed `&shared_buffer.list` and the returned slice aliases it. + // Zig passed `&shared_buffer.list` and the returned slice aliases it. // We pre-set `list.len` to the un-BOM'd payload length so the helper sees the // correct logical size (the read loop above truncated to `file_contents_len`). shared_buffer.list.truncate(file_contents_len); @@ -2203,7 +2213,7 @@ pub fn read_file_with_handle_impl<'p, 'buf, const USE_SHARED_BUFFER: bool, const let read_count = file.read_all(buf)?; if read_count + 1 < buf.len() { // allocator.dupeZ — own the buffer; caller frees via PathContentsPair drop. - // PORT NOTE: Zig returned an allocator-owned `[:0]u8` and the caller freed it + // Zig returned an allocator-owned `[:0]u8` and the caller freed it // later; Rust returns `Cow::Owned` so the caller's drop frees it. The trailing // NUL sentinel is not part of `contents` (matches Zig `[:0]`). // Allocate exact (no zero-fill), like Zig's @@ -2279,7 +2289,7 @@ pub fn read_file_with_handle_impl<'p, 'buf, const USE_SHARED_BUFFER: bool, const }); } - // PORT NOTE: `file_contents_ptr` always equals `shared_buffer.list.as_ptr()` on every + // `file_contents_ptr` always equals `shared_buffer.list.as_ptr()` on every // shared-buffer path above (read loop and BOM rewrite both anchor at index 0), so we // re-derive the final slice safely from `shared_buffer` with the real `'buf` lifetime // instead of fabricating `'static` via `from_raw_parts`. @@ -2430,7 +2440,7 @@ impl RealFS { bun_sys::open_file_absolute_z(absolute_path_c, bun_sys::OpenFlags::READ_ONLY)? .into_raw() } else { - // PORT NOTE: Zig `bun.openFileForPath` (bun.zig:1900-1910) — O_PATH is + // Zig `bun.openFileForPath` (bun.zig:1900-1910) — O_PATH is // Linux-only; macOS/BSD use O_RDONLY. Both add O_NOCTTY|O_CLOEXEC. #[cfg(any(target_os = "linux", target_os = "android"))] let flags = bun_sys::O::PATH | bun_sys::O::CLOEXEC | bun_sys::O::NOCTTY; @@ -2440,7 +2450,7 @@ impl RealFS { }; FileSystem::set_max_fd(file.native()); - // PORT NOTE: Zig `defer { if (...) file.close() else cache.fd = file }` runs on + // Zig `defer { if (...) file.close() else cache.fd = file }` runs on // BOTH success and error paths — use scopeguard so close-or-store happens even if // stat()/get_fd_path() return early with `?`. let need_to_close_files = self.need_to_close_files(); @@ -2501,7 +2511,7 @@ pub struct PathContentsPair<'a, 'buf> { pub path: Path<'a>, /// `Owned` for the heap-allocated branch (caller frees on drop); `Borrowed` /// for the shared-buffer branch (points into the caller's `MutableString`, - /// tied to `'buf` — see PORT NOTE in `read_file_with_handle_and_allocator`). + /// tied to `'buf` — see the note in `read_file_with_handle_and_allocator`). pub contents: Cow<'buf, [u8]>, } @@ -2535,7 +2545,7 @@ impl core::fmt::Display for PrintHandle { write!(f, "{}", self.0) } } -// PORT NOTE: PrintHandle overlaps PrintHandle on every supported target — dropped. +// PrintHandle overlaps PrintHandle on every supported target — dropped. impl core::fmt::Display for PrintHandle<*mut c_void> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{:p}", self.0) diff --git a/src/resolver/lib.rs b/src/resolver/lib.rs index d4d9598a3d50..3234119aea55 100644 --- a/src/resolver/lib.rs +++ b/src/resolver/lib.rs @@ -74,9 +74,9 @@ pub mod fs { // Zig type params are pre-transformed to match `BSSStringList`'s // `COUNT = _COUNT * 2, ITEM_LENGTH = _ITEM_LENGTH + 1` const-generic encoding. - // PORT NOTE: `BSSStringList(2048, 128)` → `<{2048*2}, {128+1}>` + // `BSSStringList(2048, 128)` → `<{2048*2}, {128+1}>` bun_alloc::bss_string_list! { pub dirname_store_backing : 4096, 129 } - // PORT NOTE: `BSSStringList(4096, 64)` → `<{4096*2}, {64+1}>` + // `BSSStringList(4096, 64)` → `<{4096*2}, {64+1}>` bun_alloc::bss_string_list! { pub filename_store_backing : 8192, 65 } /// Port of `FileSystem.DirnameStore` (`BSSStringList<2048,128>`). @@ -195,7 +195,7 @@ pub mod fs { pub static INSTANCE_LOADED: AtomicBool = AtomicBool::new(false); /// Port of `FileSystem.max_fd` global in `fs.zig`. - // PORT NOTE: Windows uses `HANDLE` (no monotone ordering); tracked POSIX-only. + // Windows uses `HANDLE` (no monotone ordering); tracked POSIX-only. #[cfg(not(windows))] pub(crate) static MAX_FD: core::sync::atomic::AtomicI32 = core::sync::atomic::AtomicI32::new(0); @@ -209,7 +209,7 @@ pub mod fs { buf: &'b mut [u8], hash: u64, ) -> core::result::Result<&'b mut ZStr, bun_core::Error> { - // PORT NOTE: `std.time.nanoTimestamp()` — bun_core has no `time` module yet; + // `std.time.nanoTimestamp()` — bun_core has no `time` module yet; // use std directly (matches Zig which also calls std.time). let nanos: u128 = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) @@ -288,7 +288,7 @@ pub mod fs { } } let cwd: &'static [u8] = match top_level_dir { - // PORT NOTE: intern into the process-lifetime `DirnameStore` so + // intern into the process-lifetime `DirnameStore` so // callers may pass a borrowed path without leaking it themselves // (Zig had no lifetime; the singleton outlives every caller). Some(d) => DirnameStore::instance().append_slice(d)?, @@ -392,7 +392,7 @@ pub mod fs { ) -> core::result::Result<&'static [u8], bun_alloc::AllocError> { use bun_paths::resolve_path::{join_abs_string, platform}; let joined = join_abs_string::(self.top_level_dir, parts); - // PORT NOTE: Zig duped via `allocator.dupe`; route through DirnameStore so + // Zig duped via `allocator.dupe`; route through DirnameStore so // the resolver's `&'static [u8]` storage contract holds. DirnameStore::instance() .append_slice(joined) @@ -954,7 +954,7 @@ pub mod fs { } /// Port of `FileSystem.RealFS.EntriesOption` in `fs.zig`. - // PORT NOTE: Zig stores `*DirEntry` (raw, BSSMap-owned). Modeled as + // Zig stores `*DirEntry` (raw, BSSMap-owned). Modeled as // an unbounded `&mut DirEntry` so resolver match arms (`Entries(entries) => // entries.dir`) auto-deref. The backing storage is the BSSMap singleton; // `'static` is the ARENA lifetime. @@ -1046,7 +1046,7 @@ pub mod fs { result: &mut bun_alloc::Result, value: EntriesOption, ) -> core::result::Result<*mut EntriesOption, bun_core::Error> { - // PORT NOTE: `BSSMapInner::put` mutates `result.index` to record placement; callers + // `BSSMapInner::put` mutates `result.index` to record placement; callers // (e.g. `dir_info_cached_maybe_log`) re-read `result.index` post-`put`, so the // mutation must be visible — pass through directly (Zig: `*Result`). self.inner() @@ -1156,7 +1156,7 @@ pub mod fs { } #[cfg(not(windows))] { - // PORT NOTE: Zig used `std.fs.openDirAbsolute` on POSIX, which + // Zig used `std.fs.openDirAbsolute` on POSIX, which // reduces to `open(path, O_DIRECTORY)`; route through // `bun_sys::open_a` for the NUL-termination handling. bun_sys::open_a(unsafe_dir_string, bun_sys::O::DIRECTORY, 0).map_err(Into::into) @@ -1285,7 +1285,7 @@ pub mod fs { let cr = cache_result.as_ref().unwrap(); if cr.has_checked_if_exists() { if let Some(cached_result) = self.entries.at_index(cr.index) { - // PORT NOTE: erase to raw immediately so the early-return reborrow + // erase to raw immediately so the early-return reborrow // doesn't conflict with the `&mut self.entries` borrow above. let cached_ptr = std::ptr::from_mut::(cached_result); // SAFETY: BSSMap-owned slot; uniquely held under `entries_mutex`. @@ -1318,7 +1318,7 @@ pub mod fs { }, }; - // PORT NOTE: Zig `defer { if (...) handle.close() }` — runs on every exit. Use + // Zig `defer { if (...) handle.close() }` — runs on every exit. Use // scopeguard so close happens even if `readdir`/`put` early-return with `?`. let should_close_handle = !had_handle && (!store_fd || self.need_to_close_files()); let _close_guard = scopeguard::guard(handle, move |h| { @@ -1337,7 +1337,7 @@ pub mod fs { DirnameStore::instance().append_slice(dir_maybe_trail_slash)? } } else { - // PORT NOTE: Zig stored the caller-provided slice directly (no lifetime + // Zig stored the caller-provided slice directly (no lifetime // system). Intern into DirnameStore so the cache entry never dangles — // `append_slice` is a bump-pointer copy, cost is bounded. DirnameStore::instance().append_slice(dir)? @@ -1354,7 +1354,7 @@ pub mod fs { Err(err) => { if let Some(existing) = in_place { // SAFETY: see above. - // PORT NOTE: Zig `clear_and_free`; bun_collections::StringHashMap exposes `clear`. + // Zig `clear_and_free`; bun_collections::StringHashMap exposes `clear`. unsafe { (*existing).data.clear() }; } return self.read_directory_error(dir, err); @@ -1362,7 +1362,7 @@ pub mod fs { }; if bun_core::FeatureFlags::ENABLE_ENTRY_CACHE { - // PORT NOTE: Zig `entries_ptr = in_place orelse allocator.create(DirEntry)`. + // Zig `entries_ptr = in_place orelse allocator.create(DirEntry)`. // `EntriesOption::Entries` here holds an unbounded `&mut DirEntry` (raw, BSSMap-stored // pointer), so a fresh slot is a leaked `Box` whose lifetime is the // `entries_option_map()` singleton (process-static). @@ -1372,7 +1372,7 @@ pub mod fs { }; if let Some(original) = in_place { // SAFETY: BSSMap-owned; entries_mutex held. - // PORT NOTE: Zig `clear_and_free`; bun_collections::StringHashMap exposes `clear`. + // Zig `clear_and_free`; bun_collections::StringHashMap exposes `clear`. unsafe { (*original).data.clear() }; } if store_fd && !entries.fd.is_valid() { @@ -1551,7 +1551,7 @@ pub mod fs { )? .into_raw() } else { - // PORT NOTE: Zig `bun.openFileForPath` (bun.zig:1900-1910) — O_PATH is + // Zig `bun.openFileForPath` (bun.zig:1900-1910) — O_PATH is // Linux-only; macOS/BSD use O_RDONLY. Both add O_NOCTTY|O_CLOEXEC. #[cfg(any(target_os = "linux", target_os = "android"))] let flags = bun_sys::O::PATH | bun_sys::O::CLOEXEC | bun_sys::O::NOCTTY; @@ -1561,7 +1561,7 @@ pub mod fs { }; FileSystem::set_max_fd(file.native()); - // PORT NOTE: Zig `defer { if (...) file.close() else cache.fd = file }` runs on + // Zig `defer { if (...) file.close() else cache.fd = file }` runs on // BOTH success and error paths — use scopeguard so close-or-store happens even if // fstat()/get_fd_path() return early with `?`. let need_to_close_files = self.need_to_close_files(); @@ -1643,7 +1643,7 @@ pub mod fs { index: bun_alloc::IndexType, generation: Generation, ) -> Option<&mut EntriesOption> { - // PORT NOTE: erase to raw immediately so re-borrowing `&mut self` for + // erase to raw immediately so re-borrowing `&mut self` for // `open_dir`/`readdir`/`read_directory_error` doesn't conflict. // `entries_mutex` held by caller; sole `&mut` to this slot. let result_ptr = std::ptr::from_mut::(self.entries.at_index(index)?); @@ -1667,7 +1667,7 @@ pub mod fs { return self.read_directory_error(dir, err.into()).ok(); } }; - // PORT NOTE: Zig `defer handle.close()` — runs on every exit. + // Zig `defer handle.close()` — runs on every exit. let _close_guard = scopeguard::guard(handle, |h| { let _ = bun_sys::close(h); }); @@ -1815,7 +1815,7 @@ pub mod fs { let stat = file.stat()?; const NS_PER_S: i128 = 1_000_000_000; - // PORT NOTE: `bun_sys::Stat` is `libc::stat`; Zig's + // `bun_sys::Stat` is `libc::stat`; Zig's // `std.fs.File.stat()` returned a normalized struct with // `mtime: i128` ns. Reconstruct from `st_mtime` (sec) + // `st_mtime_nsec` (ns). The `libc` crate flattens BSD/Darwin @@ -1836,7 +1836,7 @@ pub mod fs { // Don't generate a modification key if the file is too new let now = bun_core::time::nano_timestamp(); let now_seconds = now / NS_PER_S; - // PORT NOTE: Zig had `seconds > seconds` (always false) — preserved + // Zig had `seconds > seconds` (always false) — preserved #[allow(clippy::eq_op)] if seconds > seconds || (seconds == now_seconds && mtime > now) { return Err(bun_core::err!("Unusable")); @@ -1906,7 +1906,7 @@ pub mod dir_entry_accessor { } } - // PORT NOTE: Zig `FS.DirEntry.EntryMap.Iterator` (key_ptr/value_ptr shape). + // Zig `FS.DirEntry.EntryMap.Iterator` (key_ptr/value_ptr shape). // `dir_entry::EntryMap` = `StringHashMap<*mut Entry>` which derefs to // `std::collections::HashMap, *mut Entry>`; iterate that directly. type EntryMapIter = bun_collections::hashbrown::hash_map::Iter< @@ -2104,7 +2104,10 @@ pub mod dir_entry_accessor { fn getcwd(path_buf: &mut PathBuffer) -> Maybe<&[u8]> { let cwd = FS::instance().fs.cwd; path_buf[..cwd.len()].copy_from_slice(cwd); - // TODO(port): Zig version has no return; assuming it should return the copied slice + // The Zig sibling (GlobWalker.zig `DirEntryAccessor.getcwd`) only + // memcpys and falls off the end despite declaring `Maybe([]const u8)`; + // returning the copied slice is what every Accessor caller expects + // (it matches the syscall-backed Accessor's contract). Ok(&path_buf[..cwd.len()]) } } @@ -2388,8 +2391,9 @@ pub mod cache { if self.fd.is_valid() { let fd = self.fd; self.fd = Fd::INVALID; - // TODO(port): @returnAddress() has no stable Rust equivalent; pass None. - return fd.close_allowing_bad_file_descriptor(None); + // Zig passed `@returnAddress()`; `bun_core::return_address()` is the + // equivalent (called directly from this frame so the PC anchors our caller). + return fd.close_allowing_bad_file_descriptor(Some(bun_core::return_address())); } None } @@ -2508,7 +2512,7 @@ pub mod cache { /// Port of `Fs.readFileWithAllocator` (cache.zig:146). /// - /// PORT NOTE: `comptime use_shared_buffer` is taken at runtime — the live + /// `comptime use_shared_buffer` is taken at runtime — the live /// callers (`ParseTask::get_code_for_parse_task_without_plugins`, /// `Transpiler::parse`) pass a value computed from runtime state, and the /// resolver's earlier forward-decl already pinned this shape. @@ -2536,7 +2540,7 @@ pub mod cache { let will_close = rfs.need_to_close_files() && _file_handle.is_none(); - // PORT NOTE: reshaped — Zig declared `file_handle = undefined` then assigned on each + // reshaped — Zig declared `file_handle = undefined` then assigned on each // branch; restructured into a single let-expression to avoid `mem::zeroed()` on a // type that may have niche (NonZero) fields. let file_handle: bun_sys::File = if let Some(f) = _file_handle { @@ -2586,7 +2590,7 @@ pub mod cache { fd ); - // PORT NOTE: reshaped for borrowck — capture `stream` scalar before borrowing + // reshaped for borrowck — capture `stream` scalar before borrowing // the shared buffer. let stream = self.stream; diff --git a/src/resolver/node_fallbacks.rs b/src/resolver/node_fallbacks.rs index cfc4f98a309b..9a5a3340fcd1 100644 --- a/src/resolver/node_fallbacks.rs +++ b/src/resolver/node_fallbacks.rs @@ -10,10 +10,9 @@ const _: () = assert!(IMPORT_PATH.len().is_multiple_of(8)); pub(crate) struct FallbackModule { pub path: fs::Path<'static>, - // PORT NOTE: Zig stored `*const PackageJSON` to a comptime literal (rvalue static - // promotion). PackageJSON has heap-backed fields (`Box<[u8]>`, hash maps) that cannot - // be const-constructed, so the LazyLock below owns the PackageJSONs and we hand out - // `&'static` borrows into it. + // PackageJSON has heap-backed fields (`Box<[u8]>`, hash maps) that cannot + // be const-constructed, so the process-lifetime singleton below owns the + // PackageJSONs and we hand out `&'static` borrows into it. pub package_json: &'static PackageJSON, pub code: fn() -> &'static str, } @@ -22,10 +21,9 @@ pub(crate) struct FallbackModule { // Using `include_str!` forces you to wait for the native build to finish in // debug builds, even when you only changed JS builtins. // -// PORT NOTE: Zig's `createSourceCodeGetter(comptime code_path: string)` returned a -// `*const fn () string` by defining a nested struct with a `get` fn closing over the -// comptime path. Rust fn pointers cannot close over const-generic `&str` on stable, so -// this is expressed as a macro that expands to a local `fn get()` and yields its pointer. +// Rust fn pointers cannot close over const-generic `&str` on stable, so this +// is expressed as a macro that expands to a local `fn get()` and yields its +// pointer. // // Release builds (`bun_codegen_embed`) embed the zstd-compressed `.js.zst` // written by the codegen step (src/node-fallbacks/build-fallbacks.ts) and @@ -72,10 +70,8 @@ macro_rules! create_source_code_getter { }}; } -// PORT NOTE: Zig's `pub fn init(comptime name: string) FallbackModule` did comptime string -// concatenation (`++`) and took the address of a comptime `PackageJSON` literal. PackageJSON -// is not const-constructible in Rust (Box<[u8]>/HashMap fields), so per PORTING.md -// §Concurrency this is a `LazyLock` runtime-init singleton. `@setEvalBranchQuota` is dropped. +// PackageJSON is not const-constructible (Box<[u8]>/HashMap fields), so the +// table is built at runtime, once, on first access. // // PERF(port): Zig used a comptime perfect-hash map; this builds at first access. macro_rules! fallback_module_init { @@ -93,8 +89,8 @@ macro_rules! fallback_module_init { name: Box::from($name.as_bytes()), version: Box::from(_VERSION), module_type: ModuleType::Esm, - // PORT NOTE: Zig used `undefined` for main_fields/browser_map (never read on - // this code path); Default::default() is the closest safe equivalent. + // main_fields/browser_map are never read on this code path; + // Default::default() fills them safely. source: bun_ast::Source::init_path_string(_PKGJSON_PATH, b""), side_effects: SideEffects::False, ..Default::default() @@ -112,11 +108,11 @@ type FallbackEntry = ( fn() -> &'static str, ); -// PORT NOTE: `PackageJSON` is `!Sync` (contains `StringArrayHashMap` with a -// `Cell`), so it cannot live in `LazyLock`/`OnceLock`. Zig built this at -// comptime (no thread-safety concern); the Rust port uses `RacyCell` + `Once`, -// which matches the "process-lifetime singleton, init once, read-only thereafter" -// shape. All reads go through `modules()`/`map()` which assert init ordering. +// `PackageJSON` is `!Sync` (contains `StringArrayHashMap` with a `Cell`), +// so it cannot live in `LazyLock`/`OnceLock`. `RacyCell` + `Once` matches the +// "process-lifetime singleton, init once, read-only thereafter" shape. All +// reads go through `map()`, which initializes via `INIT.call_once(init_modules)` +// before touching the cells. static MODULES: bun_core::RacyCell>> = bun_core::RacyCell::new(None); static MAP: bun_core::RacyCell>> = bun_core::RacyCell::new(None); diff --git a/src/resolver/package_json.rs b/src/resolver/package_json.rs index 9065c0dd3680..b8550fea9530 100644 --- a/src/resolver/package_json.rs +++ b/src/resolver/package_json.rs @@ -40,7 +40,6 @@ pub mod install_stubs { pub use ::bun_install_types::resolver_hooks::DependencyVersionTag as Tag; } } -// TODO(port): bun_bundler::options::{Framework, RouteConfig} — local opaque // FORWARD_DECL: legacy `options::Framework` and friends. The Zig // `package_json.zig:loadFramework*` block references `options.Framework`, which // no longer exists in `options.zig` (removed upstream); the loaders have no @@ -135,7 +134,9 @@ pub mod options { } } use bun_options_types::schema::api; -// TODO(port): bun_collections::StringMap (array-backed string→string map) +// Deliberately a bare alias rather than `bun_collections::StringMap` (which +// wraps the same `StringArrayHashMap>` with a `dupe_keys` flag the +// resolver never needs); callers here use the map API directly. pub type StringMap = StringArrayHashMap>; pub use bun_collections::StringHashMapUnownedKey; use bun_glob as glob; @@ -149,14 +150,17 @@ pub type BrowserMap = StringMap; pub type MacroImportReplacementMap = StringArrayHashMap>; pub type MacroMap = StringArrayHashMap; -type ScriptsMap = StringArrayHashMap<&'static [u8]>; // TODO(port): lifetime — values borrow source buffer +// Values borrow the package.json source buffer; `'static` is a lifetime-erased +// borrow kept alive by `PackageJSON::source_contents` (the owning field). +type ScriptsMap = StringArrayHashMap<&'static [u8]>; pub type MainFieldMap = StringMap; #[derive(Default)] pub struct DependencyMap { pub map: DependencyHashMap, - // TODO(port): lifetime — borrows the package.json source contents + // Borrows the package.json source contents; lifetime-erased to 'static, + // kept alive by `PackageJSON::source_contents`. pub source_buf: &'static [u8], } @@ -174,29 +178,29 @@ impl Clone for DependencyMap { } } -// PORT NOTE: Zig had `DependencyMap.HashMap` as a nested decl; Rust inherent impls cannot carry associated type aliases (stable), so use a free alias. +// Zig had `DependencyMap.HashMap` as a nested decl; Rust inherent impls cannot carry associated type aliases (stable), so use a free alias. pub type DependencyHashMap = ArrayHashMap; -// TODO(port): ArrayHashMap context param — Zig used String.ArrayHashContext with store_hash=false pub struct PackageJSON { pub name: Box<[u8]>, pub source: bun_ast::Source, - /// PORT NOTE: owns the file bytes that `source.contents` (and the + /// Owns the file bytes that `source.contents` (and the /// `&'static [u8]` map values below) borrow. Replaces the prior /// `mem::forget` leak — forbidden per docs/PORTING.md §Forbidden patterns. /// Zig (`package_json.zig:615`) used `bun.default_allocator` and never /// freed on success because the DirInfo cache is process-lifetime; here the /// `PackageJSON` itself is the owner so the bytes free if it ever drops. - // TODO(port): lifetime — once `bun_ast::Source::contents` becomes - // `Cow<'static, [u8]>`, fold this into `source` and drop the re-borrow. + /// (`bun_ast::Source::contents` is `&'static [u8]`, so this separate owner + /// field is what keeps that borrow — and the map values above — alive.) pub source_contents: Box<[u8]>, pub main_fields: MainFieldMap, pub module_type: ModuleType, pub version: Box<[u8]>, pub scripts: Option>, - pub config: Option>>, // TODO(port): value lifetime + // Values borrow the source buffer (lifetime-erased; owned by `source_contents`). + pub config: Option>>, pub arch: Architecture, pub os: OperatingSystem, @@ -237,7 +241,7 @@ pub struct PackageJSON { pub imports: Option, } -// PORT NOTE: hand-rolled `Default` because `#[derive(Default)]` would zero +// hand-rolled `Default` because `#[derive(Default)]` would zero // `package_manager_package_id` (a valid lockfile id — typically the root // package). Spec `package_json.zig:68` declares the field default as // `Install.invalid_package_id` (= `u32::MAX`); `node_fallbacks.rs` relies on @@ -326,7 +330,6 @@ impl ::bun_install_types::resolver_hooks::PackageJsonView for PackageJSON { impl PackageJSON { pub fn name_for_import(&self) -> Result, bun_core::Error> { - // TODO(port): narrow error set if strings::index_of(self.source.path.text, NODE_MODULES_PATH.as_bytes()).is_some() { Ok(Box::from(&*self.name)) } else { @@ -370,7 +373,6 @@ pub enum SideEffects { Mixed(MixedPatterns), } -// TODO(port): std.HashMapUnmanaged with StringHashMapUnowned.Key/Adapter and 80% load factor pub type SideEffectsMap = bun_collections::HashMap; pub type GlobList = Vec>; @@ -426,7 +428,7 @@ impl SideEffects { // ── Local extension shims so `parse` can call shapes that live in higher-tier // crates (full FileSystem). Bodies forward to bun_paths. ──────────────── // -// PORT NOTE: the former `JsonCachePackageJsonExt` shim trait is removed — +// the former `JsonCachePackageJsonExt` shim trait is removed — // `JsonCacheVTable` now has a real `parse_package_json` slot and `JsonCache` // exposes the inherent forwarder (tsconfig_json.rs). // `bun_bundler::cache::JSON_CACHE_VTABLE` wires it to `bun_parsers::json`. @@ -446,7 +448,7 @@ pub trait FileSystemPackageJsonExt { } impl FileSystemPackageJsonExt for crate::fs::FileSystem { fn abs_owned(&self, parts: &[&[u8]]) -> Box<[u8]> { - // PORT NOTE: Zig `FileSystem.abs` joins against `top_level_dir` into a + // Zig `FileSystem.abs` joins against `top_level_dir` into a // threadlocal buffer; caller immediately dupes. Return owned to avoid // laundering the threadlocal borrow into `'static`. let out = resolve_path::resolve_path::join_abs_string::< @@ -458,7 +460,7 @@ impl FileSystemPackageJsonExt for crate::fs::FileSystem { resolve_path::resolve_path::join::(parts) } fn normalize(&self, str: &[u8]) -> Box<[u8]> { - // PORT NOTE: Zig `FileSystem.normalize` (fs.zig) is + // Zig `FileSystem.normalize` (fs.zig) is // `path_handler.normalizeString(str, true, .auto)` — collapses `.`/`..`/dup-separators // only; it does NOT join against cwd. Writes into a threadlocal buffer; // caller immediately dupes. Return owned to avoid laundering the @@ -471,8 +473,8 @@ impl FileSystemPackageJsonExt for crate::fs::FileSystem { } } -// TODO(port): bun_bundler::options + bun_ast::Expr full API + bun_install + bun_schema -// — framework/define loaders stay gated until bun_bundler::options lands. +// Legacy framework/define loaders: no callers today (the Zig upstream removed +// `options.Framework`); bodies kept compiling against the local `options` shim. impl PackageJSON { fn load_define_defaults( @@ -480,7 +482,6 @@ impl PackageJSON { json: &js_ast::E::Object, bump: &Bump, ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set let mut valid_count: usize = 0; for prop in json.properties.slice() { if !matches!( @@ -550,11 +551,9 @@ impl PackageJSON { valid_count += 1; } - let buffer: Vec> = vec![Box::default(); valid_count * 2]; - // TODO(port): Zig used a single allocation split into keys/values; Rust uses two Vecs + // Zig used a single allocation split into keys/values; two Vecs here. let mut keys: Vec> = Vec::with_capacity(valid_count); let mut values: Vec> = Vec::with_capacity(valid_count); - let _ = buffer; // unused after reshaping for prop in json.properties.slice() { if !matches!( prop.value @@ -730,7 +729,7 @@ impl PackageJSON { || framework.fallback.is_enabled() } - // PORT NOTE: Zig `comptime load_framework: LoadFramework` lowered to a runtime + // Zig `comptime load_framework: LoadFramework` lowered to a runtime // arg — `LoadFramework` is a plain enum (no `ConstParamTy`), and the only // use is the trailing `match`. PERF(port): was comptime — irrelevant (dead). pub fn load_framework_with_preference( @@ -867,7 +866,7 @@ impl PackageJSON { } extensions.push(e_str.data.slice()); } - // TODO(port): `extensions` is computed but never assigned anywhere (matches Zig) + // `extensions` is computed but never assigned anywhere (matches Zig). let _ = extensions; } } @@ -1090,7 +1089,7 @@ impl PackageJSON { return None; } }; - // PORT NOTE: reshaped for borrowck — `mem::take` the contents (leaving + // Reshaped for borrowck — `mem::take` the contents (leaving // `Contents::Empty` behind) so `entry` stays whole for the close-guard. // Immediately convert to owned `Box<[u8]>`: `use_shared_buffer = false` // above guarantees `Contents::Owned`/`Empty`, so the match is exhaustive @@ -1111,13 +1110,12 @@ impl PackageJSON { )); } - // PORT NOTE: `bun_ast::Source.path` is the lightweight `bun_paths::fs::Path<'static>` (no + // `bun_ast::Source.path` is the lightweight `bun_paths::fs::Path<'static>` (no // `pretty`/`is_node_module`); `key_path` is only used for `text`, so init the // source directly from the interned path. // - // TODO(port): lifetime — `bun_ast::Source::contents` is `&'static [u8]`; once - // it becomes `Cow<'static, [u8]>` move `entry_contents` straight into - // `json_source` and delete this re-borrow. + // `bun_ast::Source::contents` is `&'static [u8]`, so `json_source` + // re-borrows `entry_contents` (lifetime-erased) instead of owning it. // // SAFETY: `entry_contents: Box<[u8]>` is the unique owner of these bytes. // On the success path it is *moved* (not leaked) into @@ -1156,7 +1154,7 @@ impl PackageJSON { let mut package_json = PackageJSON { name: Box::default(), version: Box::default(), - // PORT NOTE: reshaped for borrowck — `json_source` stays a local until the + // Reshaped for borrowck — `json_source` stays a local until the // end so we can borrow it while mutating other `package_json` fields. source: bun_ast::Source::default(), // Filled at the bottom by moving `entry_contents` in (see SAFETY note above). @@ -1174,7 +1172,7 @@ impl PackageJSON { exports: None, imports: None, }; - // PORT NOTE: shadow as `&Source` so the body matches the Zig shape; the + // shadow as `&Source` so the body matches the Zig shape; the // owned value is reconstructed at the bottom (Source isn't `Clone`). let json_source = &json_source; @@ -1291,7 +1289,7 @@ impl PackageJSON { // import of "foo", but that's actually not a bug. Or arguably it's a // bug in Browserify but we have to replicate this bug because packages // do this in the wild. - // PORT NOTE: inherent `FileSystem::normalize` (fs.rs) + // inherent `FileSystem::normalize` (fs.rs) // returns a threadlocal-backed `&[u8]` and shadows the // owned-returning trait method; UFCS to get the `Box`. let key: Box<[u8]> = @@ -1315,7 +1313,7 @@ impl PackageJSON { } _ => { // Only print this warning if its not inside node_modules, since node_modules/ is not actionable. - // PORT NOTE: `bun_paths::fs::Path<'static>` has no `is_node_module`; inline the check. + // `bun_paths::fs::Path<'static>` has no `is_node_module`; inline the check. if !strings::contains( json_source.path.text, NODE_MODULES_PATH.as_bytes(), @@ -1358,7 +1356,7 @@ impl PackageJSON { } } else if let js_ast::ExprData::EArray(e_array) = &side_effects_field.data { // Handle arrays, including empty arrays - // PORT NOTE: reshaped — `ArrayIterator` is not `Clone`; iterate the + // Reshaped — `ArrayIterator` is not `Clone`; iterate the // underlying `Vec` slice directly for both passes. let items = e_array.items.slice(); let mut map = SideEffectsMap::default(); @@ -1463,10 +1461,6 @@ impl PackageJSON { } } - // TODO(port): bun_install::{Dependency, Architecture, OperatingSystem, - // lockfile::Package::DependencyGroup, PackageManager}. The whole - // dependencies/os/cpu block is install-tier. - if INCLUDE_DEPENDENCIES == IncludeDependencies::Main || INCLUDE_DEPENDENCIES == IncludeDependencies::Local { @@ -1560,9 +1554,10 @@ impl PackageJSON { if total_dependency_count > 0 { package_json.dependencies.map = DependencyHashMap::default(); - // TODO(port): lifetime — source_buf borrows json_source.contents + // source_buf borrows json_source.contents (lifetime-erased; + // owned by `package_json.source_contents` on the success path). package_json.dependencies.source_buf = contents_static; - // PORT NOTE: Zig used `SemverString.ArrayHashContext` (compares against + // Zig used `SemverString.ArrayHashContext` (compares against // `source_buf`); ArrayHashMap has no `*_context` variant yet — the // generic `put_assume_capacity` path is sufficient because keys are // `SemverString` (offset+len into `source_buf`, hashed by content). @@ -1644,7 +1639,7 @@ impl PackageJSON { } // used by `bun run` - // PORT NOTE: `Expr::as_property_string_map` returns + // `Expr::as_property_string_map` returns // `ArrayHashMap<&'bump [u8], &'bump [u8]>` (bump-tied lifetimes), but // `ScriptsMap` stores `&'static [u8]` borrowing the package.json source // bytes (`contents_static`, see SAFETY note above). Inline the @@ -1704,7 +1699,7 @@ impl PackageJSON { } let _ = (include_scripts, package_id); - // PORT NOTE: reshaped for borrowck — assign source last (see struct init above). + // Reshaped for borrowck — assign source last (see struct init above). // `bun_ast::Source` isn't `Clone`; reconstruct from its (all-Copy/Clone) fields. package_json.source = bun_ast::Source { path: json_source.path, @@ -1767,7 +1762,7 @@ impl<'a> Visitor<'a> { }; } js_ast::ExprData::EString(str) => { - // PORT NOTE: JSON-parsed strings are always UTF-8 (latin1 source bytes); + // JSON-parsed strings are always UTF-8 (latin1 source bytes); // `str.data` is the raw slice, no bump-arena transcode needed. debug_assert!(!str.is_utf16); return Entry { @@ -1790,11 +1785,9 @@ impl<'a> Visitor<'a> { } js_ast::ExprData::EObject(e_obj) => { let prop_len = e_obj.properties.len_u32() as usize; - // PORT NOTE: reshaped for borrowck — Zig used MultiArrayList column slices; - // EntryDataMapList is a Vec placeholder until - // bun_collections::MultiArrayList lands. Push whole entries instead of - // writing through three parallel column slices. - // TODO(port): bun_collections::MultiArrayList column accessors + // Reshaped for borrowck — Zig used MultiArrayList column slices; + // `EntryDataMapList` is a `Vec`, so push whole entries + // instead of writing through three parallel column slices. let mut map_data: EntryDataMapList = Vec::with_capacity(prop_len); let mut expansion_keys: Vec = Vec::with_capacity(prop_len); let mut is_conditional_sugar = false; @@ -1906,7 +1899,7 @@ pub enum EntryData { Invalid, Null, Boolean(bool), - String(Box<[u8]>), // TODO(port): lifetime — borrows source contents in Zig + String(Box<[u8]>), // owned copy (Zig borrowed the package.json source contents) Array(Box<[Entry]>), Map(EntryDataMap), } @@ -1928,34 +1921,25 @@ pub struct EntryDataMap { pub list: EntryDataMapList, } -// TODO(port): bun_collections::MultiArrayList — needs MultiArrayElement derive + -// per-field column accessors. Using Vec as a placeholder shape. +// Zig used `MultiArrayList(MapEntry)` (SoA columns); a plain `Vec` +// (AoS) keeps entries whole — same contents, different layout. pub type EntryDataMapList = Vec; #[derive(Clone)] pub struct MapEntry { - pub key: Box<[u8]>, // TODO(port): lifetime — borrows source contents in Zig + pub key: Box<[u8]>, // owned copy (Zig borrowed the package.json source contents) pub key_range: bun_ast::Range, pub value: Entry, } -// TODO(port): MultiArrayList field selector enum -pub enum MapEntryField { - Key, - KeyRange, - Value, -} - impl Entry { pub fn keys_start_with_dot(&self) -> bool { - // TODO(port): bun_collections::MultiArrayList column accessor; Vec placeholder. matches!(&self.data, EntryData::Map(m) if !m.list.is_empty() && strings::starts_with_char(&m.list[0].key, b'.')) } pub fn value_for_key(&self, key_: &[u8]) -> Option<&Entry> { match &self.data { EntryData::Map(m) => { - // TODO(port): bun_collections::MultiArrayList column accessor; Vec placeholder. for entry in m.list.iter() { if strings::eql(&entry.key, key_) { return Some(&entry.value); @@ -1981,10 +1965,9 @@ pub struct ESModule<'a> { #[derive(Clone)] pub struct Resolution { pub status: Status, - // PORT NOTE: Zig returned slices into threadlocal PathBuffers / the package.json source + // Zig returned slices into threadlocal PathBuffers / the package.json source // buffer. In Rust the source-buffer case (`EntryData::String(Box<[u8]>)`) is owned by a // possibly-temporary `Entry`, so borrowing would dangle. Copy out into an owned buffer. - // TODO(perf): thread a real `'a` lifetime once `EntryData::String` is `&'a [u8]`. pub path: Box<[u8]>, pub debug: ResolutionDebug, } @@ -2178,7 +2161,6 @@ impl<'a> Package<'a> { } pub fn parse(specifier: &'a [u8], subpath_buf: &'a mut [u8]) -> Option> { - // TODO(port): lifetime — &'static is a placeholder; should be <'a> if specifier.is_empty() { return None; } @@ -2244,56 +2226,7 @@ impl<'a> Package<'a> { } } -// ── Local string helpers (TODO(port): bun_core::{replacement_size, replace}) ── -// Minimal local impls so the ESModule resolution algorithm compiles; replace with the -// canonical bun_core versions once they land. Recorded in blocked_on. - -/// Port of `std.mem.replacementSize` — total bytes after replacing every `needle` in -/// `input` with `replacement`. -#[inline] -fn replacement_size(input: &[u8], needle: &[u8], replacement: &[u8]) -> usize { - if needle.is_empty() { - return input.len(); - } - let mut size = 0usize; - let mut i = 0usize; - while i < input.len() { - if input[i..].starts_with(needle) { - size += replacement.len(); - i += needle.len(); - } else { - size += 1; - i += 1; - } - } - size -} - -/// Port of `std.mem.replace` — replace every `needle` in `input` with `replacement`, -/// writing into `output`. Returns number of replacements. -#[inline] -fn replace(input: &[u8], needle: &[u8], replacement: &[u8], output: &mut [u8]) -> usize { - if needle.is_empty() { - output[..input.len()].copy_from_slice(input); - return 0; - } - let mut i = 0usize; - let mut o = 0usize; - let mut count = 0usize; - while i < input.len() { - if input[i..].starts_with(needle) { - output[o..o + replacement.len()].copy_from_slice(replacement); - o += replacement.len(); - i += needle.len(); - count += 1; - } else { - output[o] = input[i]; - o += 1; - i += 1; - } - } - count -} +use bun_core::strings::{replace, replacement_size}; const INVALID_PERCENT_CHARS: [&[u8]; 4] = [b"%2f", b"%2F", b"%5c", b"%5C"]; @@ -2304,7 +2237,7 @@ struct ModuleBufs { } thread_local! { - // PORT NOTE: bun.ThreadlocalBuffers — Zig heap-allocates the buffer struct on first use and + // bun.ThreadlocalBuffers — Zig heap-allocates the buffer struct on first use and // stores only a pointer in TLS so the static-TLS template stays small (PE/COFF has no // TLS-BSS; ELF PT_TLS MemSiz scales with this — see test/js/bun/binary/tls-segment-size). // resolve_target / resolve_target_reverse are RECURSIVE (Map/Array arms call themselves), so a @@ -2331,7 +2264,7 @@ fn module_bufs() -> *mut ModuleBufs { }) } -// PORT NOTE: Zig used `r: *const ESModule` (const ptr) but mutated `r.module_type.*` +// Zig used `r: *const ESModule` (const ptr) but mutated `r.module_type.*` // and `r.debug_logs.?.*` through interior pointers. In Rust those fields are `&'a mut T`, // so reading/writing them requires `&mut self`. All resolution methods take `&mut self`. impl<'a> ESModule<'a> { @@ -2419,7 +2352,7 @@ impl<'a> ESModule<'a> { }; } - // PORT NOTE: Zig returned a slice into the threadlocal resolved_path_buf_percent. + // Zig returned a slice into the threadlocal resolved_path_buf_percent. // Copy out — see `Resolution.path` note. PERF(port): avoid the alloc if hot. result.path = Box::<[u8]>::from(resolved_path); result @@ -2640,10 +2573,10 @@ impl<'a> ESModule<'a> { )); log.increase_indent(); } - // PORT NOTE: Zig had `defer log.decrease_indent()` capturing the unwrapped + // Zig had `defer log.decrease_indent()` capturing the unwrapped // `*DebugLogs`. Rust scopeguard cannot hold the &mut across the recursive - // `&mut self` calls below; manual decrease at each return below. - // TODO(port): errdefer — verify all return paths decrease_indent. + // `&mut self` calls below; every return path in this arm invokes + // `dedent!()` manually instead (audited: all 10 returns in this arm dedent). macro_rules! dedent { () => { if let Some(log) = self.debug_logs.as_deref_mut() { @@ -2738,7 +2671,7 @@ impl<'a> ESModule<'a> { }, }; } else { - // PORT NOTE: Zig used `.auto` here, carried over as a + // Zig used `.auto` here, carried over as a // latent Windows bug (#30839): this branch runs when an // `imports` target is itself a package specifier // (e.g. `@myproject/resolver`) that we hand back to @@ -2909,7 +2842,7 @@ impl<'a> ESModule<'a> { let mut did_find_map_entry = false; let mut last_map_entry_i: usize = 0; - // PORT NOTE: Zig used MultiArrayList column slices; `EntryDataMapList` + // Zig used MultiArrayList column slices; `EntryDataMapList` // is `Vec` so iterate AoS directly. for (i, entry) in object.list.iter().enumerate() { let key: &[u8] = &entry.key; @@ -2991,7 +2924,7 @@ impl<'a> ESModule<'a> { // | ^ // // More information: https://github.com/evanw/esbuild/issues/1484 - // PORT NOTE: reshaped for borrowck — return_target points into slice; clone keys below + // Reshaped for borrowck — return_target points into slice; clone keys below return_target = last_map_entry_value; } diff --git a/src/resolver/resolver.rs b/src/resolver/resolver.rs index ae4cd3193287..7aff6314722d 100644 --- a/src/resolver/resolver.rs +++ b/src/resolver/resolver.rs @@ -112,7 +112,7 @@ mod bun_paths { ) } pub(super) fn join_abs(cwd: &[u8], platform: Platform, part: &[u8]) -> &'static [u8] { - // PORT NOTE: `resolve_path::join_abs` ties the result lifetime to `cwd`, but the + // NOTE: `resolve_path::join_abs` ties the result lifetime to `cwd`, but the // returned slice always points into the threadlocal `PARSER_JOIN_INPUT_BUFFER` // (or is `cwd` itself when `parts.is_empty()`, which never happens here — we // pass exactly one part). Re-erase to `'static` so the resolver can hold it @@ -120,7 +120,7 @@ mod bun_paths { let s = dispatch_platform!(platform, |P| ::bun_paths::resolve_path::join_abs::

( cwd, part )); - // SAFETY: see PORT NOTE — slice borrows threadlocal storage, valid 'static per-thread. + // SAFETY: see NOTE — slice borrows threadlocal storage, valid 'static per-thread. unsafe { bun_ptr::detach_lifetime(s) } } pub(super) fn join(parts: &[&[u8]], platform: Platform) -> &'static [u8] { @@ -217,7 +217,7 @@ mod bun_sys { path: &[u8], _opts: OpenDirOptions, ) -> core::result::Result { - // PORT NOTE: callers pass either a `&'static [u8]` literal or a NUL-terminated + // NOTE: callers pass either a `&'static [u8]` literal or a NUL-terminated // slice; `open_dir_at` builds its own ZStr internally so we strip the sentinel. let path = if path.last() == Some(&0) { &path[..path.len() - 1] @@ -331,14 +331,18 @@ fn intern_package_json(pkg: PackageJSON) -> core::ptr::NonNull { // `debuglog!` macro forwards to the real `bun_core::scoped_log!` so debug builds // emit and release builds dead-strip (PORTING.md §Logging). // -// PORT NOTE: resolver.zig:1692 also binds `const dev = Output.scoped(.Resolver, -// .visible)` for `bustDirCache` — same scope name, different visibility. Rust's -// `declare_scope!` is one static per ident; route both through the `.hidden` -// declaration (matches the file-top binding) and let `BUN_DEBUG_Resolver=1` -// surface the bust log. bun_core::define_scoped_log!(debuglog, Resolver, hidden); -// PORT NOTE: `Path` in the body is the `'static`-interned variant (paths borrow +// Port of `const dev = Output.scoped(.Resolver, .visible)` (resolver.zig:1692), +// used by `bustDirCache`. Same `Resolver` tag as `debuglog` above (so +// `BUN_DEBUG_Resolver` controls both) but visible by default. `declare_scope!` +// derives the tag from the static's ident, so this one is hand-declared to +// keep the printed tag identical. +#[allow(non_upper_case_globals)] +static ResolverDev: bun_core::output::ScopedLogger = + bun_core::output::ScopedLogger::new("Resolver", bun_core::output::Visibility::Visible); + +// NOTE: `Path` in the body is the `'static`-interned variant (paths borrow // DirnameStore/FilenameStore). Alias here so the ~80 bare-`Path` use sites // resolve without a per-site lifetime annotation. type Path = crate::fs::Path<'static>; @@ -367,7 +371,7 @@ pub struct Bufs { pub esm_absolute_package_path: PathBuffer, pub esm_absolute_package_path_joined: PathBuffer, - // PORT NOTE: Zig left this `= undefined`; `DirEntryResolveQueueItem` holds + // NOTE: Zig left this `= undefined`; `DirEntryResolveQueueItem` holds // `&'static [u8]` fields, so a zeroed bit-pattern is UB in Rust. Use // `MaybeUninit` and `assume_init_{ref,mut}` at the (linear write-then-read) // use sites in `dir_info_cached_maybe_log`. @@ -462,19 +466,17 @@ macro_rules! bufs { } // This is a global so even if multiple resolvers are created, the mutex will still work -// TODO(port): `bun_threading::Mutex` has no `const fn new()`; use LazyLock until it does. // `pub(crate)` so the `fs::EntriesMap::inner` debug-assert can verify it is held // (the resolver mutex is one of the two documented guards for the entries singleton). -pub(crate) static RESOLVER_MUTEX: std::sync::LazyLock = - std::sync::LazyLock::new(Mutex::default); +pub(crate) static RESOLVER_MUTEX: Mutex = Mutex::new(); // Zig had `resolver_Mutex_loaded` to lazily zero-init; Rust const init handles that. type BinFolderArray = BoundedArray<&'static [u8], 128>; -// TODO(port): `BoundedArray` has no const constructor; init lazily under +// `BoundedArray` has no const constructor; init lazily under // `BIN_FOLDERS_LOADED` (matches Zig's `bin_folders_loaded` lazy zero-init). static BIN_FOLDERS: bun_core::RacyCell> = bun_core::RacyCell::new(core::mem::MaybeUninit::uninit()); -static BIN_FOLDERS_LOCK: std::sync::LazyLock = std::sync::LazyLock::new(Mutex::default); +static BIN_FOLDERS_LOCK: Mutex = Mutex::new(); static BIN_FOLDERS_LOADED: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false); @@ -489,7 +491,7 @@ pub use bun_watcher::AnyResolveWatcher; // type-generator returning a struct with `.init(ctx) -> AnyResolveWatcher` and a // monomorphized `watch` shim. Per PORTING.md (`fn Foo(comptime T) type` → `struct Foo`). // -// PORT NOTE: const fn-pointer generics (`adt_const_params` for fn ptrs) and +// NOTE: const fn-pointer generics (`adt_const_params` for fn ptrs) and // const params depending on type params are both forbidden. Reshape to a // runtime fn-pointer carried alongside the context — `init` produces the same // `AnyResolveWatcher` erased shim as Zig's monomorphized `wrap`. @@ -520,7 +522,7 @@ impl ResolveWatcher { pub struct Resolver<'a> { pub opts: options::BundleOptions, - // PORT NOTE: Zig `fs: *Fs.FileSystem` / `log: *logger.Log` are raw aliasing + // NOTE: Zig `fs: *Fs.FileSystem` / `log: *logger.Log` are raw aliasing // pointers — the bundler builds a `Resolver` per worker thread sharing the // process-wide `FileSystem` singleton, so `&'a mut` here would manufacture // aliased unique refs across threads (instant UB). Model as `*mut` / @@ -529,7 +531,7 @@ pub struct Resolver<'a> { pub fs: *mut Fs::FileSystem, pub log: NonNull, // allocator dropped — global mimalloc - /// PORT NOTE: Zig stores `[]const []const u8` aliasing into + /// NOTE: Zig stores `[]const []const u8` aliasing into /// `r.opts.extension_order` and saves/restores it across nested resolves. /// Stored as a `Copy` enum tag (no self-reference) and resolved on demand /// via [`Self::extension_order`] / [`options::BundleOptions::ext_order_slice`]. @@ -617,7 +619,7 @@ pub struct Resolver<'a> { /// This cache maps a directory path to information about that directory and /// all parent directories. When interacting with this structure, make sure /// to validate your keys with `Resolver.assertValidCacheKey` - // PORT NOTE: Zig `dir_cache: *DirInfo.HashMap` is a raw aliasing pointer to the + // NOTE: Zig `dir_cache: *DirInfo.HashMap` is a raw aliasing pointer to the // `DirInfo::hash_map_instance()` singleton. Modeled as `*mut` (not `&'static mut`) // for the same reason as `fs`/`log` above — every per-worker `Resolver` shares the // singleton, so a `&'static mut` here would manufacture aliased unique refs (UB). @@ -717,7 +719,7 @@ impl<'a> Resolver<'a> { /// Port of Zig `r.fs` deref. /// - /// PORT NOTE (Stacked Borrows): returns the RAW `*mut` (NOT `&'a mut`). A + /// NOTE (Stacked Borrows): returns the RAW `*mut` (NOT `&'a mut`). A /// `&'a mut` accessor would let two `fs()` calls manufacture coexisting /// aliased unique refs to the same singleton (PORTING.md §Forbidden: /// aliased-&mut), and any later `&mut *self.fs` retag would pop a previously @@ -770,7 +772,7 @@ impl<'a> Resolver<'a> { /// Raw-pointer projection to the inner `RealFS` (`self.fs.fs`). /// - /// PORT NOTE (Stacked Borrows): derived directly from the raw `*mut + /// NOTE (Stacked Borrows): derived directly from the raw `*mut /// FileSystem` field via `addr_of_mut!` so the resulting `*mut RealFS` /// carries SharedReadWrite provenance — later `fs_ref()` (Shared) or /// short-lived `&mut *self.fs()` retags do NOT invalidate it. Callers @@ -786,7 +788,7 @@ impl<'a> Resolver<'a> { /// Port of Zig `r.log` deref. /// - /// PORT NOTE (Stacked Borrows): returns RAW `*mut` (see `fs()` note). BACKREF + /// NOTE (Stacked Borrows): returns RAW `*mut` (see `fs()` note). BACKREF /// — owner (Transpiler/BundleV2) outlives the Resolver; worker clones share /// the same Log under the resolver mutex. Caller `unsafe { &mut *r.log() }` /// at each use site; do not bind the projected `&mut Log` across another @@ -856,7 +858,7 @@ impl<'a> Resolver<'a> { /// Port of Zig `r.dir_cache` deref. /// - /// PORT NOTE (Stacked Borrows): returns RAW `*mut` (see `fs()` note). ARENA — + /// NOTE (Stacked Borrows): returns RAW `*mut` (see `fs()` note). ARENA — /// `DirInfo::hash_map_instance()` singleton; never freed. Caller /// `unsafe { &mut *r.dir_cache() }` at each use site. #[inline(always)] @@ -997,7 +999,7 @@ impl<'a> Resolver<'a> { // `DirInfo::get_parent()` / `get_enclosing_browser_scope()` share storage // (Zig `BSSMap.init()` is a per-type singleton, not a fresh alloc). dir_cache: DirInfo::hash_map_instance(), - mutex: &*RESOLVER_MUTEX, + mutex: &RESOLVER_MUTEX, caches: CacheSet::init(), opts, timer: Timer::start().unwrap_or_else(|_| panic!("Timer fail")), @@ -1081,8 +1083,7 @@ impl<'a> Resolver<'a> { &mut self, flush_mode: FlushMode, ) -> core::result::Result<(), bun_core::Error> { - // TODO(port): narrow error set - // PORT NOTE: capture `log` before partially borrowing `self.debug_logs` + // NOTE: capture `log` before partially borrowing `self.debug_logs` // so the method call doesn't conflict with the field borrow (`log()` // derefs the raw `*mut Log` and is lifetime-decoupled from `&self`). // SAFETY: BACKREF — `self.log` points at owner-allocated `Log`; disjoint from @@ -1090,7 +1091,7 @@ impl<'a> Resolver<'a> { // `self.debug_logs.as_mut()` borrow below. let log = unsafe { &mut *self.log() }; if let Some(debug) = self.debug_logs.as_mut() { - // PORT NOTE: spec resolver.zig:650-658 — only consume `what`/`notes` inside + // NOTE: spec resolver.zig:650-658 — only consume `what`/`notes` inside // the arm that actually emits, so the success-at-non-verbose path touches // nothing. `add_range_debug_with_notes`/`add_verbose_with_notes` take // `&'static [u8]`; bypass them and build the `Msg` directly so the Log owns @@ -1165,11 +1166,25 @@ impl<'a> Resolver<'a> { bstr::BStr::new(import_path), bstr::BStr::new(source_dir), ); - // @breakpoint() — no Rust equiv; left as TODO(port) + // Zig `@breakpoint()` — traps into an attached debugger. + // NOTE: `core::arch::breakpoint()` is still unstable on the pinned + // toolchain; emit the trap instruction directly via stable `asm!`. + #[cfg(target_arch = "x86_64")] + // SAFETY: `int3` only raises SIGTRAP/EXCEPTION_BREAKPOINT; no memory or + // register state is touched. + unsafe { + core::arch::asm!("int3") + }; + #[cfg(target_arch = "aarch64")] + // SAFETY: `brk` only raises SIGTRAP/EXCEPTION_BREAKPOINT; no memory or + // register state is touched. + unsafe { + core::arch::asm!("brk #0xf000") + }; } let original_order = self.extension_order; - // PORT NOTE: Zig `defer r.extension_order = original_order` — reshaped for + // NOTE: Zig `defer r.extension_order = original_order` — reshaped for // borrowck so the restore happens explicitly at every return point below. self.extension_order = match kind { ast::ImportKind::Url | ast::ImportKind::AtConditional | ast::ImportKind::At => { @@ -1513,8 +1528,31 @@ impl<'a> Resolver<'a> { let _ = self.flush_debug_logs(FlushMode::Success); result.import_kind = kind; - if cfg!(feature = "debug_logs") { - // TODO(port): debuglog! with bun.fmt.fmtPath formatting + if cfg!(debug_assertions) { + // Spec resolver.zig:945-967 — `Environment.enable_logs` block; + // `debuglog!` self-gates on `debug_assertions`, the outer `if` + // dead-strips the `resolved_text` computation in release too. + let resolved_text: &[u8] = + result.path().map_or(&b""[..], |path| path.text()); + let opts = bun_core::fmt::PathFormatOptions::default; + if let Some(secondary) = result.path_pair.secondary.as_ref() { + debuglog!( + "resolve({}, from: {}, {}) = {} (secondary: {})", + bun_core::fmt::fmt_path(import_path, opts()), + bun_core::fmt::fmt_path(source_dir, opts()), + bstr::BStr::new(kind.label()), + bun_core::fmt::fmt_path(resolved_text, opts()), + bun_core::fmt::fmt_path(secondary.text(), opts()), + ); + } else { + debuglog!( + "resolve({}, from: {}, {}) = {}", + bun_core::fmt::fmt_path(import_path, opts()), + bun_core::fmt::fmt_path(source_dir, opts()), + bstr::BStr::new(kind.label()), + bun_core::fmt::fmt_path(resolved_text, opts()), + ); + } } ResultUnion::Success(result) } @@ -1543,7 +1581,6 @@ impl<'a> Resolver<'a> { import_path: &[u8], kind: ast::ImportKind, ) -> core::result::Result { - // TODO(port): narrow error set match self.resolve_and_auto_install(source_dir, import_path, kind, GlobalCache::disable) { ResultUnion::Success(result) => Ok(result), ResultUnion::Pending(_) | ResultUnion::NotFound => { @@ -1564,7 +1601,6 @@ impl<'a> Resolver<'a> { // SAFETY: PORT — `import_path` is caller-interned (source text / DirnameStore) // and outlives the returned Result. TODO(port): thread explicit lifetime. let import_path: &'static [u8] = unsafe { &*std::ptr::from_ref::<[u8]>(import_path) }; - // TODO(port): narrow error set if let Some(f) = self.opts.framework.as_ref() { if let Some(mod_) = f.built_in_modules.get(import_path) { match mod_ { @@ -1583,7 +1619,7 @@ impl<'a> Resolver<'a> { }); } bun_options_types::BuiltInModule::Import(path) => { - // PORT NOTE: copy out `path` so the `&self.opts.framework` borrow + // NOTE: copy out `path` so the `&self.opts.framework` borrow // ends before `self.resolve(&mut self, ...)`. // SAFETY: `path` borrows `self.opts.framework`, which lives for the // resolver's lifetime; the `'static` erase only releases the `&self` borrow. @@ -1604,7 +1640,6 @@ impl<'a> Resolver<'a> { result: &mut Result, kind: ast::ImportKind, ) -> core::result::Result<(), bun_core::Error> { - // TODO(port): narrow error set if result.flags.is_external() { return Ok(()); } @@ -1741,7 +1776,7 @@ impl<'a> Resolver<'a> { let parts = [dir.abs_real_path, query.entry().base()]; let mut buf = bun_paths::PathBuffer::uninit(); - // PORT NOTE: `abs_buf` returns a borrow of `buf`; capture only the + // NOTE: `abs_buf` returns a borrow of `buf`; capture only the // length so `buf` can be re-borrowed for null-termination below. let out_len = self.fs_ref().abs_buf(&parts, &mut buf).len(); @@ -1761,7 +1796,7 @@ impl<'a> Resolver<'a> { Fs::FileSystem::set_max_fd(file.native()); } - // PORT NOTE: snapshot `need_to_close_files` and raw-ptr the entry so + // NOTE: snapshot `need_to_close_files` and raw-ptr the entry so // the closure captures only Copy values — keeps `self` and // `query.entry` reborrowable across the guard's lifetime. let need_close = self.fs_ref().fs.need_to_close_files(); @@ -2211,7 +2246,7 @@ impl<'a> Resolver<'a> { } let prev_extension_order = self.extension_order; - // PORT NOTE: defer restore reshaped — restored before each return + // NOTE: defer restore reshaped — restored before each return if strings::path_contains_node_modules_folder(abs_path) { self.extension_order = self.opts.extension_order.kind(kind, true); } @@ -2568,7 +2603,7 @@ impl<'a> Resolver<'a> { let first_bust = self.fs_mut().fs.bust_entries_cache(path); let second_bust = self.dir_cache_mut().remove(path); bun_core::scoped_log!( - Resolver, + ResolverDev, "Bust {} = {}, {}", bstr::BStr::new(path), first_bust, @@ -2614,7 +2649,7 @@ impl<'a> Resolver<'a> { &mut self, import_path: &[u8], kind: ast::ImportKind, - // PORT NOTE: `DirInfoRef` (not `&mut`) — body re-enters `dir_cache` via + // NOTE: `DirInfoRef` (not `&mut`) — body re-enters `dir_cache` via // `dir_info_cached()` which, in the self-reference branch, returns the // SAME BSSMap slot. A `&mut` param carries an FnEntry protector under // Stacked Borrows; the inner retag would pop it (aliased-&mut UB). @@ -2634,7 +2669,7 @@ impl<'a> Resolver<'a> { )); debug.increase_indent(); } - // PORT NOTE: Zig `defer { debug.decreaseIndent() }` — reshaped for borrowck; + // NOTE: Zig `defer { debug.decreaseIndent() }` — reshaped for borrowck; // `decrease_indent()` is called explicitly at every return point below. // First, check path overrides from the nearest enclosing TypeScript "tsconfig.json" file @@ -2751,7 +2786,7 @@ impl<'a> Resolver<'a> { } let prev_extension_order = self.extension_order; - // PORT NOTE: defer restore reshaped — restored at end of block + // NOTE: defer restore reshaped — restored at end of block if let Some(ref esm) = esm_ { let abs_package_path: &[u8] = if is_self_reference { @@ -2774,7 +2809,7 @@ impl<'a> Resolver<'a> { if let Some(exports_map) = package_json.exports.as_ref() { // The condition set is determined by the kind of import let mut module_type = package_json.module_type; - // PORT NOTE: reshaped for borrowck — Zig held a single `ESModule` + // NOTE: reshaped for borrowck — Zig held a single `ESModule` // with a raw `*DebugLogs` across both `resolve` calls and the // intervening `handle_esm_resolution`. In Rust, keeping the // `ESModule` (which holds `&mut self.debug_logs`) alive across a @@ -2893,7 +2928,7 @@ impl<'a> Resolver<'a> { d.decrease_indent(); } *out = MatchResult { - // PORT NOTE: PackageJSON.source.path is bun_paths::fs::Path<'static>; convert + // NOTE: PackageJSON.source.path is bun_paths::fs::Path<'static>; convert // to the resolver's interned crate::fs::Path<'static> via its text. path_pair: PathPair { primary: Path::init(package_json.source.path.text), @@ -2994,7 +3029,7 @@ impl<'a> Resolver<'a> { // If the source directory doesn't have a node_modules directory, we can // check the global cache directory for a package.json file. // - // PORT NOTE (Stacked Borrows): `get_package_manager` returns the + // NOTE (Stacked Borrows): `get_package_manager` returns the // `*mut dyn AutoInstaller` raw pointer; the body below re-borrows // `self` for `enqueue_dependency_to_resolve` / `debug_logs` / // `log()`. The PackageManager lives in a separate allocation, so @@ -3165,7 +3200,7 @@ impl<'a> Resolver<'a> { if err == bun_core::err!("FileNotFound") { match manager!().get_preinstall_state(resolved_package_id) { Install::PreinstallState::Done => { - // PORT NOTE: `MatchResult.path_pair` is `Path<'static>`; + // NOTE: `MatchResult.path_pair` is `Path<'static>`; // intern `import_path` so the disabled-module record // outlives this frame (Zig had no lifetime here). let interned = Fs::file_system::DirnameStore::instance() @@ -3252,7 +3287,7 @@ impl<'a> Resolver<'a> { if let Some(package_json) = pkg_dir_info.package_json() { if let Some(exports_map) = package_json.exports.as_ref() { // The condition set is determined by the kind of import - // PORT NOTE: reshaped for borrowck — see identical note above. + // NOTE: reshaped for borrowck — see identical note above. // Resolve against the path "/", then join it with the absolute // directory path. This is done because ESM package resolution uses // URLs while our path resolution uses file system paths. We don't @@ -3415,7 +3450,6 @@ impl<'a> Resolver<'a> { dir_path_maybe_trail_slash: &[u8], package_id: Install::PackageID, ) -> core::result::Result, bun_core::Error> { - // TODO(port): narrow error set debug_assert!(self.package_manager.is_some()); let dir_path = strings::without_trailing_slash_windows_path(dir_path_maybe_trail_slash); @@ -3446,7 +3480,7 @@ impl<'a> Resolver<'a> { // resolver mutex held; `EntriesMap` methods are safe wrappers over the singleton. let mut cached_dir_entry_result = rfs!().entries.get_or_put(dir_path)?; - // PORT NOTE: always assigned by either the cached-hit arm or the + // NOTE: always assigned by either the cached-hit arm or the // `needs_iter` block below; null-init so rustc accepts the proof. let mut dir_entries_option: *mut Fs::file_system::real_fs::EntriesOption = core::ptr::null_mut(); @@ -3515,7 +3549,7 @@ impl<'a> Resolver<'a> { } if let Some(existing) = in_place { // SAFETY: see block-wide note above. - // PORT NOTE: Zig `clearAndFree` — `StringHashMap` (std::HashMap newtype) + // NOTE: Zig `clearAndFree` — `StringHashMap` (std::HashMap newtype) // has no separate `clear_and_free`; `clear()` drops all entries. unsafe { &mut *existing }.data.clear(); } @@ -3523,7 +3557,7 @@ impl<'a> Resolver<'a> { if self.store_fd { new_entry.fd = open_dir; } - // PORT NOTE: see `dir_info_cached_maybe_log` — `DirEntry.data` holds a `NonNull`, + // NOTE: see `dir_info_cached_maybe_log` — `DirEntry.data` holds a `NonNull`, // so a zeroed slot is UB; box `new_entry` directly for the fresh case. let dir_entries_ptr = match in_place { Some(p) => { @@ -3534,7 +3568,15 @@ impl<'a> Resolver<'a> { None => bun_core::heap::into_raw(Box::new(new_entry)), }; - // bun.fs.debug("readdir({f}, {s}) = {d}", ...) — TODO(port): scoped log + bun_core::scoped_log!( + crate::fs_full::Fs, + "readdir({}, {}) = {}", + open_dir, + bstr::BStr::new(dir_path), + // SAFETY: `dir_entries_ptr` is a live BSSMap slot (`in_place`) or a freshly + // boxed entry (see block-wide note above). + unsafe { (*dir_entries_ptr).data.count() }, + ); dir_entries_option = rfs!() .entries @@ -3550,7 +3592,7 @@ impl<'a> Resolver<'a> { // We must initialize it as empty so that the result index is correct. // This is important so that browser_scope has a valid index. - // PORT NOTE: erase the `&mut DirInfo` borrow to `*mut` immediately so + // NOTE: erase the `&mut DirInfo` borrow to `*mut` immediately so // `self.dir_cache` (and `*self`) are reborrowable for the call below. let dir_info_ptr: *mut DirInfo::DirInfo = self .dir_cache_mut() @@ -3586,7 +3628,7 @@ impl<'a> Resolver<'a> { fn enqueue_dependency_to_resolve( &mut self, - // PORT NOTE: Zig `package_json_: ?*PackageJSON` (mutable). Carried as + // NOTE: Zig `package_json_: ?*PackageJSON` (mutable). Carried as // `NonNull` end-to-end so the mut-provenance from `intern_package_json` // survives to the `package_manager_package_id` write below — taking // `*const` and casting back to `*mut` would be UB under Stacked Borrows. @@ -3606,7 +3648,7 @@ impl<'a> Resolver<'a> { } let input_package_id = *input_package_id_; - // PORT NOTE: see `manager_ptr` note in `load_node_modules` — split the + // NOTE: see `manager_ptr` note in `load_node_modules` — split the // `&mut self` borrow by holding the PackageManager via raw pointer. let pm_ptr: *mut dyn AutoInstaller = self.get_package_manager(); macro_rules! pm { @@ -3629,7 +3671,7 @@ impl<'a> Resolver<'a> { // from `NonNull::from(&mut **last)` and no other live borrow // exists here. let package_json: &mut PackageJSON = unsafe { package_json.as_mut() }; - // PORT NOTE: Zig called `Package.fromPackageJSON(lockfile, pm, + // NOTE: Zig called `Package.fromPackageJSON(lockfile, pm, // log, package_json, features)` then `setHasInstallScript` then // `lockfile.appendPackage`. The `Package` type is bun_install- // internal; the `AutoInstaller` impl performs all three steps. @@ -3697,7 +3739,7 @@ impl<'a> Resolver<'a> { } } - // PORT NOTE: 1:1 with Zig — `resolver.zig` ends this function with + // NOTE: 1:1 with Zig — `resolver.zig` ends this function with // `bun.unreachablePanic("TODO: implement enqueueDependencyToResolve for // non-root packages", .{})`. The non-root path is genuinely unimplemented // in the Zig source; this is not a porting stub. @@ -3795,7 +3837,7 @@ impl<'a> Resolver<'a> { )); } esm_resolution.status = Status::ModuleNotFoundMissingExtension; - let _ = ext; // PORT NOTE: Zig stored `missing_suffix = ext` here; unused after `return null`. + let _ = ext; // NOTE: Zig stored `missing_suffix = ext` here; unused after `return null`. break; } } @@ -3919,7 +3961,7 @@ impl<'a> Resolver<'a> { pub fn resolve_without_remapping( &mut self, - // PORT NOTE: `DirInfoRef` (not `&mut`) — forwards into `load_node_modules` + // NOTE: `DirInfoRef` (not `&mut`) — forwards into `load_node_modules` // which re-enters `dir_cache` and may re-derive the same DirInfo slot. // Spec resolver.zig:2584 takes raw `*DirInfo`. source_dir_info: DirInfoRef, @@ -3949,7 +3991,7 @@ impl<'a> Resolver<'a> { // Since tsconfig.json is cached permanently, in our DirEntries cache // we must use the global allocator let mut entry = self.caches.fs.read_file_with_allocator( - // SAFETY: process-global `FileSystem` singleton (see `fs()` PORT NOTE); narrow `&mut` + // SAFETY: process-global `FileSystem` singleton (see `fs()` NOTE); narrow `&mut` // for this call only — `self.caches` is a field of `self` (disjoint allocation). unsafe { &mut *self.fs() }, file, @@ -3958,7 +4000,7 @@ impl<'a> Resolver<'a> { None, None, )?; - // PORT NOTE: reshaped for borrowck — `mem::take` the contents (leaving + // NOTE: reshaped for borrowck — `mem::take` the contents (leaving // `Contents::Empty` behind) so `entry` stays whole for the close-guard. let entry_contents = core::mem::take(&mut entry.contents); let _close_guard = scopeguard::guard(entry, |mut e| { @@ -3989,7 +4031,7 @@ impl<'a> Resolver<'a> { let source = bun_ast::Source::init_path_string_owned(key_path, contents); let file_dir = source.path.source_dir(); - // SAFETY: BACKREF — `self.log` (see `log()` PORT NOTE); disjoint from `self.caches`, + // SAFETY: BACKREF — `self.log` (see `log()` NOTE); disjoint from `self.caches`, // narrow `&mut` for this call only. let mut result = match TSConfigJSON::parse(unsafe { &mut *self.log() }, &source, &mut self.caches.json)? @@ -4001,7 +4043,7 @@ impl<'a> Resolver<'a> { if result.has_base_url() { // this might leak if !bun_paths::is_absolute(&result.base_url) { - // PORT NOTE: Zig interns into `dirname_store` and stores the + // NOTE: Zig interns into `dirname_store` and stores the // arena slice; Rust `base_url: Box<[u8]>` owns its bytes, so // copy `abs_buf`'s thread-local result directly instead of // double-copying through the arena. @@ -4023,7 +4065,7 @@ impl<'a> Resolver<'a> { result.base_url_for_paths = Box::from(abs); } - // PORT NOTE: Zig `TSConfigJSON.parse` returns `*TSConfigJSON` (already + // NOTE: Zig `TSConfigJSON.parse` returns `*TSConfigJSON` (already // heap). Return the `Box` so the caller (`dir_info_uncached`) takes // ownership — intermediate configs in an extends-chain are dropped via // `heap::take`, the final one is interned into the DirInfo cache. @@ -4046,7 +4088,7 @@ impl<'a> Resolver<'a> { package_id: Option, ) -> core::result::Result>, bun_core::Error> { use crate::package_json::{IncludeDependencies, IncludeScripts}; - // PORT NOTE: Zig threaded both as comptime params; `IncludeDependencies` is a + // NOTE: Zig threaded both as comptime params; `IncludeDependencies` is a // const generic on `PackageJSON::parse`, `IncludeScripts` is runtime (it only // gates one branch). let include_scripts = if self.care_about_scripts { @@ -4073,7 +4115,7 @@ impl<'a> Resolver<'a> { }; let Some(pkg) = pkg else { return Ok(None) }; - // PORT NOTE: Zig `PackageJSON.new` = `bun.TrivialNew` (heap-allocate, + // NOTE: Zig `PackageJSON.new` = `bun.TrivialNew` (heap-allocate, // never freed — DirInfo cache holds `&'static` refs). PORTING.md // §Forbidden bars `Box::leak`; intern into the process-lifetime arena // owned alongside the DirInfo singleton instead. @@ -4099,7 +4141,7 @@ impl<'a> Resolver<'a> { self.dir_info_cached_maybe_log(false, path).ok().flatten() } - // PORT NOTE: Zig's `dirInfoCachedMaybeLog` takes `comptime enable_logging` + // NOTE: Zig's `dirInfoCachedMaybeLog` takes `comptime enable_logging` // and `comptime follow_symlinks`. `follow_symlinks` is `true` at every call // site, so it's dropped here; `enable_logging` is a plain runtime parameter // (it gates one cold error-formatting branch) so this large dir-walk function @@ -4109,7 +4151,6 @@ impl<'a> Resolver<'a> { enable_logging: bool, raw_input_path: &[u8], ) -> core::result::Result, bun_core::Error> { - // TODO(port): narrow error set // `self.mutex` is `&'static Mutex` (Copy) — bind it first so the guard // doesn't keep `self` borrowed across the body. let _unlock = self.mutex.lock_guard(); @@ -4228,7 +4269,7 @@ impl<'a> Resolver<'a> { let root_path = &path[0..1]; Self::assert_valid_cache_key(root_path); - // PORT NOTE: hold RealFS as a raw `*mut` so the entries-mutex/close-dirs + // NOTE: hold RealFS as a raw `*mut` so the entries-mutex/close-dirs // scopeguards can capture it by Copy without keeping a `self.rfs_ptr()` // borrow live across the loop body (which calls `&mut self` methods). // SAFETY: ARENA — `self.fs` points at the process-global FileSystem singleton. @@ -4332,7 +4373,7 @@ impl<'a> Resolver<'a> { let open_dir_count = core::cell::Cell::new(0usize); // When this function halts, any item not processed means it's not found. - // PORT NOTE: capture only what the cleanup needs by-value (store_fd) / by-Cell + // NOTE: capture only what the cleanup needs by-value (store_fd) / by-Cell // (open_dir_count) so the guard doesn't pin `&mut self` across the loop // body. `need_to_close_files()` is evaluated AT DROP TIME (matching // Zig's `defer`), not snapshotted up-front — the loop body calls @@ -4418,7 +4459,11 @@ impl<'a> Resolver<'a> { .map_err(Into::into) }; - // bun.fs.debug("open({s})", .{sentinel}) — TODO(port): scoped log + bun_core::scoped_log!( + crate::fs_full::Fs, + "open({})", + bstr::BStr::new(sentinel.as_bytes()), + ); // Restore the byte we NUL-terminated above (Zig: `defer path[len] = prev_char`). // No early-return path exists between the write and here, so a guard is unnecessary. path[nul_at] = prev_char; @@ -4569,7 +4614,7 @@ impl<'a> Resolver<'a> { new_entry.data.reserve(64); let mut dir_iterator = bun_sys::iterate_dir(open_dir); - // PORT NOTE: Zig `while (dir_iterator.next().unwrap()) |entry|` — + // NOTE: Zig `while (dir_iterator.next().unwrap()) |entry|` — // `.unwrap()` was on the inner `Maybe(?Entry)`; the Rust `WrappedIterator::next` // is already flattened to `Result>`, so the `.unwrap()` // moved to `?`-style break-on-error. @@ -4594,11 +4639,11 @@ impl<'a> Resolver<'a> { } if let Some(existing) = in_place { // SAFETY: see block-wide note above. - // PORT NOTE: Zig `clear_and_free`; bun_collections::StringHashMap exposes `clear`. + // NOTE: Zig `clear_and_free`; bun_collections::StringHashMap exposes `clear`. unsafe { &mut *existing }.data.clear(); } new_entry.fd = if self.store_fd { open_dir } else { FD::INVALID }; - // PORT NOTE: Zig `entries_ptr = in_place orelse allocator.create(DirEntry)` then + // NOTE: Zig `entries_ptr = in_place orelse allocator.create(DirEntry)` then // `entries_ptr.* = new_entry` (no drop glue). `DirEntry.data` is a `HashMap` // (`NonNull` inside), so a zeroed slot is UB and `*ptr = new_entry` would drop it. // Box `new_entry` directly for the fresh case; assign-into only for `in_place`. @@ -4610,6 +4655,19 @@ impl<'a> Resolver<'a> { } None => bun_core::heap::into_raw(Box::new(new_entry)), }; + // NOTE (Stacked Borrows): log BEFORE `entries.put` stores the + // `&'static mut DirEntry` — a later read through the parent raw + // pointer would pop that reference's Unique tag (Zig logs after + // the put, resolver.zig:3018, but the ordering is unobservable). + bun_core::scoped_log!( + crate::fs_full::Fs, + "readdir({}, {}) = {}", + open_dir, + bstr::BStr::new(dir_path), + // SAFETY: `dir_entries_ptr` is a live BSSMap slot (`in_place`) or a + // freshly boxed entry (see block-wide note above). + unsafe { (*dir_entries_ptr).data.count() }, + ); dir_entries_option = rfs!().entries.put( &mut cached_dir_entry_result, Fs::file_system::real_fs::EntriesOption::Entries( @@ -4617,14 +4675,13 @@ impl<'a> Resolver<'a> { unsafe { &mut *dir_entries_ptr }, ), )?; - // bun.fs.debug("readdir({f}, {s}) = {d}", ...) — TODO(port): scoped log } // We must initialize it as empty so that the result index is correct. // This is important so that browser_scope has a valid index. - // PORT NOTE: erase the `&mut DirInfo` borrow to `*mut` immediately so + // NOTE: erase the `&mut DirInfo` borrow to `*mut` immediately so // `self.dir_cache` (and `*self`) are reborrowable for the call below. - // SAFETY: ARENA — `dir_cache()` singleton (see PORT NOTE). Stacked Borrows: bind + // SAFETY: ARENA — `dir_cache()` singleton (see NOTE). Stacked Borrows: bind // ONE `&mut HashMap` and derive BOTH slot pointers from it so they share a parent // tag — a second `&mut *self.dir_cache()` Unique retag of the whole `BSSMapInner` // (whose `backing_buf` is inline) would pop `dir_info_ptr`'s tag before @@ -4706,7 +4763,7 @@ impl<'a> Resolver<'a> { // Check for exact matches first { - // PORT NOTE: ArrayHashMap has no `&self` (key,value) iterator; zip the + // NOTE: ArrayHashMap has no `&self` (key,value) iterator; zip the // parallel `keys()`/`values()` slices (insertion order). for (key, value) in tsconfig .paths @@ -4863,7 +4920,7 @@ impl<'a> Resolver<'a> { pub fn load_package_imports( &mut self, import_path: &[u8], - // PORT NOTE: `DirInfoRef` (not `&mut`) — `handle_esm_resolution` re-enters + // NOTE: `DirInfoRef` (not `&mut`) — `handle_esm_resolution` re-enters // `dir_cache` via `dir_info_cached(dirname(abs_esm_path))`; for any // imports-map entry resolving to `./` that dirname equals // `dir_info.abs_path`, re-deriving `&mut` to the SAME slot while a @@ -4882,7 +4939,8 @@ impl<'a> Resolver<'a> { bstr::BStr::new(package_json.source.path.text) )); debug.increase_indent(); - // defer debug.decreaseIndent() — TODO(port): missing matching decrease in Zig too + // No matching `decreaseIndent()` — intentional parity with the Zig spec, + // which also leaks the indent here (resolver.zig `loadFromImportsMap`). } let imports_map = package_json.imports.as_ref().unwrap(); @@ -4897,7 +4955,7 @@ impl<'a> Resolver<'a> { } let mut module_type = options::ModuleType::Unknown; - // PORT NOTE: reshaped for borrowck — Zig kept a raw `*DebugLogs` inside + // NOTE: reshaped for borrowck — Zig kept a raw `*DebugLogs` inside // `ESModule` across the subsequent `&mut self` calls. In Rust that is // aliased-&mut UB, so the `ESModule` is constructed as a temporary whose // borrow of `self.debug_logs` ends as soon as `resolve_imports` returns. @@ -5062,7 +5120,7 @@ impl<'a> Resolver<'a> { pub fn load_from_main_field( &mut self, path: &[u8], - // PORT NOTE: `DirInfoRef` (not `&mut`) — `get_enclosing_browser_scope()` + // NOTE: `DirInfoRef` (not `&mut`) — `get_enclosing_browser_scope()` // may return `dir_info` itself (resolver.zig:4161 self-browser-scope), // which would alias a live `&mut`. Spec uses raw `*DirInfo`. dir_info: DirInfoRef, @@ -5174,7 +5232,7 @@ impl<'a> Resolver<'a> { // nodeModulePathsForJS / Resolver__propForRequireMainPaths: see src/jsc/resolver_jsc.zig // (no Zig callers; exported to C++ only) - // PORT NOTE: `dir_info` is a `DirInfoRef` (matching spec `*DirInfo`) so + // NOTE: `dir_info` is a `DirInfoRef` (matching spec `*DirInfo`) so // `load_index_with_extension` may re-borrow without aliasing the caller's `&mut`. pub fn load_as_index( &mut self, @@ -5183,7 +5241,7 @@ impl<'a> Resolver<'a> { out: &mut MatchResult, ) -> MatchStatus { // Try the "index" file with extensions - // PORT NOTE: index by `0..len` so each iteration takes a fresh short + // NOTE: index by `0..len` so each iteration takes a fresh short // borrow of `self.opts` that ends before `&mut self` is taken by // `load_index_with_extension` (matches `extra_cjs_extensions` loop below). let n = self.opts.ext_order_slice(extension_order).len(); @@ -5199,7 +5257,7 @@ impl<'a> Resolver<'a> { return MatchStatus::Success; } } - // PORT NOTE: index by `0..len` so each iteration takes a fresh short + // NOTE: index by `0..len` so each iteration takes a fresh short // borrow of `self.opts` that ends before `&mut self` is taken by // `load_index_with_extension` (avoids the forbidden lifetime-extension cast). let n = self.opts.extra_cjs_extensions.len(); @@ -5305,7 +5363,7 @@ impl<'a> Resolver<'a> { pub fn load_as_index_with_browser_remapping( &mut self, - // PORT NOTE: `DirInfoRef` (not `&mut`) — `get_enclosing_browser_scope()` + // NOTE: `DirInfoRef` (not `&mut`) — `get_enclosing_browser_scope()` // may return `dir_info` itself (resolver.zig:4161 self-browser-scope), // which would alias a live `&mut`. Spec uses raw `*DirInfo`. dir_info: DirInfoRef, @@ -5465,7 +5523,7 @@ impl<'a> Resolver<'a> { }}; } - // PORT NOTE: `DirInfoRef` (matching spec resolver.zig:3674 raw `*DirInfo`). + // NOTE: `DirInfoRef` (matching spec resolver.zig:3674 raw `*DirInfo`). // The callees fetch `get_enclosing_browser_scope()` which can resolve // back to this same BSSMap slot — holding a `&mut` here would alias. let dir_info: DirInfoRef = match self.dir_info_cached(path) { @@ -5771,7 +5829,7 @@ impl<'a> Resolver<'a> { // Try the path with extensions bufs!(load_as_file)[..path.len()].copy_from_slice(path); - // PORT NOTE: index by `0..len` so each iteration takes a fresh short + // NOTE: index by `0..len` so each iteration takes a fresh short // borrow of `self.opts` that ends before `&mut self` is taken by // `load_extension` (matches `extra_cjs_extensions` loop below). let n = self.opts.ext_order_slice(extension_order).len(); @@ -5785,7 +5843,7 @@ impl<'a> Resolver<'a> { } } - // PORT NOTE: index by `0..len` so each iteration takes a fresh short + // NOTE: index by `0..len` so each iteration takes a fresh short // borrow of `self.opts` that ends before `&mut self` is taken by // `load_extension` (avoids the forbidden lifetime-extension cast). let n = self.opts.extra_cjs_extensions.len(); @@ -5815,7 +5873,7 @@ impl<'a> Resolver<'a> { // https://github.com/microsoft/TypeScript/issues/4595 if let Some(last_dot) = strings::last_index_of_char(base, b'.') { let ext = &base[last_dot..base.len()]; - // PORT NOTE: spec resolver.zig:3890-3891 — Zig `and` binds tighter than `or`, so the + // NOTE: spec resolver.zig:3890-3891 — Zig `and` binds tighter than `or`, so the // node_modules gate only applies to the `.mjs` arm. Mirror that precedence exactly. if ext == b".js" || ext == b".jsx" @@ -6009,7 +6067,7 @@ impl<'a> Resolver<'a> { let rfs_ptr: *mut Fs::file_system::RealFS = self.rfs_ptr(); // SAFETY: caller passes `_entries` as a live slot in the global BSSMap-backed entries cache (ARENA). let entries_ptr: *mut Fs::file_system::DirEntry = unsafe { &mut *_entries }.entries_mut(); - // PORT NOTE: re-borrow per use; see SAFETY note above. + // NOTE: re-borrow per use; see SAFETY note above. macro_rules! rfs { () => { // SAFETY: `rfs_ptr` points at the process-global RealFS singleton; see note above. @@ -6174,7 +6232,7 @@ impl<'a> Resolver<'a> { if parent_package_json.dependencies.map.count() > 0 || parent_package_json.package_manager_package_id != Install::INVALID_PACKAGE_ID { - // PORT NOTE: store the raw `NonNull` field (not the + // NOTE: store the raw `NonNull` field (not the // `&'static` accessor result) so mut-provenance flows // through to `enqueue_dependency_to_resolve`. info.package_json_for_dependencies = parent_.package_json; @@ -6224,7 +6282,7 @@ impl<'a> Resolver<'a> { } else if !parent_.abs_real_path.is_empty() { // this might leak a little i'm not sure let parts = [parent_.abs_real_path, base]; - // PORT NOTE: split into two statements so the two `&mut FileSystem` + // NOTE: split into two statements so the two `&mut FileSystem` // borrows from `unsafe { &mut *self.fs() }` don't overlap (Stacked Borrows). let joined = self .fs_ref() @@ -6312,7 +6370,7 @@ impl<'a> Resolver<'a> { if pkg.dependencies.map.count() > 0 || pkg.package_manager_package_id != Install::INVALID_PACKAGE_ID { - // PORT NOTE: store the raw `NonNull` field (not the + // NOTE: store the raw `NonNull` field (not the // `&'static` accessor result) so mut-provenance flows // through to `enqueue_dependency_to_resolve`. info.package_json_for_dependencies = info.package_json; @@ -6366,7 +6424,7 @@ impl<'a> Resolver<'a> { } } } else if parent.is_none() { - // PORT NOTE: re-borrow as 'static so the `&self.opts` borrow ends before + // NOTE: re-borrow as 'static so the `&self.opts` borrow ends before // `self.parse_tsconfig(&mut self, ...)`. `tsconfig_override` is owned by // BundleOptions (lives for the resolver's lifetime). // SAFETY: `tsconfig_override` is owned by `self.opts` (resolver-lifetime); @@ -6417,7 +6475,7 @@ impl<'a> Resolver<'a> { None } }; - // PORT NOTE: spec resolver.zig:4207 assigns info.tsconfig_json here (a raw + // NOTE: spec resolver.zig:4207 assigns info.tsconfig_json here (a raw // ?*TSConfigJSON), then frees that allocation in the merge loop below before // reassigning. With Rust references (Option<&'static TSConfigJSON>, dir_info.rs) // that briefly-dangling state is UB. Defer the assignment to after the merge — @@ -6537,7 +6595,7 @@ impl<'a> Resolver<'a> { impl<'a> Resolver<'a> { /// Port of `pub fn deinit(r: *ThisResolver)` (resolver.zig:601-604). /// - /// PORT NOTE: NOT `impl Drop` — the bundler builds a `Resolver` per worker + /// NOTE: NOT `impl Drop` — the bundler builds a `Resolver` per worker /// thread (see `for_worker`), and all instances share the same `dir_cache` /// singleton. A `Drop` impl would fire once per worker going out of scope, /// resetting the SHARED cache (freeing PackageJSON/TSConfigJSON, closing cached @@ -6551,8 +6609,11 @@ impl<'a> Resolver<'a> { // deref intrusive refcounts). Ported as `DirInfo::reset`. di.reset(); } - // dir_cache is &'static — do not deinit the singleton here - // TODO(port): Zig calls dir_cache.deinit() but it's a global BSSMap; revisit ownership + // dir_cache is &'static — do not deinit the singleton here. Zig calls + // `dir_cache.deinit()`, but its `dir_cache` is the same process-global + // BSSMap singleton (`DirInfo.HashMap` / `hash_map_instance()`); the + // entries' owned resources are released by the `reset()` loop above and + // the map storage itself lives for the process. } } @@ -6710,10 +6771,6 @@ fn module_type_from_ext(ext: &[u8]) -> Option { const NODE_MODULE_ROOT_STRING: &[u8] = const_format::concatcp!(SEP_STR, "node_modules", SEP_STR).as_bytes(); -// `dev` scope (Output.scoped(.Resolver, .visible)) — same scope name as `debuglog` but visible. -// Folded into the same `Resolver` declared scope; TODO(port): restore the visible/hidden -// scope distinction. - pub struct Dirname; impl Dirname { diff --git a/src/resolver/result.rs b/src/resolver/result.rs index 85095a5ec1c9..510177a1eb40 100644 --- a/src/resolver/result.rs +++ b/src/resolver/result.rs @@ -21,13 +21,17 @@ use crate::options; use crate::package_json::PackageJSON; use crate::resolver::Dependency; -// PORT NOTE: `Path` in the body is the `'static`-interned variant (paths borrow +// NOTE: `Path` in the body is the `'static`-interned variant (paths borrow // DirnameStore/FilenameStore). Alias here so the bare-`Path` use sites resolve // without a per-site lifetime annotation. type Path = crate::fs::Path<'static>; pub struct SideEffectsData { - pub source: Option>, // TODO(port): lifetime — never instantiated + // Zig `source: *logger.Source` (resolver.zig:29-30, non-optional). Modeled as + // `Option` here as a deliberate loosening: no constructor ever + // populates the field in either language, so `None` stands in for the + // never-written pointer until lifetime modeling is actually needed. + pub source: Option>, pub range: bun_ast::Range, // If true, "sideEffects" was an array. If false, "sideEffects" was false. @@ -339,7 +343,8 @@ impl DebugMeta { r: bun_ast::Range, args: core::fmt::Arguments<'_>, ) -> core::result::Result<(), bun_core::Error> { - // TODO(port): narrow error set + // Zig declared a narrow inferred error set here; the port uses the + // broad `bun_core::Error` per repo-wide convention. if source.is_some() && !self.suggestion_message.is_empty() { let suggestion_range = if self.suggestion_range == SuggestionRange::End { bun_ast::Range { @@ -352,7 +357,7 @@ impl DebugMeta { r }; let data = bun_ast::range_data(source, suggestion_range, self.suggestion_message); - // PORT NOTE: Zig spec writes `data.location.?.suggestion = m.suggestion_text` + // NOTE: Zig spec writes `data.location.?.suggestion = m.suggestion_text` // here, but `logger.Location` (logger.zig:73) has no `suggestion` field — // `logErrorMsg` is uncalled in the Zig source so the field access is never // type-checked under lazy compilation. Mirror the effective behavior (no-op). @@ -374,7 +379,7 @@ impl DebugMeta { pub struct DirEntryResolveQueueItem { pub result: allocators::Result, - // PORT NOTE: `RawSlice` (not `&'static [u8]`) — these point into the + // NOTE: `RawSlice` (not `&'static [u8]`) — these point into the // threadlocal `dir_info_uncached_path` buffer and are consumed before // `dir_info_cached_maybe_log` returns. `RawSlice` is `repr(transparent)` // over `*const [u8]` so the bit-level zero-init invariant for `Bufs` is @@ -550,7 +555,7 @@ impl Default for PendingResolution { } impl PendingResolution { - // PORT NOTE: deinitListItems → Drop on MultiArrayList + // NOTE: deinitListItems → Drop on MultiArrayList // (Zig body only freed `dependency` + `string_buf` per item; both are owned fields with Drop.) // deinit → Drop (frees dependency + string_buf; both have Drop) @@ -560,7 +565,7 @@ impl PendingResolution { dependency: Dependency::Version, resolution_id: Install::PackageID, ) -> core::result::Result { - // PORT NOTE: Zig body called `try esm.copy(allocator)` and left `string_buf` + // NOTE: Zig body called `try esm.copy(allocator)` and left `string_buf` // / `tag` defaulted; that fn was never compiled (Zig lazy-analyzes unreferenced // fns). `Package::copy` is the count→allocate→clone Builder dance the live // call sites open-code, so thread the freshly-allocated buffer into @@ -584,7 +589,9 @@ pub enum PendingResolutionTag { } pub struct LoadResult { - pub path: &'static [u8], // TODO(port): lifetime — interned in dirname_store + /// Interned in `DirnameStore`/`FilenameStore` (process-lifetime singletons), + /// so the `'static` borrow is genuine. + pub path: &'static [u8], pub diff_case: Option>, pub dirname_fd: FD, pub file_fd: FD, diff --git a/src/resolver/tsconfig_json.rs b/src/resolver/tsconfig_json.rs index 30c6535c4b99..6029d3ee8fc3 100644 --- a/src/resolver/tsconfig_json.rs +++ b/src/resolver/tsconfig_json.rs @@ -61,7 +61,7 @@ impl JsonCache { ) -> Result, bun_core::Error> { let mut temp_log = bun_ast::Log::init(); let bump = self.bump.get_or_insert_with(bun_alloc::Arena::new); - // PORT NOTE: reshaped for borrowck — Zig `defer temp_log.appendToMaybeRecycled(log, source) catch {}` + // NOTE: reshaped for borrowck — Zig `defer temp_log.appendToMaybeRecycled(log, source) catch {}` // runs after the `func() catch null` body; here the append is hoisted past the match. let result = func(source, &mut temp_log, bump).ok(); let _ = temp_log.append_to_maybe_recycled(log, source); @@ -122,7 +122,8 @@ impl JsonCache { // Heuristic: you probably don't have 100 of these // Probably like 5-10 // Array iteration is faster and deterministically ordered in that case. -// TODO(port): bun.StringArrayHashMap — confirm bun_collections key/value ownership for byte-slice keys +// Zig `bun.StringArrayHashMap` borrows arena-owned `[]const u8` keys; here both +// keys and values are owned (`Box`/`Vec`) and freed when the map drops. pub(crate) type PathsMap = ArrayHashMap, Vec>>; // Zig: `fn FlagSet(comptime Type: type) type { return std.EnumSet(std.meta.FieldEnum(Type)); }` @@ -139,8 +140,8 @@ pub(crate) enum JsxField { pub(crate) type JsxFieldSet = EnumSet; pub struct TSConfigJSON { - // TODO(port): lifetime — Zig never frees these string fields (resolver-lifetime arena); - // modeled here as owned Box<[u8]>. Revisit if profiling shows churn. + // Zig never frees these string fields (resolver-lifetime arena); modeled + // here as owned Box<[u8]>. pub abs_path: Box<[u8]>, /// The absolute path of "compilerOptions.baseUrl" @@ -317,7 +318,7 @@ impl TSConfigJSON { Ok(Box::from(&written[..len])) } - // PORT NOTE: Zig `Expr.asString(allocator)` allocates and never frees (the + // NOTE: Zig `Expr.asString(allocator)` allocates and never frees (the // resolver owns the JSON AST for its lifetime). The live Rust `Expr` query // API exposes `as_utf8_string_literal() -> Option<&[u8]>` instead — the // tsconfig parser forces UTF-8 (cache.zig:313 `force_utf8=true`), so every @@ -590,7 +591,7 @@ impl TSConfigJSON { // Parse "paths" if let Some(paths_prop) = paths_v { if let bun_ast::ExprData::EObject(paths) = &paths_prop.data { - // PORT NOTE: Zig `defer { Features.tsconfig_paths += 1 }` hoisted to top of block; + // NOTE: Zig `defer { Features.tsconfig_paths += 1 }` hoisted to top of block; // it runs on every exit path either way. bun_analytics::features::tsconfig_paths .fetch_add(1, core::sync::atomic::Ordering::Relaxed); @@ -736,7 +737,6 @@ impl TSConfigJSON { loc: bun_ast::Loc, text: &[u8], ) -> Result]>, bun_core::Error> { - // TODO(port): narrow error set if text.is_empty() { return Ok(Box::default()); } diff --git a/src/router/lib.rs b/src/router/lib.rs index 3f2969d695a3..dfcc238ea39b 100644 --- a/src/router/lib.rs +++ b/src/router/lib.rs @@ -185,10 +185,10 @@ impl RouteConfig { } // const index_route_hash = @truncate(bun.hash("$$/index-route$$-!(@*@#&*%-901823098123")) -// TODO(port): make this a true const once bun_wyhash::hash is const fn -fn index_route_hash() -> u32 { - wyhash(b"$$/index-route$$-!(@*@#&*%-901823098123") as u32 -} +// `hash_const` is byte-identical to the runtime `bun_wyhash::hash` (seed 0); +// `tests::hash_const_matches_runtime` in `bun_wyhash` guards drift. +const INDEX_ROUTE_HASH: u32 = + bun_wyhash::hash_const(0, b"$$/index-route$$-!(@*@#&*%-901823098123") as u32; // Param/List are lifetime-parameterized: `name` borrows the route name // (DirnameStore-backed) and `value` borrows the *request URL buffer*. The Zig @@ -234,7 +234,7 @@ impl<'a> Router<'a> { } pub fn route_index_by_hash(&self, hash: u32) -> Option { - if hash == index_route_hash() { + if hash == INDEX_ROUTE_HASH { return self.routes.index_id; } @@ -503,7 +503,7 @@ impl Routes { path: index.abs_path.as_bytes(), pathname: url_path.pathname, basename: index.basename, - hash: index_route_hash(), + hash: INDEX_ROUTE_HASH, file_path: index.abs_path.as_bytes(), query_string: url_path.query_string, client_framework_enabled: self.client_framework_enabled, @@ -514,7 +514,7 @@ impl Routes { return None; } - // PORT NOTE: Zig moved params into a local MatchContextType then back via + // NOTE: Zig moved params into a local MatchContextType then back via // defer; in Rust a plain reborrow suffices. if let Some(route_ptr) = self.match_(path, params) { // SAFETY: pointers from static_/dynamic alias Box stored in @@ -596,7 +596,7 @@ struct RouteLoader<'a> { dedupe_dynamic: ArrayHashMap, log: &'a mut bun_ast::Log, - // PORT NOTE: raw NonNull (not &'a Route) because it points into self.all_routes + // NOTE: raw NonNull (not &'a Route) because it points into self.all_routes // (self-referential); `Routes` co-owns it with `list`. index: Option>, static_list: StringHashMap<*const Route>, @@ -611,7 +611,7 @@ impl<'a> RouteLoader<'a> { use bun_collections::hash_map::Entry; // /index.js - if route.full_hash == index_route_hash() { + if route.full_hash == INDEX_ROUTE_HASH { let new_route = Box::new(route); // SAFETY: Box contents have stable address; never removed from all_routes until consumed by load_all self.index = Some(NonNull::from(&*new_route)); @@ -621,7 +621,7 @@ impl<'a> RouteLoader<'a> { // static route if route.param_count == 0 { - // PORT NOTE: Zig getOrPut → std Entry API (StringHashMap = std HashMap). + // NOTE: Zig getOrPut → std Entry API (StringHashMap = std HashMap). if let Some(existing) = self.static_list.get(route.match_name.as_bytes()) { let source = bun_ast::Source::init_empty_file(route.abs_path.as_bytes()); self.log.add_error_fmt( @@ -755,7 +755,7 @@ impl<'a> RouteLoader<'a> { dynamic_start = Some(i); } - if route.full_hash == index_route_hash() { + if route.full_hash == INDEX_ROUTE_HASH { index_id = Some(i); } @@ -812,7 +812,7 @@ impl<'a> RouteLoader<'a> { if let Some(entries) = root_dir_info.get_entries_const() { let iter = entries.iter(); 'outer: for entry_ptr in iter { - // PORT NOTE: `iter()` yields raw `*mut Entry` (matching Zig's + // NOTE: `iter()` yields raw `*mut Entry` (matching Zig's // `*Entry` map value type, fs.zig:117). Reborrow locally for // each access so `&` reads and the `&mut` `kind()` call do not // overlap. Single iterator active for this scan; serialized via @@ -1010,7 +1010,7 @@ impl Route { /// # Safety /// `entry` must point to a live `Fs::Entry` (EntryStore-owned) with no /// other active `&mut` borrow for the duration of the call. `base_` and - /// `extname` may borrow `(*entry).base_`; see the PORT NOTE below. + /// `extname` may borrow `(*entry).base_`; see the NOTE below. pub unsafe fn parse( base_: &[u8], extname: &[u8], @@ -1019,7 +1019,7 @@ impl Route { public_dir_: &[u8], routes_dirname_len: u16, ) -> Option { - // PORT NOTE: `entry` is a raw `*mut Entry` (matching Zig's `*Entry`) + // NOTE: `entry` is a raw `*mut Entry` (matching Zig's `*Entry`) // because `base_`/`extname` may borrow `(*entry).base_` (tiny inline // string, fs.zig:333) and a `&mut Entry` parameter would alias them. // Reads go through `unsafe { &*entry }`; the single mutation @@ -1052,7 +1052,7 @@ impl Route { let mut buf: &mut [u8] = &mut route_file_buf[..]; if !public_dir.is_empty() { - // PORT NOTE: reshaped for borrowck — `buf` already aliases + // NOTE: reshaped for borrowck — `buf` already aliases // route_file_buf[..]; write through it instead of re-borrowing. buf[0] = b'/'; buf = &mut buf[1..]; @@ -1098,7 +1098,7 @@ impl Route { let is_index = name.is_empty(); let mut has_uppercase = false; - // PORT NOTE: reshaped for borrowck — both arms intern via DirnameStore + // NOTE: reshaped for borrowck — both arms intern via DirnameStore // (process-lifetime arena → `&'static`), so the post-if bindings are // 'static and the route_file_buf borrow is dropped before the // abs-path block below needs it mutably. @@ -1118,7 +1118,7 @@ impl Route { let name_offset = name.as_ptr() as usize - public_path.as_ptr() as usize; let name_len = name.len(); - // PORT NOTE: DirnameStore::append returns `&'static [u8]` (process- + // NOTE: DirnameStore::append returns `&'static [u8]` (process- // lifetime arena), so rebinding here drops the borrow on // `route_file_buf` and avoids needing lifetime transmutes // below. @@ -1149,7 +1149,7 @@ impl Route { }; if abs_path_str.is_empty() { - // PORT NOTE: reshaped for borrowck — `defer if (needs_close) file.close()` + // NOTE: reshaped for borrowck — `defer if (needs_close) file.close()` // becomes a scopeguard owning the Option; `needs_close` is a // Cell so the drop closure can read it while the body still mutates. // The guard is inverted: when the fd belongs to the cache @@ -1164,12 +1164,12 @@ impl Route { } }); - // SAFETY: see fn-level PORT NOTE — read-only reborrow. + // SAFETY: see fn-level NOTE — read-only reborrow. if let Some(valid) = unsafe { &*entry }.cache().fd.unwrap_valid() { *file = Some(bun_sys::File::from_fd(valid)); needs_close.set(false); } else { - // SAFETY: see fn-level PORT NOTE — read-only reborrow. + // SAFETY: see fn-level NOTE — read-only reborrow. let entry_r = unsafe { &*entry }; let parts = [entry_r.dir(), entry_r.base()]; let abs_len = FileSystem::instance().abs_buf(&parts, route_file_buf).len(); @@ -1261,7 +1261,7 @@ impl Route { debug_assert!(!strings::index_of_char(unsafe { &*entry }.base(), b'\\').is_some()); } - // PORT NOTE: name/match_name/public_path are already `&'static` via + // NOTE: name/match_name/public_path are already `&'static` via // DirnameStore::append above. `entry.base()` borrows the entry (it // may be inline-stored for ≤31-byte names, fs.zig:333); intern it // explicitly to get `&'static` without a lifetime transmute. @@ -1277,7 +1277,7 @@ impl Route { public_path: Interned::from_static(public_path), match_name: Interned::from_static(match_name), full_hash: if is_index { - index_route_hash() + INDEX_ROUTE_HASH } else { wyhash(name) as u32 }, @@ -1363,7 +1363,7 @@ pub mod sorter { } } -// PORT NOTE: Zig nested `Sorter` under `Route`; Rust has no `impl Route { pub use Sorter }` +// NOTE: Zig nested `Sorter` under `Route`; Rust has no `impl Route { pub use Sorter }` // equivalent, so callers use `crate::sorter` directly. struct RouteBufs { @@ -1398,7 +1398,7 @@ pub struct Match<'a> { pub basename: &'a [u8], pub hash: u32, - // PORT NOTE: raw `*mut` (not `&'a mut`) to match Zig's `*Param.List`. + // NOTE: raw `*mut` (not `&'a mut`) to match Zig's `*Param.List`. // `MatchedRoute` (bun_runtime) stores this self-referentially — a // `&'a mut List` here would be invalidated under Stacked Borrows the // moment any `&mut MatchedRoute` is taken. Callers that need a borrow @@ -1569,7 +1569,7 @@ pub mod pattern { let segment = &path_[0..path_.iter().position(|&b| b == b'/').unwrap_or(path_.len())]; if !str_.eql_bytes(segment) { - params.truncate(0); // TODO(b1): was shrink_retaining_capacity (MultiArrayList API) + params.truncate(0); // shrinkRetainingCapacity(0) return false; } @@ -1592,7 +1592,7 @@ pub mod pattern { path_ = &path_[i + 1..]; if pattern.is_end(name) { - params.truncate(0); // TODO(b1): was shrink_retaining_capacity (MultiArrayList API) + params.truncate(0); // shrinkRetainingCapacity(0) return false; } @@ -1917,7 +1917,6 @@ pub mod pattern { pub kind: Tag, } - // TODO(b1): thiserror not in deps; manual Display/Error impl. #[derive(strum::IntoStaticStr, Debug, Clone, Copy)] pub enum PatternParseError { CatchAllMustBeAtTheEnd, @@ -2047,7 +2046,7 @@ mod tests { } fn make_test(cwd_path: &[u8], data: &[(&str, &str)]) -> Result<(), bun_core::Error> { - // PORT NOTE: Zig used comptime field iteration over an anonymous struct. + // NOTE: Zig used comptime field iteration over an anonymous struct. // Ported as runtime slice of (path, content) pairs. Output::init_test(); debug_assert!(cwd_path.len() > 1 && cwd_path != b"/" && !cwd_path.ends_with(b"bun")); @@ -2066,7 +2065,7 @@ mod tests { for (name, value) in data { let name_b = name.as_bytes(); // if (std.fs.path.dirname(field.name)) |dir| { try cwd.makePath(dir); } - // PORT NOTE: `std.fs.path.dirname` returns null for paths without a + // NOTE: `std.fs.path.dirname` returns null for paths without a // separator; replicate with rposition on '/' (test fixture paths // are always forward-slash). if let Some(slash) = name_b.iter().rposition(|&c| c == b'/') { @@ -2137,7 +2136,7 @@ mod tests { )?; let mut log = bun_ast::Log::init(); - // PORT NOTE: `errdefer logger.print(Output.errorWriter())` — Rust has + // NOTE: `errdefer logger.print(Output.errorWriter())` — Rust has // no errdefer; the test harness panics on error anyway, but the guard // still flushes diagnostics on early-return for parity. let _err_dump = scopeguard::guard(core::ptr::from_mut(&mut log), |log| { @@ -2146,7 +2145,7 @@ mod tests { }); // const opts = Options.BundleOptions{ .target = .browser, ... }; - // PORT NOTE: the resolver-side `BundleOptions` subset omits + // NOTE: the resolver-side `BundleOptions` subset omits // `loaders`/`define`/`log`/`routes`/`entry_points`/`out_extensions`/ // `transform_options` — none are read by `Resolver::init1` or the // dir-info walk, so `Default` + `target` is the faithful projection. @@ -2465,29 +2464,12 @@ mod tests { assert!(run(optional_catch_all) == 0); } - #[test] - #[ignore = "TODO(port): depends on Test::make filesystem fixture harness"] - fn github_api_route_loader() { - // TODO(port): port body once Test::make is implemented; see router.zig:1571-1678 - } - - #[test] - #[ignore = "TODO(port): depends on Test::make filesystem fixture harness"] - fn sample_route_loader() { - // TODO(port): port body once Test::make is implemented; see router.zig:1680-1782 - } - - #[test] - #[ignore = "TODO(port): depends on Test::make filesystem fixture harness"] - fn routes_basic() { - // TODO(port): port body once Test::make is implemented; see router.zig:1784-1832 - } - - #[test] - #[ignore = "TODO(port): depends on Test::make filesystem fixture harness"] - fn dynamic_routes() { - // TODO(port): port body once Test::make is implemented; see router.zig:1834-1868 - } + // The route-loader integration tests from router.zig:1571-1868 ("Github + // API Route Loader", "Sample Route Loader", "Routes Basic", "Dynamic + // Routes") are not ported: they assert through `ctx.matched_route`, which + // `Router::match_` does not yet populate (the JavaScriptHandler enqueue + // path is still commented out), and they consume fixture route lists that + // live in the test-runner harness crate. #[test] fn pattern() { diff --git a/src/runtime/allocators/LinuxMemFdAllocator.rs b/src/runtime/allocators/LinuxMemFdAllocator.rs index d6e90adaed45..6d76fe7164c0 100644 --- a/src/runtime/allocators/LinuxMemFdAllocator.rs +++ b/src/runtime/allocators/LinuxMemFdAllocator.rs @@ -86,9 +86,8 @@ impl LinuxMemFdAllocator { /// outstanding ref. After this call `this` may be dangling; the caller /// must not hold any live `&Self`/`&mut Self` derived from `this`. // - // PORT NOTE: takes `*mut Self` (not `&self`) to mirror Zig's - // `RefCount.deref(self: *Self)`. Taking `&self` and then freeing the - // allocation via `heap::take(self as *const _ as *mut _)` is UB: + // Takes `*mut Self`, not `&self`: taking `&self` and then freeing the + // allocation via `heap::take(self as *const _ as *mut _)` is UB — // it materializes `&mut Self` (via `Drop`) while a shared `&self` // borrow is still live. pub unsafe fn deref(this: *mut Self) { @@ -284,11 +283,10 @@ impl LinuxMemFdAllocator { } } - // PORT NOTE: Zig's `Self.new` returns a raw `*Self` (refcount=1). - // `into_raw()` extracts the `heap::alloc` pointer and transfers - // the +1 to us (RefPtr has no `Drop`); on `Ok` that ref moves into - // `res.allocator`, on `Err` we `deref` it explicitly — exactly the - // Zig flow. + // `Self::new` returns refcount=1; `into_raw()` extracts the + // `heap::alloc` pointer and transfers the +1 to us (RefPtr has no + // `Drop`). On `Ok` that ref moves into `res.allocator`; on `Err` + // we `deref` it explicitly. let memfd: *mut Self = Self::new(fd, bytes.len()).into_raw(); // SAFETY: `memfd` is the `heap::alloc` pointer @@ -344,9 +342,9 @@ mod allocator_interface { // Zig: `var self: *Self = @ptrCast(@alignCast(ptr)); defer self.deref();` // — runs after munmap regardless of result. // - // PORT NOTE: takes the raw vtable data pointer (Zig's `*anyopaque`) - // directly rather than `&self`. `deref` may free the allocation, which - // requires `*mut Self` with full `heap::alloc` provenance; deriving + // Takes the raw vtable data pointer directly rather than `&self`. + // `deref` may free the allocation, which requires + // `*mut Self` with full `heap::alloc` provenance; deriving // it from a `&self` (`as *const _ as *mut _`) would be SharedReadOnly // provenance and the `heap::take` → `Drop` write would be UB under // Stacked Borrows. diff --git a/src/runtime/api.rs b/src/runtime/api.rs index d81b659979e9..c6fbc4576f63 100644 --- a/src/runtime/api.rs +++ b/src/runtime/api.rs @@ -21,8 +21,6 @@ pub use crate::socket::NewSocket; pub use crate::socket::SocketAddress; pub use crate::socket::TCPSocket; pub use crate::socket::TLSSocket; -// PORT NOTE: dropped `comptime { _ = @import("./socket/uws_jsc.zig"); }` force-reference; -// Rust links `us_socket_buffered_js_write` via `pub` export in crate::socket::uws_jsc. pub use crate::socket::udp_socket::UDPSocket; pub use crate::ffi::FFI; diff --git a/src/runtime/api/Archive.rs b/src/runtime/api/Archive.rs index de38a21b15bf..b8a48ccf0117 100644 --- a/src/runtime/api/Archive.rs +++ b/src/runtime/api/Archive.rs @@ -44,8 +44,9 @@ impl Default for GzipOptions { } } -// TODO(port): #[bun_jsc::JsClass] derive — hand-written until the proc-macro -// grows `no_finalize`/`no_construct` knobs Archive needs (custom `finalize`). +// Hand-written JS class glue (not the `#[bun_jsc::JsClass]` derive): Archive +// needs a custom `finalize` and no constructor, which the proc-macro does not +// expose. #[repr(C)] pub struct Archive { /// The underlying data for the archive - uses Blob.Store for thread-safe ref counting @@ -167,7 +168,7 @@ impl Archive { /// - compress: "gzip" - Enable gzip compression /// - level: number (1-12) - Compression level (default 6) /// When no options are provided, no compression is applied - // PORT NOTE: `#[bun_jsc::host_fn]` has no `constructor` kind yet; the + // NOTE: `#[bun_jsc::host_fn]` has no `constructor` kind yet; the // `JsClass` derive emits a `constructor` shim that calls this directly. pub fn constructor(global: &JSGlobalObject, callframe: &CallFrame) -> JsResult> { let [data_arg, options_arg] = callframe.arguments_as_array::<2>(); @@ -949,8 +950,14 @@ impl TaskContext for BlobContext { PromiseResult::Resolve(unsafe { (*blob_ptr).to_js(global) }) } BlobOutputType::Bytes => { - let dup = self.store.shared_view().to_vec(); - // TODO(port): Zig matched OOM here and rejected; Rust Vec aborts on OOM. + // Zig: `dupe(...) catch return .{ .reject = createOutOfMemoryError() }` + // (Archive.zig:643) — reject the promise instead of aborting. + let view = self.store.shared_view(); + let mut dup: Vec = Vec::new(); + if dup.try_reserve_exact(view.len()).is_err() { + return Ok(PromiseResult::Reject(global.create_out_of_memory_error())); + } + dup.extend_from_slice(view); PromiseResult::Resolve(JSValue::create_buffer_from_box( global, dup.into_boxed_slice(), @@ -1181,7 +1188,7 @@ impl FilesContext { // Read error - returned as a normal Result (not a Zig error), so the // errdefer above won't fire. Free the current buffer and all previously // collected entries manually to avoid leaking them. - // PORT NOTE: in Rust both `data` and `entries` drop automatically here. + // NOTE: in Rust both `data` and `entries` drop automatically here. // SAFETY: `archive` is the live `read_new()` handle opened above. return Ok(if let Some(err) = Self::clone_error_string(&archive) { FilesResult::LibarchiveErr(err) @@ -1420,7 +1427,6 @@ fn extract_to_disk_filtered( root: &[u8], glob_patterns: Option<&[Box<[u8]>]>, ) -> Result { - // TODO(port): narrow error set use libarchive::lib; let archive = lib::ReadArchive::new(); configure_archive_reader(&archive); diff --git a/src/runtime/api/BunObject.rs b/src/runtime/api/BunObject.rs index acc8abfb3905..2a4ca3c87a7e 100644 --- a/src/runtime/api/BunObject.rs +++ b/src/runtime/api/BunObject.rs @@ -28,7 +28,7 @@ pub(crate) fn get_public_path_with_asset_prefix( let relative_path: &[u8] = if strings::has_prefix(to, dir) { strings::without_trailing_slash(&to[dir.len()..]) } else { - // PORT NOTE: spec is `VirtualMachine.get().transpiler.fs.relativePlatform(dir, to, platform)`; + // NOTE: spec is `VirtualMachine.get().transpiler.fs.relativePlatform(dir, to, platform)`; // that wrapper is stateless and forwards to bun_paths — dispatch on runtime `platform` // here to keep this fn callable without const-generic plumbing through `transpiler.fs`. match platform { @@ -257,14 +257,15 @@ mod static_adapters { pub mod bun_object { use super::*; - // TODO(port): proc-macro — Zig used `toJSCallback = jsc.toJSHostFn` and - // `toJSLazyPropertyCallback` (comptime fn wrappers) plus comptime `@export` - // to emit each callback under `BunObject_callback_` / - // `BunObject_lazyPropCb_`. In Rust, the `#[bun_jsc::host_fn]` - // attribute on the underlying fn emits the JSC-ABI shim; the export name - // is set with `#[unsafe(no_mangle)]` on the shim. The two `macro_rules!` - // below expand the static export tables; verify the shim ABI matches - // `LazyPropertyCallback` for the property variants. + // Zig used `toJSCallback = jsc.toJSHostFn` / `toJSLazyPropertyCallback` + // (comptime fn wrappers) plus comptime `@export` to emit each callback + // under `BunObject_callback_` / `BunObject_lazyPropCb_`. The + // two `macro_rules!` below expand the equivalent static export tables. + // ABI check vs the C++ declarations (BunObject+exports.h:90): + // `extern "C" EncodedJSValue SYSV_ABI (JSGlobalObject*, JSObject*)` for the + // property variants — matched here by `jsc_host_abi!` (`extern "sysv64"` + // on Windows-x64, `extern "C"` elsewhere) returning `JSValue`, which is + // `#[repr(transparent)]` over `EncodedJSValue`. // Ident concat via `${concat()}` is unstable (`macro_metavar_expr_concat`), // so the full `BunObject_callback_` / `BunObject_lazyPropCb_` @@ -429,7 +430,7 @@ pub mod bun_object { pub use super::set_main; // --- Setters --- - // PORT NOTE: Zig's `lazyPropertyCallbackName`/`callbackName` were comptime + // NOTE: Zig's `lazyPropertyCallbackName`/`callbackName` were comptime // string concats used only at `comptime @export` sites. The export names // are now spelled out verbatim in the `export_*!` macro invocations above, // so the comptime-only helpers are dropped (no runtime callers). @@ -517,7 +518,7 @@ pub(crate) fn braces( let expansion_count = Braces::calculate_expanded_amount(&lexer_output.tokens[..]); if opts.tokenize { - // PORT NOTE: Zig's `std.json.fmt` reflects over the tagged-union token + // NOTE: Zig's `std.json.fmt` reflects over the tagged-union token // list. The Rust `Braces::Token` enum has no `serde::Serialize`; emit // the same shape (`[{"": |{}} , …]`) by hand so the // debug-only `Bun.braces(str, {tokenize:true})` round-trips. @@ -531,7 +532,7 @@ pub(crate) fn braces( Ok(v) => v, Err(err) => return Err(global.throw_error(err.into(), "failed to parse braces")), }; - // PORT NOTE: see `tokenize` arm — manual JSON encoder for the AST. + // NOTE: see `tokenize` arm — manual JSON encoder for the AST. let str = Braces::ast_to_json(&ast_node); let bun_str = BunString::from_bytes(&str); return bun_str.to_js(global); @@ -652,7 +653,7 @@ pub(crate) fn inspect_table( return BunString::empty().to_js(global_this); } - // PORT NOTE: protect/unprotect over a copied [JSValue; 5]; the borrow of + // NOTE: protect/unprotect over a copied [JSValue; 5]; the borrow of // `all_arguments` cannot escape into a guard closure, so copy out into an // array of RAII guards. let _prot: [bun_jsc::ProtectedJSValue; 5] = @@ -995,7 +996,7 @@ pub(crate) fn get_argv(global_this: &JSGlobalObject, _: &JSObject) -> JSValue { node::process::get_argv(global_this) } -// PORT NOTE (layering): `RareData.editor_context` in `bun_jsc` is an opaque ZST +// NOTE (layering): `RareData.editor_context` in `bun_jsc` is an opaque ZST // stub — the real `EditorContext` lives in this crate (`cli::open`) and depends // on `bun_dotenv` / `bun_spawn`, so it can't move down without dragging those // into `bun_jsc`'s graph. Zig stored it on `rareData()`; semantically it is @@ -1053,7 +1054,7 @@ pub(crate) fn open_in_editor( if !strings::eql_long(prev_name, sliced.slice(), true) { let prev = core::mem::take(edit); - // PORT NOTE: Zig stashed the arena-backed slice + // NOTE: Zig stashed the arena-backed slice // directly into the persistent EditorContext (latent // UAF in spec). Own the bytes in `name_storage` and // hand back a thread-lifetime borrow. @@ -1599,7 +1600,7 @@ pub(crate) fn serve(global_object: &JSGlobalObject, callframe: &CallFrame) -> Js // SAFETY: same VM pointer; re-borrow after `args` is dropped. let vm = global_object.bun_vm().as_mut(); - // PORT NOTE (layering): Zig's `HotMap` is a `TaggedPtrUnion` over the four + // NOTE (layering): Zig's `HotMap` is a `TaggedPtrUnion` over the four // `NewServer` monomorphizations + sockets. `bun_jsc::rare_data::HotMapEntry` // is the erased `(tag: u8, ptr: *mut ())` lowering of that union; the tag // values for servers are pinned here to match `crate::server::AnyServerTag` @@ -1653,7 +1654,7 @@ pub(crate) fn serve(global_object: &JSGlobalObject, callframe: &CallFrame) -> Js } let obj = <$ServerType>::ptr_to_js(server, global_object); if route_list_object != JSValue::ZERO { - // PORT NOTE: `ServerType.js.routeListSetCached` (codegen + // NOTE: `ServerType.js.routeListSetCached` (codegen // `.classes.ts`) — routed through the typed helper in // `server_body` until per-type codegen externs land. <$ServerType>::js_gc_route_list_set(obj, global_object, route_list_object); @@ -1693,7 +1694,7 @@ pub(crate) fn serve(global_object: &JSGlobalObject, callframe: &CallFrame) -> Js }}; } - // PORT NOTE: Zig used nested `switch (bool) { inline else => |c| ... }` to + // NOTE: Zig used nested `switch (bool) { inline else => |c| ... }` to // monomorphize over (has_ssl_config, development). Expanded here. let has_ssl_config = config.ssl_config.is_some(); let development = config.is_development(); @@ -1762,7 +1763,7 @@ pub(crate) fn mmap_file(global_this: &JSGlobalObject, callframe: &CallFrame) -> // SAFETY: buf[path_len] == 0 written above let buf_z = bun_core::ZStr::from_buf(&buf[..], path_len); - // PORT NOTE: Zig used `std.c.MAP{ .TYPE = .SHARED }` (a packed bitfield + // NOTE: Zig used `std.c.MAP{ .TYPE = .SHARED }` (a packed bitfield // struct). Rust libc exposes raw `MAP_*` ints; build the flag word // directly. let mut flags: libc::c_int = libc::MAP_SHARED; @@ -1891,7 +1892,7 @@ pub(crate) fn get_s3_client_constructor(global_this: &JSGlobalObject, _: &JSObje } pub(crate) fn get_s3_default_client(global_this: &JSGlobalObject, _: &JSObject) -> JSValue { - // PORT NOTE (layering): `RareData::s3_default_client` body lives in + // NOTE (layering): `RareData::s3_default_client` body lives in // `bun_jsc::rare_data::_accessor_body` and names `bun_runtime::s3` types. // That can't compile in `bun_jsc`, so port the body here where the S3 // types are in scope and store the cached value through the public @@ -1900,14 +1901,14 @@ pub(crate) fn get_s3_default_client(global_this: &JSGlobalObject, _: &JSObject) use bun_jsc::StrongOptional; // SAFETY: bun_vm() returns the live thread-local VM for a Bun-owned global. let vm = global_this.bun_vm().as_mut(); - // PORT NOTE: reshaped for borrowck — capture the raw env loader pointer + // NOTE: reshaped for borrowck — capture the raw env loader pointer // before `rare_data()` takes the long-lived `&mut` of `vm`. let env_ptr = vm.transpiler.env; let rare = vm.rare_data(); if let Some(v) = rare.s3_default_client.get() { return v; } - // PORT NOTE (layering): `bun_dotenv::Loader::get_s3_credentials` returns the + // NOTE (layering): `bun_dotenv::Loader::get_s3_credentials` returns the // T2 POD mirror; lift it into the refcounted `bun_s3_signing::S3Credentials` // here at the high-tier call site (dotenv ≤T2 may not name s3_signing T5). // SAFETY: `transpiler.env` is the process-lifetime dotenv loader; disjoint @@ -1989,7 +1990,7 @@ pub(crate) fn get_embedded_files(global_this: &JSGlobalObject, _: &JSObject) -> if vm.standalone_module_graph.is_none() { return JSValue::create_empty_array(global_this, 0); } - // PORT NOTE (layering): `VirtualMachine.standalone_module_graph` is + // NOTE (layering): `VirtualMachine.standalone_module_graph` is // type-erased to `&dyn bun_resolver::StandaloneModuleGraph` so `bun_jsc` // doesn't depend on `bun_standalone_graph`. The concrete graph is the // process singleton — `Graph::get()` returns the same instance the trait @@ -2028,7 +2029,7 @@ pub(crate) fn get_embedded_files(global_this: &JSGlobalObject, _: &JSObject) -> }); for (i, index) in sort_indices.iter().enumerate() { let file: &mut GraphFile = &mut unsorted_files[*index as usize]; - // PORT NOTE (layering): `File::blob()` lives in the Zig spec's + // NOTE (layering): `File::blob()` lives in the Zig spec's // `StandaloneModuleGraph.File`; that crate can't depend on // `bun_runtime::webcore::Blob`, so build the blob here from the // file's `cached_blob` slot / contents. @@ -2058,7 +2059,7 @@ fn standalone_file_blob( } let store: StoreRef = Store::init(file.contents.as_bytes().to_vec()); let blob_body = Blob::init_with_store(store, global); - // PORT NOTE (cyclebreak): `MimeType::by_loader` takes the `#[repr(u8)]` + // NOTE (cyclebreak): `MimeType::by_loader` takes the `#[repr(u8)]` // discriminant and an extension; matches Zig spec // `MimeType.byLoader(file.loader, std.fs.path.extension(file.name))`. let mime = @@ -2101,7 +2102,7 @@ impl CSRFObject { pub(crate) fn create(global_this: &JSGlobalObject) -> JSValue { let object = JSValue::create_empty_object(global_this, 2); - // PORT NOTE: `JSFunction::create` takes the raw JSC-ABI host fn pointer, + // NOTE: `JSFunction::create` takes the raw JSC-ABI host fn pointer, // so wrap the safe Rust-style `JsResult` fns via `to_js_host_call`. bun_jsc::jsc_host_abi! { unsafe fn csrf_generate_shim( @@ -2252,7 +2253,7 @@ pub mod environment_variables { // bytes stay alive; if not, they're freed now. *slot.ptr = None; - // PORT NOTE: `Loader.map` is `&'a mut Map` (a mutable reference field); + // NOTE: `Loader.map` is `&'a mut Map` (a mutable reference field); // re-borrow as `&mut *` to avoid moving the reference out of the loader. let env_map = &mut *vm.transpiler.env_mut().map; @@ -2269,7 +2270,7 @@ pub mod environment_variables { let new_val = bun_jsc::rare_data::RefCountedEnvValue::create(value_slice.slice()); let stored = slot.ptr.insert(new_val); // slot.key is a static-lifetime string literal (the struct field name). - // PORT NOTE: Zig's `map.put` stored the slice header without duping, + // NOTE: Zig's `map.put` stored the slice header without duping, // so the slot write had to precede `put` for the map to borrow live // bytes. Rust's `Map::put` boxes its own copy — the Arc wrapper now // only backs `proxy_env_storage` for worker `cloneFrom`; ordering is @@ -2360,13 +2361,13 @@ pub mod JSZlib { } } - // PORT NOTE: Zig's `list.allocatedSlice()` (the full `[0..capacity)` window) + // NOTE: Zig's `list.allocatedSlice()` (the full `[0..capacity)` window) // was previously shimmed here as `&mut [u8]`, but materializing `&mut [u8]` // over uninitialized bytes is UB in Rust regardless of later `set_len`. // Callers now use `Vec::spare_capacity_mut()` (-> `&mut [MaybeUninit]`) // with `compress_into` / `decompress_into`, which is the sound equivalent. - // PORT NOTE: Zig exported `reader_deallocator` / `compressor_deallocator` + // NOTE: Zig exported `reader_deallocator` / `compressor_deallocator` // to free a heap-allocated reader/compressor (and its owned `ArrayList`) // from the ArrayBuffer finalizer. The Rust port keeps the reader on-stack // borrowing a local `Vec`, then leaks only the Vec's allocation into @@ -2532,7 +2533,7 @@ pub mod JSZlib { return Err(global_this .throw_value(ZigString::init(msg).to_error_instance(global_this))); } - // PORT NOTE: Zig moved `list` into the reader and freed via a + // NOTE: Zig moved `list` into the reader and freed via a // dedicated finalizer. In Rust the reader *borrows* `list_ptr`, // so drop the reader to release the borrow, then leak the owned // `list` directly into the ArrayBuffer (freed by @@ -2686,7 +2687,7 @@ pub mod JSZlib { return Err(global_this .throw_value(ZigString::init(msg).to_error_instance(global_this))); } - // PORT NOTE: see gunzip path — reader borrows `list`, so drop + // NOTE: see gunzip path — reader borrows `list`, so drop // it before leaking `list` into the ArrayBuffer. drop(reader); list.shrink_to_fit(); @@ -2812,9 +2813,10 @@ pub mod JSZstd { // Calculate max compressed size let max_size = bun_zstd::compress_bound(input.len()); - let mut output = vec![0u8; max_size]; - // TODO(port): allocator.alloc(u8, n) — Zig left this uninitialized. + // Zig used an uninitialized `allocator.alloc(u8, n)`; the zero-fill + // here is output-irrelevant (zstd overwrites the prefix it reports). // PERF(port): use Box::new_uninit_slice — profile if hot. + let mut output = vec![0u8; max_size]; // Perform compression with context let compressed_size = match bun_zstd::compress(&mut output, input, Some(level)) { @@ -2881,11 +2883,17 @@ pub mod JSZstd { // Compression path // Calculate max compressed size let max_size = bun_zstd::compress_bound(input.len()); - // TODO(port): allocator.alloc(u8, n) — Zig left this uninitialized - // and surfaced OOM as an error. Rust's global allocator aborts on - // OOM, so the explicit "Out of memory" path is unreachable here. - // Could route through a fallible bun_alloc helper. - self.output = vec![0u8; max_size]; + // Zig: `allocator.alloc(u8, max_size) catch { job.error_message = + // "Out of memory"; ... }` (BunObject.zig:1969-1970) — surface OOM + // as a rejected promise instead of aborting. The zero-fill is + // output-irrelevant (zstd overwrites the prefix it reports). + let mut output: Vec = Vec::new(); + if output.try_reserve_exact(max_size).is_err() { + self.error_message = Some(b"Out of memory"); + return; + } + output.resize(max_size, 0); + self.output = output; // Perform compression self.output = match bun_zstd::compress(&mut self.output, input, Some(self.level)) { @@ -3019,14 +3027,14 @@ pub mod JSZstd { // } // }; -// PORT NOTE: Zig `comptime { _ = ...; BunObject.exportAll(); }` block dropped — +// NOTE: Zig `comptime { _ = ...; BunObject.exportAll(); }` block dropped — // Rust links what's pub via the `#[unsafe(no_mangle)]` exports above. // Referenced: Crypto::JSPasswordObject::JSPasswordObject__create, // bun_jsc::btjs::dump_btjs_trace. // LazyProperty initializers for stdin/stderr/stdout // -// PORT NOTE (layering): `RareData.{stdin,stdout,stderr}_store` are typed as +// NOTE (layering): `RareData.{stdin,stdout,stderr}_store` are typed as // `Option>` opaque stubs in `bun_jsc`. The real // `Blob::Store` (intrusively refcounted, with `File` payload) lives in this // crate and can't move down without dragging `node::PathLike`/S3/aio. The Zig @@ -3051,7 +3059,7 @@ mod stdio_stores { Ok(stat) => stat.st_mode as bun_sys::Mode, Err(_) => 0, }; - // PORT NOTE: Zig set `ref_count = 2` to account for the RareData slot + // NOTE: Zig set `ref_count = 2` to account for the RareData slot // plus the Blob; with `StoreRef` (intrusive RAII) the slot is +1 and // the Blob takes its own +1 via `clone()`. let store = Store::new(Store { diff --git a/src/runtime/api/HashObject.rs b/src/runtime/api/HashObject.rs index 1d0ebb5a538d..4d90c4fbc466 100644 --- a/src/runtime/api/HashObject.rs +++ b/src/runtime/api/HashObject.rs @@ -318,12 +318,9 @@ fn hash_wrap(global: &JSGlobalObject, frame: &CallFrame) -> Js } } - // std.hash has inconsistent interfaces - // - // PORT NOTE: the Zig used `@hasDecl`/`ArgsTuple`/`bun.trait.isNumber` to - // pick between `hash` vs `hashWithSeed`, 1-arg vs 2-arg, and seed-first - // vs bytes-first. That dispatch is absorbed into `HashAlgorithm::hash` - // per-impl above; here we always read an optional seed and pass it. + // The per-algorithm hash/hashWithSeed signature differences are absorbed + // into `HashAlgorithm::hash` per-impl above; here we always read an + // optional seed and pass it. let mut seed: u64 = 0; if let Some(arg) = args.next_eat() { if arg.is_number() || arg.is_big_int() { diff --git a/src/runtime/api/JSBundler.rs b/src/runtime/api/JSBundler.rs index 5c9fa5dd73d5..e96c774b2c34 100644 --- a/src/runtime/api/JSBundler.rs +++ b/src/runtime/api/JSBundler.rs @@ -850,7 +850,7 @@ pub mod js_bundler { }); } - // PORT NOTE: `get_if_exists_longest_common_path` wants `&[&[u8]]` + // NOTE: `get_if_exists_longest_common_path` wants `&[&[u8]]` // but `StringSet::keys()` yields `&[Box<[u8]>]`; build a borrow // adapter on the stack. let borrowed: Vec<&[u8]> = entry_points.iter().map(|b| b.as_ref()).collect(); @@ -1201,7 +1201,7 @@ pub mod js_bundler { return Err(global_this.throw_invalid_arguments(format_args!("cannot use compile with an output file named 'bun' because bun won't realize it's a standalone executable. Please choose a different name for compile.outfile"))); } - // PORT NOTE (diverges from Zig spec — flake fix): when no + // NOTE (diverges from Zig spec — flake fix): when no // `outdir`/`outfile` was given, the Zig path stores only // the basename here and `doCompilation` later resolves it // against the process-wide `top_level_dir`. Under the JS @@ -1358,7 +1358,7 @@ pub mod js_bundler { build(global_this, arguments.slice()) } - // PORT NOTE: `Resolve`/`Load`/`MiniImportRecord`/etc. are owned by + // NOTE: `Resolve`/`Load`/`MiniImportRecord`/etc. are owned by // `bun_bundler::bundle_v2::api::JSBundler` so that `BundleV2` can operate // on them directly (`on_resolve_async`/`on_load_async`). `dispatch()` and // `run_on_js_thread()` are also inherent methods there — they only need @@ -1401,7 +1401,6 @@ pub mod js_bundler { unsafe { &mut *bv2_mut(bv2).plugins.unwrap().as_ptr() } } - // TODO(port): move to runtime_sys /// # Safety /// `resolve` must be the live `*mut Resolve` previously handed to C++ via /// `Resolve::dispatch`; sole owner on the JS thread for the call duration. @@ -1519,7 +1518,6 @@ pub mod js_bundler { BundleV2::on_notify_defer_mini(unsafe { &mut *load }, unsafe { &mut *ctx }); } - // TODO(port): move to runtime_sys /// # Safety /// `load` must be the live `*mut Load` previously handed to C++ via /// `Load::dispatch`, and `global` must be the plugin's owning @@ -1533,7 +1531,6 @@ pub mod js_bundler { unsafe { jsc::to_js_host_call(&*global, || (&mut *load).on_defer(&*global)) } } - // TODO(port): move to runtime_sys #[unsafe(no_mangle)] pub(crate) extern "C" fn JSBundlerPlugin__onLoadAsync( this: &mut Load, @@ -1557,8 +1554,10 @@ pub mod js_bundler { )), ); } - // Zig: this.deinit() — explicit drop - // TODO(port): Load is not Box-allocated here; Zig deinit only resets value + // Zig: `this.deinit()` — `Load.deinit` only runs `value.deinit()`, + // which for the just-assigned `no_match` frees nothing and leaves + // `.consumed` behind. Assigning `Consumed` (dropping the trivial + // `NoMatch`) is exactly that. this.value = LoadValue::Consumed; return; } @@ -1800,7 +1799,6 @@ pub mod js_bundler { } } - // TODO(port): move to runtime_sys /// # Safety /// `plugin` must be a live `JSBundlerPlugin` opaque handle. `ctx` must be /// the live `*mut Resolve` (when `which == 0`) or `*mut Load` (when @@ -2004,7 +2002,7 @@ impl BuildArtifact { { formatter.indent_inc(); - // PORT NOTE: reshaped for borrowck — scopeguard cannot reborrow + // NOTE: reshaped for borrowck — scopeguard cannot reborrow // `formatter` while it is also borrowed for the body; decrement // after the block instead. diff --git a/src/runtime/api/JSON5Object.rs b/src/runtime/api/JSON5Object.rs index b85b4da966fa..2174efc87e0c 100644 --- a/src/runtime/api/JSON5Object.rs +++ b/src/runtime/api/JSON5Object.rs @@ -86,7 +86,7 @@ struct Stringifier { builder: wtf::StringBuilder, indent: usize, space: Space, - // PORT NOTE: `JSValue` keys live on the heap here, but every entry is also + // NOTE: `JSValue` keys live on the heap here, but every entry is also // live on the native stack via the `stringify_value` recursion chain, so the // conservative GC scan keeps them alive. Matches the Zig. visiting: HashMap, @@ -137,7 +137,7 @@ impl Space { } } -// PORT NOTE: `Space::deinit` deleted — `BunString` field derefs via `Drop`. +// NOTE: `Space::deinit` deleted — `BunString` field derefs via `Drop`. impl Stringifier { pub(crate) fn init(global: &JSGlobalObject, space_value: JSValue) -> JsResult { @@ -150,7 +150,7 @@ impl Stringifier { }) } - // PORT NOTE: `deinit` deleted — all fields (`builder`, `space`, `visiting`) + // NOTE: `deinit` deleted — all fields (`builder`, `space`, `visiting`) // free via `Drop`. pub(crate) fn stringify_value( @@ -208,15 +208,22 @@ impl Stringifier { return Ok(()); } - // Object or array — check for circular references - // TODO(port): narrow error set — `try_insert`/`get_or_put` OOM maps to JsError::OutOfMemory - let was_present = self.visiting.insert(unwrapped, ()).is_some(); + // Object or array — check for circular references. + // Zig: `try visiting.getOrPut(...)`. The call site is wired for fallible + // allocation (Err → OutOfMemory), but `zig_hash_map`'s grow path currently + // allocates infallibly and aborts on OOM, so the Err arm only becomes live + // once the collections-side grow is made fallible. + let was_present = self + .visiting + .get_or_put(unwrapped) + .map_err(|_| StringifyError::Js(JsError::OutOfMemory))? + .found_existing; if was_present { return Err(global .throw(format_args!("Converting circular structure to JSON5")) .into()); } - // PORT NOTE: reshaped for borrowck — Zig used `defer visiting.remove`; + // NOTE: reshaped for borrowck — Zig used `defer visiting.remove`; // a scopeguard here would hold `&mut self.visiting` across the recursive // `&mut self` calls below, so remove manually after the call instead. let result = if unwrapped.is_array() { @@ -427,7 +434,7 @@ impl Stringifier { } fn estring_to_js(str: &E::EString, global: &JSGlobalObject) -> JsResult { - // PORT NOTE: shim for `EString::to_js(allocator, global)` (lives in + // NOTE: shim for `EString::to_js(allocator, global)` (lives in // `bun_ast::e::String` Zig-side). The JSON5 parser never builds // ropes, so the simple slice → JS path is sufficient. if str.is_utf16 { diff --git a/src/runtime/api/JSTranspiler.rs b/src/runtime/api/JSTranspiler.rs index 4e2de009bd77..658710a23d7e 100644 --- a/src/runtime/api/JSTranspiler.rs +++ b/src/runtime/api/JSTranspiler.rs @@ -47,15 +47,13 @@ pub struct JSTranspiler { pub scan_pass_result: JsCell, pub buffer_writer: JsCell>, pub log_level: bun_ast::Level, - // TODO(port): non-AST crate keeps an arena field for bulk-freeing config strings. - // Consider replacing with per-field Box ownership. - // Boxed so its address is stable across the move into `Box` — + // Arena bulk-frees the config strings (matching the Zig). Boxed so its + // address is stable across the move into `Box` — // `transpiler.arena` holds a `&'static Arena` pointing into it. pub arena: Box, - // Intrusive refcount field for `bun_ptr::IntrusiveRc`. - // TODO(port): LIFETIMES.tsv classifies the consumer (`TransformTask.js_instance`) as - // `Arc`, but `bun.ptr.RefCount` is single-thread intrusive and `*JSTranspiler` - // crosses FFI as `m_ctx`. Reconcile (likely IntrusiveRc, not Arc). + // Intrusive refcount field for `bun_ptr::IntrusiveRc`: + // single-thread intrusive `bun.ptr.RefCount` because `*JSTranspiler` + // crosses FFI as `m_ctx` (per PORTING.md §Pointers; not `Arc`). pub ref_count: bun_ptr::RefCount, } @@ -160,7 +158,7 @@ const PROP_ITER_OPTS: JSPropertyIteratorOptions = JSPropertyIteratorOptions { }; impl Config { - // PORT NOTE: out-param constructor kept as `&mut self` because `self` is a pre-initialized + // NOTE: out-param constructor kept as `&mut self` because `self` is a pre-initialized // field on `JSTranspiler` (in-place mutation), not a fresh value to return. pub fn from_js( &mut self, @@ -563,14 +561,13 @@ impl Config { if iter.len > 0 { bun_core::handle_oom(replacements.ensure_unused_capacity(iter.len)); - // We cannot set the exception before `?` because it could be - // a double free with the errdefer. - // TODO(port): the Zig `defer if (globalThis.hasException()) { free keys; clear }` - // is a conditional cleanup at scope exit. Model with scopeguard - // (captures &mut replacements + &global; borrowck conflict with the loop - // below — needs restructuring). Keys are Box<[u8]> in Rust, so dropping - // `replacements` on `?` already frees them; only the explicit - // `clear_and_free` on the has_exception path is unported. + // Zig's `defer if (globalThis.hasException()) { free keys; clear }` + // is fully covered by RAII here: a pending exception always + // surfaces as `Err(JsError::Thrown)` through `?`, and + // `replacements` is a local (moved into + // `self.runtime.replace_exports` only on success), so the + // early return drops it — freeing the `Box<[u8]>` keys and + // clearing the map exactly as the Zig defer did. while let Some(key_) = iter.next()? { let value = iter.value; @@ -589,7 +586,7 @@ impl Config { } // PERF(port): was getOrPutAssumeCapacity — profile if hot. - // PORT NOTE: reshaped — `StringArrayHashMap::get_or_put` is gated on + // NOTE: reshaped — `StringArrayHashMap::get_or_put` is gated on // `V: Default` upstream and `ReplaceableExport` has no Default. Compute // the value first, then `put` (which upserts without needing a default // slot). The Zig getOrPut left the slot uninitialized on the error path @@ -675,9 +672,8 @@ pub(crate) struct TransformTask<'a> { /// `js_instance`, which is kept alive by the `IntrusiveRc` below for the /// task's lifetime. `ManuallyDrop` prevents double-free; the original owns. pub transpiler: core::mem::ManuallyDrop>, - // TODO(port): LIFETIMES.tsv says Arc — reconcile. JSTranspiler uses - // single-thread intrusive `bun.ptr.RefCount` and crosses FFI as `m_ctx`, so per - // PORTING.md §Pointers this must be IntrusiveRc, not Arc. + // `IntrusiveRc` (not `Arc`): JSTranspiler uses single-thread intrusive + // `bun.ptr.RefCount` and crosses FFI as `m_ctx` (PORTING.md §Pointers). pub js_instance: bun_ptr::IntrusiveRc, pub log: bun_ast::Log, pub err: Option, @@ -864,9 +860,9 @@ impl<'a> TransformTask<'a> { if printed > 0 { buffer_writer = printer.ctx; - // TODO(port): `buffer_writer.buffer.list.items = buffer_writer.written;` — - // Zig truncates the Vec's view to `written`. Map to `.truncate(written.len())` or - // a slice copy depending on BufferWriter shape. + // Zig re-sliced `buffer.list.items = buffer_writer.written` before + // copying; Rust's `written()` reslices via `written_len`, so the + // copied bytes are identical and the local writer is then dropped. self.output_code = BunString::clone_utf8(buffer_writer.written()); } else { self.output_code = BunString::empty(); @@ -874,9 +870,11 @@ impl<'a> TransformTask<'a> { } pub(crate) fn then(&mut self, promise: &mut JSPromise) -> Result<(), bun_jsc::JsTerminated> { - // defer this.deinit() — handled by caller / Drop on Box - // TODO(port): Zig `defer this.deinit()` here destroys self at end of `then`. In Rust, - // ConcurrentPromiseTask should own the Box and drop it after `then` returns. + // Zig `defer this.deinit()`: after `then` returns, the dispatcher + // (`run_then_destroy!` for `task_tag::AsyncTransformTask` in + // runtime/dispatch.rs) unconditionally calls + // `ConcurrentPromiseTask::destroy`, dropping the owned `ctx` + // (this `TransformTask`) and running its `Drop` (transpiler deref etc.). if self.log.has_any() || self.err.is_some() { let error_value: JsResult = 'brk: { @@ -987,12 +985,17 @@ impl JSTranspiler { ) -> JsResult<*mut JSTranspiler> { let arguments = callframe.arguments_old::<3>(); - // PORT NOTE: reshaped — Zig allocates `this` first with `transpiler = undefined` and + // NOTE: reshaped — Zig allocates `this` first with `transpiler = undefined` and // assigns it later. Rust cannot leave a non-POD field uninitialized in a live Box // (zeroed()/assume_init() on Transpiler is UB), so build `config` + `transpiler` on the - // stack first, then move both into the Box. - // TODO(port): in-place init — if the Box must be allocated up-front (e.g. stable - // address for resolver backrefs), switch the field to `MaybeUninit`. + // stack first, then move both into the Box. Nothing observes the + // `Box` address before construction completes. The + // address-sensitive pointers are `transpiler.arena` (points into the + // separately-boxed `arena`, whose address is move-stable) and + // `transpiler.log` (initially points at the stack-local `config.log`; + // it is re-pointed via `set_log` once the Box exists — see the NOTE + // below), so no up-front allocation / `MaybeUninit` in-place init is + // needed. let mut config = Config { log: bun_ast::Log::init(), ..Default::default() @@ -1056,7 +1059,7 @@ impl JSTranspiler { }); // errdefer past this point → `this: Box<_>` drops and runs Drop for JSTranspiler. - // PORT NOTE: reshaped — Zig allocated `this` on the heap FIRST and passed `&this.config.log` + // NOTE: reshaped — Zig allocated `this` on the heap FIRST and passed `&this.config.log` // into `Transpiler::init`, giving a stable address. We built `config` on the stack and // moved it into the Box, so `transpiler.log` (a `*mut Log`) still points at the moved-from // stack slot. Re-point it at the heap-stable field now that the Box exists. @@ -1556,7 +1559,7 @@ impl JSTranspiler { let mut ast_memory_allocator = bun_ast::ASTMemoryAllocator::borrowing(&arena); let _ast_scope = ast_memory_allocator.enter(); - // PORT NOTE: spec snapshots the WHOLE `this.transpiler` by value + // NOTE: spec snapshots the WHOLE `this.transpiler` by value // (`prev_bundler = this.transpiler`) and restores it on exit. `Transpiler` is not // bitwise-copyable in Rust, so explicitly snapshot the fields the body mutates // (`allocator`, `log`, `macro_context`) and restore them via RAII guard. @@ -1641,7 +1644,7 @@ fn named_exports_to_js( } // PERF(port): was stack-fallback allocator — profile if hot. - // PORT NOTE: Zig sorted the map in-place via `StringArrayByIndexSorter` then iterated. + // NOTE: Zig sorted the map in-place via `StringArrayByIndexSorter` then iterated. // `StringArrayHashMap` in Rust has no in-place sort, so collect the keys, sort them // lexicographically (matching `strings.order`), then emit `BunString`s in that order. let mut keys: Vec<&[u8]> = Vec::with_capacity(named_exports.count()); @@ -1796,7 +1799,7 @@ impl JSTranspiler { // the parser borrows it for the arena lifetime (Zig held `*const Define`). let define = &*transpiler.options.define; - // PORT NOTE: spec calls `transpiler.resolver.caches.js.scan`. The + // NOTE: spec calls `transpiler.resolver.caches.js.scan`. The // resolver-side `cache::JavaScript` is a fieldless shell with // no `scan` body; the real `scan` lives on `bun_bundler::cache::JavaScript`. // Both are stateless unit structs, so calling the bundler-crate one diff --git a/src/runtime/api/MarkdownObject.rs b/src/runtime/api/MarkdownObject.rs index 727ff8aae973..3d2bf77433d7 100644 --- a/src/runtime/api/MarkdownObject.rs +++ b/src/runtime/api/MarkdownObject.rs @@ -2,7 +2,7 @@ use bun_core::StackCheck; use bun_jsc::{ArrayBuffer, CallFrame, JSGlobalObject, JSValue, JsResult, MarkedArgumentBuffer}; -// PORT NOTE: Zig's `bun.md` is `src/md/root.zig`; the Rust crate's lib.rs is a +// Note: Zig's `bun.md` is `src/md/root.zig`; the Rust crate's lib.rs is a // thin mod-decl shim, so alias the `root` module (which re-exports BlockType, // SpanType, TextType, SpanDetail, Renderer, helpers, types, ansi, …) as `md`. use crate::node::StringOrBuffer; @@ -218,12 +218,10 @@ fn parse_options(global_this: &JSGlobalObject, opts_value: JSValue) -> JsResult< } } - // Handle remaining boolean options (autolinks/headings are only settable via compound options above) - // TODO(port): comptime reflection over md::Options bool fields — Zig used - // `inline for (@typeInfo(md.Options).@"struct".fields)` to iterate every bool - // field (excluding the six handled above), checking both camelCase and - // snake_case keys. This list could be generated from md::Options - // (proc-macro or hand-maintained const slice in bun_md). + // Handle remaining boolean options (autolinks/headings are only settable via + // compound options above). `md::Options::BOOL_FIELD_SETTERS` is a hand-maintained + // table in bun_md that carries each bool field's snake_case and camelCase names + // plus a setter, replacing Zig's comptime field reflection. for (snake, camel, set) in md::Options::BOOL_FIELD_SETTERS { // skip the compound-only fields if matches!( @@ -249,11 +247,9 @@ fn parse_options(global_this: &JSGlobalObject, opts_value: JSValue) -> JsResult< Ok(options) } -// TODO(port): `camelCaseOf` was a comptime fn producing `&'static [u8]` from a -// snake_case literal. In Rust this should be `const_format::map_ascii_case!` or -// a `macro_rules!` mapper. The only caller is the reflection loop above, which -// is itself TODO'd to use a precomputed table that already carries the camelCase -// form, so this helper is intentionally omitted. +// Zig's `camelCaseOf` comptime helper is intentionally omitted: the option loop +// above uses `md::Options::BOOL_FIELD_SETTERS`, a precomputed table that already +// carries each field's camelCase form. /// `Bun.markdown.render(text, callbacks, options?)` — render markdown with custom callbacks. /// @@ -314,9 +310,9 @@ pub(crate) fn render(global_this: &JSGlobalObject, callframe: &CallFrame) -> JsR /// `Bun.markdown.react(text, components?, options?)` — returns a React Fragment element /// containing the parsed markdown as children. -// TODO(port): Zig used `jsc.MarkedArgumentBuffer.wrap(renderReactImpl)` to generate -// the host-fn shim that allocates a MarkedArgumentBuffer. Here we hand-roll the -// equivalent until bun_jsc provides a `#[marked_args]` attribute. +// Hand-rolled equivalent of Zig's `jsc.MarkedArgumentBuffer.wrap(renderReactImpl)`: +// the closure scopes a MarkedArgumentBuffer around the impl so every JSValue it +// accumulates stays GC-visible for the duration of the call. #[bun_jsc::host_fn] pub(crate) fn render_react( global_this: &JSGlobalObject, @@ -325,7 +321,6 @@ pub(crate) fn render_react( MarkedArgumentBuffer::new(|marked_args| render_react_impl(global_this, callframe, marked_args)) } -// TODO(port): move to _sys unsafe extern "C" { safe fn JSReactElement__createFragment( global_object: &JSGlobalObject, @@ -432,7 +427,7 @@ fn render_ast( struct ParseRenderer<'a> { global_object: &'a JSGlobalObject, marked_args: &'a mut MarkedArgumentBuffer, - // PORT NOTE: JSValue in Vec is safe here — every entry.children is also appended to self.marked_args (GC root). + // Note: JSValue in Vec is safe here — every entry.children is also appended to self.marked_args (GC root). stack: Vec, stack_check: StackCheck, src_text: &'a [u8], @@ -441,7 +436,6 @@ struct ParseRenderer<'a> { react_version: Option, } -// TODO(port): move to _sys unsafe extern "C" { safe fn JSReactElement__create( global_object: &JSGlobalObject, @@ -485,13 +479,13 @@ struct Components { u: JSValue, br: JSValue, } -// PORT NOTE: `Default` for JSValue must be `JSValue::ZERO` (encoded 0), matching Zig's `.zero` initializers. +// Note: `Default` for JSValue must be `JSValue::ZERO` (encoded 0), matching Zig's `.zero` initializers. struct ParseStackEntry { children: JSValue, data: u32, flags: u32, - // PORT NOTE: `SpanDetail` borrows from `src_text`; the `RendererImpl` + // Note: `SpanDetail` borrows from `src_text`; the `RendererImpl` // trait erases that lifetime, so we extend it to `'static` at the // `enter_span` boundary (see SAFETY note there). Entries are popped // and consumed strictly before `src_text` is dropped. @@ -509,7 +503,7 @@ impl Default for ParseStackEntry { } } -// PORT NOTE: Zig used a hand-rolled `*anyopaque + VTable`; the Rust `bun_md` +// Note: Zig used a hand-rolled `*anyopaque + VTable`; the Rust `bun_md` // `Renderer` is `&mut dyn RendererImpl`, so the trait already gives us // `&mut self` — the `*_impl` bodies below are plain methods, no pointer // round-trip needed. @@ -572,7 +566,7 @@ impl<'a> ParseRenderer<'a> { Ok(self_) } - // PORT NOTE: deinit() dropped — Vec and HeadingIdTracker free via Drop. + // Note: deinit() dropped — Vec and HeadingIdTracker free via Drop. /// Extract component overrides from options. Any non-boolean truthy value /// (function, class, string, etc.) keyed by an HTML tag name is stored @@ -718,7 +712,7 @@ impl<'a> ParseRenderer<'a> { let tag_index = get_block_type_tag(block_type, entry.data); // For headings, compute slug before counting props - // PORT NOTE: own the slug bytes — leave_heading() borrows the tracker mutably, + // Note: own the slug bytes — leave_heading() borrows the tracker mutably, // and we need self again below for get_block_component(). Mirrors the Zig // path which allocator-allocated the slug. let slug: Option> = if block_type == md::BlockType::H { @@ -973,7 +967,7 @@ impl<'a> ParseRenderer<'a> { if self.stack.is_empty() { return Ok(()); } - // PORT NOTE: reshaped for borrowck — capture parent.children (Copy JSValue) instead of holding &mut into self.stack. + // Note: reshaped for borrowck — capture parent.children (Copy JSValue) instead of holding &mut into self.stack. let parent_children = self.stack.last().unwrap().children; match text_type { @@ -1024,7 +1018,7 @@ impl<'a> ParseRenderer<'a> { /// callback's return value to the parent buffer. struct JsCallbackRenderer<'a> { global_object: &'a JSGlobalObject, - // PORT NOTE: #allocator field dropped — global mimalloc. + // Note: #allocator field dropped — global mimalloc. src_text: &'a [u8], stack: Vec, callbacks: Callbacks, @@ -1056,7 +1050,7 @@ struct Callbacks { strikethrough: JSValue, text: JSValue, } -// PORT NOTE: `Default` for JSValue must be `JSValue::ZERO`. +// Note: `Default` for JSValue must be `JSValue::ZERO`. struct CallbackStackEntry { buffer: Vec, @@ -1066,7 +1060,7 @@ struct CallbackStackEntry { /// For ul/ol: number of li children seen so far (next li's index). /// For li: this item's 0-based index within its parent list. child_index: u32, - // PORT NOTE: `SpanDetail` borrows from `src_text`; the `RendererImpl` + // Note: `SpanDetail` borrows from `src_text`; the `RendererImpl` // trait erases that lifetime, so we extend it to `'static` at the // `enter_span` boundary (see SAFETY note there). Entries are popped // and consumed strictly before `src_text` is dropped. @@ -1173,7 +1167,7 @@ impl<'a> JsCallbackRenderer<'a> { Ok(()) } - // PORT NOTE: deinit() dropped — Vec (with Vec buffers) and HeadingIdTracker free via Drop. + // Note: deinit() dropped — Vec (with Vec buffers) and HeadingIdTracker free via Drop. fn renderer(&mut self) -> md::Renderer<'_> { md::Renderer { ptr: self } @@ -1283,7 +1277,7 @@ impl<'a> JsCallbackRenderer<'a> { } let callback = self.get_block_callback(block_type); - // PORT NOTE: reshaped for borrowck — clone the saved entry (cheap; buffer not used) instead of holding a borrow across method calls. + // Note: reshaped for borrowck — clone the saved entry (cheap; buffer not used) instead of holding a borrow across method calls. let saved = if self.stack.len() > 1 { CallbackStackEntry { buffer: Vec::new(), @@ -1386,7 +1380,7 @@ impl<'a> JsCallbackRenderer<'a> { let mut buf = [0u8; 8]; let decoded = md::helpers::decode_entity_to_utf8(entity_text, &mut buf).unwrap_or(entity_text); - // PORT NOTE: reshaped for borrowck — copy the (≤8-byte) decoded slice out of `buf` + // Note: reshaped for borrowck — copy the (≤8-byte) decoded slice out of `buf` // before calling &mut self method, to avoid overlapping borrows when the // borrow checker tracks `buf` as borrowed by `decoded`. self.append_text_or_raw(decoded) @@ -1654,7 +1648,6 @@ pub(crate) enum TagIndex { Br = 29, } -// TODO(port): move to _sys // `JSGlobalObject` is `#[repr(C)]` with `UnsafeCell<[u8; 0]>`, so `&JSGlobalObject` // is ABI-identical to a non-null pointer; all other params are value types. unsafe extern "C" { diff --git a/src/runtime/api/NativePromiseContext.rs b/src/runtime/api/NativePromiseContext.rs index b0a73ae7a041..80da00463aef 100644 --- a/src/runtime/api/NativePromiseContext.rs +++ b/src/runtime/api/NativePromiseContext.rs @@ -84,7 +84,7 @@ pub(crate) trait NativePromiseContextType { const TAG: Tag; } -// PORT NOTE (layering): blanket-impl over `ThisServer` so that ANY server +// Layering note: blanket-impl over `ThisServer` so that ANY server // type (mod.rs::NewServer or server_body::NewServer) yields the same Tag — // the tag depends only on (SSL, DBG, H3), never on the server type. This is // the Zig semantics (the Zig Tag enum cases name the (ssl,debug,h3) tuple). @@ -110,7 +110,6 @@ impl NativePromiseContextType for body::ValueBufferer<'_> { const TAG: Tag = Tag::BodyValueBufferer; } -// TODO(port): move to _sys // `&JSGlobalObject` is ABI-identical to a non-null pointer. `ctx` is stored // opaquely (never dereferenced by the C++ side), so the FFI itself has no // pointer-validity precondition — the ref-count contract is documented on diff --git a/src/runtime/api/TOMLObject.rs b/src/runtime/api/TOMLObject.rs index 63bde07cc49e..2936aca1395e 100644 --- a/src/runtime/api/TOMLObject.rs +++ b/src/runtime/api/TOMLObject.rs @@ -33,7 +33,6 @@ pub fn parse(global: &JSGlobalObject, frame: &CallFrame) -> JsResult { // for now... let buffer_writer = js_printer::BufferWriter::init(); let mut writer = js_printer::BufferPrinter::init(buffer_writer); - // PORT NOTE: Zig passed `*js_printer.BufferPrinter` as a comptime type param; dropped per (comptime X: type, arg: X) rule if js_printer::print_json( &mut writer, parse_result, diff --git a/src/runtime/api/UnsafeObject.rs b/src/runtime/api/UnsafeObject.rs index fe134902e606..3deebe34a423 100644 --- a/src/runtime/api/UnsafeObject.rs +++ b/src/runtime/api/UnsafeObject.rs @@ -60,7 +60,6 @@ pub(crate) fn array_buffer_to_string( } } -// TODO(port): move to _sys unsafe extern "C" { safe fn dump_zone_malloc_stats(); safe fn Bun__memoryFootprint() -> usize; @@ -83,12 +82,18 @@ fn memory_footprint(_global: &JSGlobalObject, _frame: &CallFrame) -> JsResult JsResult { - // SAFETY: `bun_vm()` returns a non-null `*mut VirtualMachine` for a Bun-owned global. - let _vm = global.bun_vm(); - // TODO(port): blocked_on: bun_alloc::Arena::dump_stats — `VirtualMachine.arena` is now - // `Option>` and bumpalo has no `dump_stats()`; the original - // mimalloc-arena stat dump needs a dedicated shim once the arena type lands. +fn dump_mimalloc(_global: &JSGlobalObject, _frame: &CallFrame) -> JsResult { + // Zig: `globalObject.bunVM().arena.dumpStats()` — `MimallocArena.dumpStats` + // ignores its receiver and prints the process-wide mimalloc stats to stderr, + // so call `mi_stats_print_out` directly here. + extern "C" fn dump(text: *const core::ffi::c_char, _arg: *mut core::ffi::c_void) { + // SAFETY: mimalloc passes a valid NUL-terminated string. + let text = unsafe { core::ffi::CStr::from_ptr(text) }; + let _ = bun_core::Output::error_writer().write_all(text.to_bytes()); + } + // SAFETY: `dump` matches `mi_output_fun` and does not unwind. + unsafe { bun_alloc::mimalloc::mi_stats_print_out(Some(dump), core::ptr::null_mut()) }; + bun_core::Output::flush(); if bun_alloc::heap_breakdown::ENABLED { dump_zone_malloc_stats(); } diff --git a/src/runtime/api/YAMLObject.rs b/src/runtime/api/YAMLObject.rs index 8cdf66497198..bfe3057f76e1 100644 --- a/src/runtime/api/YAMLObject.rs +++ b/src/runtime/api/YAMLObject.rs @@ -114,10 +114,9 @@ pub(crate) struct AnchorAlias { impl Default for AnchorAlias { fn default() -> Self { - // PORT NOTE: `HashMap::get_or_put` requires `V: Default` to fill the - // freshly-inserted slot before the caller overwrites `*value_ptr`. Zig - // left it `undefined`; this is the closest legal Rust equivalent and is - // immediately overwritten by the caller (see `find_anchors_and_aliases`). + // Exists only because `HashMap::get_or_put` requires `V: Default` to + // fill the freshly-inserted slot; the value is immediately overwritten + // by the caller (see `find_anchors_and_aliases`). AnchorAlias { anchored: false, used: false, @@ -424,7 +423,8 @@ impl Stringifier { return Ok(()); } - // PORT NOTE: reshaped for borrowck — set anchored before newline() + // `anchored` is set before `newline()` (the order is irrelevant to + // output; doing it here releases the `anchor` borrow first). anchor.anchored = true; match self.space { Space::Minified => { diff --git a/src/runtime/api/bun/SSLContextCache.rs b/src/runtime/api/bun/SSLContextCache.rs index fbe1bda339a7..a53da6726292 100644 --- a/src/runtime/api/bun/SSLContextCache.rs +++ b/src/runtime/api/bun/SSLContextCache.rs @@ -23,7 +23,7 @@ use core::ffi::{c_int, c_long, c_void}; use core::ptr; use bun_boringssl_sys as boringssl; -use bun_collections::ArrayHashMap; +use bun_collections::array_hash_map::{ArrayHashContext, ArrayHashMap}; use bun_threading::Mutex; use bun_uws as uws; use bun_uws::create_bun_socket_error_t; @@ -33,8 +33,7 @@ use crate::api::server::server_config::SSLConfig; #[derive(Default)] pub struct SSLContextCache { - // TODO(port): ArrayHashMap needs custom context = DigestContext, store_hash = false - map: ArrayHashMap, + map: ArrayHashMap, mutex: Mutex, ops_since_compact: u32, } @@ -45,17 +44,19 @@ pub type Digest = [u8; 32]; /// bucket hash — no need to re-Wyhash 32 bytes (what AutoContext would do). /// `eql` still compares the full digest. `store_hash = false` since recompute /// is a single load. +#[derive(Default)] pub struct DigestContext; -impl DigestContext { - pub fn hash(&self, k: &Digest) -> u32 { +impl ArrayHashContext for DigestContext { + #[inline] + fn hash(&self, k: &Digest) -> u32 { u32::from_le_bytes([k[0], k[1], k[2], k[3]]) } - pub fn eql(&self, a: &Digest, b: &Digest, _: usize) -> bool { + #[inline] + fn eql(&self, a: &Digest, b: &Digest, _b_index: usize) -> bool { bun_core::strings::eql_long(a, b, false) } } -// TODO(port): wire DigestContext as the ArrayHashMap hasher/eq (Zig: 4th generic param) pub struct Entry { /// Nulled by `bun_ssl_ctx_cache_on_free` when BoringSSL drops the last @@ -270,7 +271,6 @@ impl Drop for SSLContextCache { pub mod c { use core::ffi::c_int; - // TODO(port): move to bun_uws_sys unsafe extern "C" { /// Registered alongside the other usockets ex_data slots in /// `us_ex_idx_init` (pthread_once-guarded). diff --git a/src/runtime/api/bun/SecureContext.rs b/src/runtime/api/bun/SecureContext.rs index deb91602e377..4350b6ca7dcf 100644 --- a/src/runtime/api/bun/SecureContext.rs +++ b/src/runtime/api/bun/SecureContext.rs @@ -53,7 +53,7 @@ pub(crate) fn js_live_count(_global: &JSGlobalObject, _callframe: &CallFrame) -> } impl SecureContext { - // PORT NOTE: no `#[bun_jsc::host_fn]` here — the `Free` shim it emits calls + // Note: no `#[bun_jsc::host_fn]` here — the `Free` shim it emits calls // a bare `constructor(...)` which cannot resolve inside an `impl`. The // `#[bun_jsc::JsClass]` macro already emits the `::constructor` shim. pub fn constructor( @@ -81,7 +81,7 @@ impl SecureContext { /// `SSLContextCache`) on miss. Returning the same cell is what makes /// `secureContext === createSecureContext(opts)` hold and lets `Listener.zig` /// pointer-compare without a JS-side WeakRef map. - // PORT NOTE: codegen (`generated_classes.rs::SecureContextClass__intern`) + // Note: codegen (`generated_classes.rs::SecureContextClass__intern`) // wraps this in `host_fn_result` and exports the C-ABI shim, so no // `#[bun_jsc::host_fn]` here — that macro's Free shim calls by bare name // and cannot resolve an associated fn. @@ -140,7 +140,7 @@ impl SecureContext { d: [u8; 32], ) -> JsResult> { let mut err = uws::create_bun_socket_error_t::none; - // PORT NOTE: spec is `global.bunVM().rareData().sslCtxCache()`. In the + // Note: spec is `global.bunVM().rareData().sslCtxCache()`. In the // Rust crate split, `bun_jsc::RareData::ssl_ctx_cache()` returns an // opaque cycle-break stub; the concrete per-VM `SSLContextCache` lives // on this crate's `RuntimeState` (one per JS thread, same lifetime as @@ -206,7 +206,6 @@ use bun_uws_sys::socket_context::c; mod cpp { use super::*; - // TODO(port): move to runtime_sys unsafe extern "C" { pub(super) safe fn Bun__SecureContextCache__get( global: &JSGlobalObject, diff --git a/src/runtime/api/bun/Terminal.rs b/src/runtime/api/bun/Terminal.rs index 32a4dca94285..d166e182fada 100644 --- a/src/runtime/api/bun/Terminal.rs +++ b/src/runtime/api/bun/Terminal.rs @@ -144,9 +144,9 @@ pub struct Terminal { event_loop_handle: EventLoopHandle, /// Global object reference. Read-only after construction. - // PORT NOTE: LIFETIMES.tsv says JSC_BORROW → `&JSGlobalObject`, but Terminal - // is a heap-allocated `.classes.ts` m_ctx payload and cannot carry a lifetime - // param. Stored as a `BackRef`; deref via `self.global()`. + // Terminal is a heap-allocated `.classes.ts` m_ctx payload and cannot + // carry a lifetime param, so the global is stored as a `BackRef` rather + // than `&JSGlobalObject`; deref via `self.global()`. global_this: bun_ptr::BackRef, /// Writer for sending data to the terminal @@ -697,9 +697,8 @@ impl Terminal { /// before the thread completes. #[cfg(windows)] fn close_pseudoconsole_off_thread(&self, hpcon: windows::HPCON) { - // PORT NOTE: Zig used `std.Thread.spawn(.{}, fn, .{hpcon})` then - // `.detach()`. PORTING.md bans std::process but not std::thread; a raw - // detached OS thread matches the Zig (no event-loop integration needed). + // PORTING.md bans std::process but not std::thread; a raw detached OS + // thread is intentional here (no event-loop integration needed). let hpcon_addr = hpcon as usize; match std::thread::Builder::new().spawn(move || { // SAFETY: hpcon was a valid HPCON when taken; ClosePseudoConsole is @@ -794,9 +793,9 @@ mod lib_util { use super::*; use bun_core::ZStr; - // PORT NOTE: Zig used non-atomic file-level vars (single-threaded init). - // PORTING.md §Global mutable state: bool→AtomicBool; handle slot is an - // AtomicPtr (null ⇔ None — dlopen never yields a null Some). JS-thread-only. + // Per PORTING.md §Global mutable state: the flag is an AtomicBool and the + // handle slot an AtomicPtr (null ⇔ None — dlopen never yields a null Some). + // JS-thread-only. static HANDLE: core::sync::atomic::AtomicPtr = core::sync::atomic::AtomicPtr::new(core::ptr::null_mut()); static LOADED: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false); @@ -834,8 +833,8 @@ fn get_open_pty_fn() -> Option { // On macOS, openpty is in libc, so we can use it directly #[cfg(target_os = "macos")] { - // PORT NOTE: declared locally (not via the `libc` crate) so the - // `OpenPtyFn` type unifies with the Linux dlsym path. + // Declared locally (not via the `libc` crate) so the `OpenPtyFn` + // type unifies with the Linux dlsym path. unsafe extern "C" { // SAFETY precondition: out-param fd pointers must be writable and // termp/winp must be null or valid — raw-pointer contract. Kept @@ -902,9 +901,6 @@ fn create_pty_posix(cols: u16, rows: u16) -> Result { let mut t = termios; // Input flags: standard terminal input processing - // PORT NOTE: Zig used struct-literal flag init on std.posix tc_iflag_t; - // Rust libc termios uses raw tcflag_t bitfields, so these are bit-ORs - // of the libc constants (identical values). t.c_iflag = libc::ICRNL // Map CR to NL on input | libc::IXON // Enable XON/XOFF flow control on output | libc::IXANY // Any character restarts output @@ -954,8 +950,7 @@ fn create_pty_posix(cols: u16, rows: u16) -> Result { t.c_cc[libc::VTIME] = 0; // Timeout for non-canonical read // Set baud rate to 38400 (standard for PTYs) - // PORT NOTE: Zig assigned `.B38400` to ispeed/ospeed enum fields; - // libc termios on Linux encodes speed in c_cflag, so use + // libc termios on Linux encodes speed in c_cflag; use // cfsetispeed/cfsetospeed (the portable way to set both). // SAFETY: `t` is a fully-initialized termios from tcgetattr. unsafe { @@ -1111,10 +1106,9 @@ fn create_pty_windows(cols: u16, rows: u16) -> Result let mut hpcon: Option = None; // errdefer block: scopeguard captures &mut to all of the above. - // PORT NOTE: reshaped for borrowck — using a single closure that reads the - // Option cells at drop-time would require interior mutability. Instead, - // inline the cleanup at each early-return point. This matches the Zig - // errdefer semantics exactly (cleanup runs on every `return Err`). + // Cleanup is inlined at each early-return point (a single drop-time + // closure reading the Option cells would require interior mutability); + // it must run on every `return Err`. macro_rules! cleanup { () => { // SAFETY: every Some(h) is a valid open Win32 handle still owned by @@ -1262,9 +1256,6 @@ impl Terminal { let Some(termios_data) = get_termios(self.master_fd.get()) else { return JSValue::js_number(0.0); }; - // PORT NOTE: Zig used @typeInfo to extract the packed-struct backing - // integer of std.posix tc_*flag_t. In Rust/libc these are already raw - // integers (tcflag_t), so read the field directly. let raw: u64 = match FIELD { TermiosField::Iflag => termios_data.c_iflag as u64, TermiosField::Oflag => termios_data.c_oflag as u64, @@ -1294,8 +1285,6 @@ impl Terminal { let Some(mut termios_data) = get_termios(self.master_fd.get()) else { return Ok(()); }; - // PORT NOTE: Zig computed maxInt of the packed-struct backing integer - // via @typeInfo. tcflag_t::MAX is the same value (platform width). let max_val: f64 = libc::tcflag_t::MAX as f64; let clamped = num.max(0.0).min(max_val); let bits = clamped as libc::tcflag_t; diff --git a/src/runtime/api/bun/h2_frame_parser.rs b/src/runtime/api/bun/h2_frame_parser.rs index 14ce18902081..ffe3db0a1410 100644 --- a/src/runtime/api/bun/h2_frame_parser.rs +++ b/src/runtime/api/bun/h2_frame_parser.rs @@ -110,7 +110,6 @@ enum BunSocket { TcpWriteonly(bun_ptr::BackRef), } -// TODO(port): move to _sys unsafe extern "C" { safe fn JSC__JSGlobalObject__getHTTP2CommonString( global_object: &JSGlobalObject, @@ -307,7 +306,7 @@ impl UInt31WithReserved { fn init(value: u32, reserved: bool) -> Self { Self((value & 0x7fff_ffff) | if reserved { 0x8000_0000 } else { 0 }) } - /// PORT NOTE (intentional divergence): Zig's `toUInt32()` is `@bitCast` of + /// Note (intentional divergence): Zig's `toUInt32()` is `@bitCast` of /// `packed struct(u32){ reserved: bool, uint31: u31 }`, which on little-endian places /// `reserved` in bit 0 and yields `(uint31 << 1) | reserved`. That is a latent RFC 7540 /// §6.3 bug in Zig's deprecated PRIORITY path — the wire format wants the reserved/E @@ -368,7 +367,8 @@ impl StreamPriority { } // packed struct(u72): length: u24, type: u8, flags: u8, streamIdentifier: u32 -// TODO(port): u24 — represented as u32 here; wire encoding handled in write()/from() +// `length` is u24 on the wire; widened to u32 here (Rust has no u24). The 3-byte +// big-endian encoding is handled explicitly in write()/decode(). #[derive(Clone, Copy)] pub struct FrameHeader { length: u32, // u24 on the wire @@ -444,7 +444,9 @@ impl SettingsPayloadUnit { } // packed struct(u336) — 7 × (u16 type + u32 value) = 42 bytes -// TODO(port): #[repr(C, packed)] for wire layout; verify byteSwapAllFields equivalence in Phase B +// Wire layout via #[repr(C, packed)]: all fields are byte-aligned u16/u32, so the +// per-field swap_bytes() in write() is exactly Zig's std.mem.byteSwapAllFields +// (which swaps packed-struct fields individually, not the whole backing int). #[repr(C, packed)] #[derive(Clone, Copy)] pub(crate) struct FullSettingsPayload { @@ -1401,7 +1403,7 @@ impl H2FrameParser { /// mid-loop are not visited, and nothing is yielded twice. That's the /// guarantee the call sites actually rely on (flush / emit-to-all / detach). pub(crate) struct StreamResumableIterator { - // PORT NOTE: Zig's `parser: *H2FrameParser` freely aliases. R-2: `streams` + // Note: Zig's `parser: *H2FrameParser` freely aliases. R-2: `streams` // is now `JsCell`-backed, so a shared backref suffices and the in-loop // body can keep its own `&H2FrameParser` without provenance gymnastics. // `ParentRef` encapsulates the back-pointer invariant (parser outlives the @@ -1494,7 +1496,6 @@ pub(crate) struct SignalRef { // (mirrors Zig `*AbortSignal`). `BackRef` captures the backref invariant // (signal is `ref_()`'d in `attach_signal` and outlives this struct until // `Drop` calls `detach()`/`unref()`), so reads go through safe `Deref`. - // TODO(port): wrap in a dedicated smart-pointer once AbortSignal grows one. signal: bun_ptr::BackRef, // LIFETIMES.tsv: SHARED — H2FrameParser carries an intrusive RefCount and is // recovered via `from_field_ptr!` from the auto-flusher. It uses a hand-rolled @@ -1881,7 +1882,7 @@ impl Stream { ) { let global_this = client.global_this; - // PORT NOTE: `dispatch_write_callback()` below re-enters JS, which can + // Note: `dispatch_write_callback()` below re-enters JS, which can // call back into `H2FrameParser` host-fns (e.g. `writeStream`) that // look this `Stream` up by id from `client.streams` and reach // `queue_frame()` again with a fresh `&mut Stream` aliasing this one. @@ -1900,13 +1901,13 @@ impl Stream { // SAFETY: helper for the pre-dispatch accesses below; `last_frame` // is the unique tail slot in `self.data_frame_queue.data`, valid // until the dispatch call (after which we re-`black_box` before - // every access — see PORT NOTE above). + // every access — see note above). macro_rules! lf { () => { // SAFETY: `last_frame` points at the live tail slot of // `self.data_frame_queue`; provenance is re-laundered via // `black_box` before each post-dispatch expansion so no - // other `&mut` to the slot is live here (see PORT NOTE). + // other `&mut` to the slot is live here (see note). unsafe { &mut *last_frame } }; } @@ -2157,17 +2158,6 @@ impl AbortListener for SignalRef { type HeaderValue = lshpack::DecodeResult; -// PORT NOTE: `lshpack::HpackError` does not yet impl `From` for `bun_core::Error` -// (see TODO in lshpack.rs). Map variants 1:1 to interned error names so Zig -// callers that match on `error.UnableToDecode` etc. keep their semantics. -fn hpack_error_to_core(e: &lshpack::HpackError) -> bun_core::Error { - match e { - lshpack::HpackError::UnableToDecode => bun_core::err!("UnableToDecode"), - lshpack::HpackError::EmptyHeaderName => bun_core::err!("EmptyHeaderName"), - lshpack::HpackError::UnableToEncode => bun_core::err!("UnableToEncode"), - } -} - // ────────────────────────────────────────────────────────────────────────── // H2FrameParser impl — core methods // ────────────────────────────────────────────────────────────────────────── @@ -2184,10 +2174,9 @@ impl H2FrameParser { value: &[u8], never_index: bool, ) -> Result { - // TODO(port): narrow error set let old_len = encoded_headers.len(); let required = old_len + name.len() + value.len() + HPACK_ENTRY_OVERHEAD; - // PORT NOTE: Zig wrote into `allocatedSlice()` past `.len` then bumped `.len` on + // Note: Zig wrote into `allocatedSlice()` past `.len` then bumped `.len` on // success. In Rust, materializing `&mut [u8]` over uninitialized capacity is UB and // hpack.encode() needs `&mut [u8]` (not `&mut [MaybeUninit]`), so zero-extend to // `required` first. On both Ok and Err we truncate so `len` never exposes scratch @@ -2214,9 +2203,7 @@ impl H2FrameParser { pub(crate) fn decode(&self, src_buffer: &[u8]) -> Result { self.hpack.with_mut(|hpack| { if let Some(hpack) = hpack.as_mut() { - return hpack - .decode(src_buffer) - .map_err(|e| hpack_error_to_core(&e)); + return hpack.decode(src_buffer).map_err(bun_core::Error::from); } Err(bun_core::err!("UnableToDecode")) }) @@ -2235,7 +2222,7 @@ impl H2FrameParser { // lets make sure the name is lowercase return hpack .encode(name, value, never_index, dst_buffer, dst_offset) - .map_err(|e| hpack_error_to_core(&e)); + .map_err(bun_core::Error::from); } Err(bun_core::err!("UnableToEncode")) }) @@ -2287,7 +2274,7 @@ impl H2FrameParser { } fn increment_window_size_if_needed(&self) { - // PORT NOTE: reshaped for borrowck — collect actions then apply + // Note: reshaped for borrowck — collect actions then apply let mut updates: Vec<(u32, u64)> = Vec::new(); for (_, item) in self.streams.get().iter() { // SAFETY: item is &*mut Stream from streams.iter(); the boxed Stream outlives the iteration @@ -3112,7 +3099,7 @@ impl H2FrameParser { } } -// PORT NOTE: raw-ptr slice — Zig's `[]const u8` payload may alias `this.readBuffer` across +// Note: raw-ptr slice — Zig's `[]const u8` payload may alias `this.readBuffer` across // `readBuffer.reset()` (e.g. handleHeadersFrame resets then calls decodeHeaderBlock(payload)). // A borrowed `&'a [u8]` tied to `&'a mut self` forces every caller into an aliasing // `unsafe { &mut *self_ptr }` reborrow, which under Stacked Borrows invalidates the slice the @@ -3986,7 +3973,7 @@ impl H2FrameParser { let payload = content.data(); let is_not_ack = frame.flags & PingFrameFlags::ACK as u8 == 0; let end = content.end; - // PORT NOTE: Zig resets readBuffer before send_ping(payload); reset() only clears len + // Note: Zig resets readBuffer before send_ping(payload); reset() only clears len // so the bytes stay readable. Copy out anyway so send_ping/to_js below don't depend on // that subtlety once read_buffer is mutated further. let payload_owned = payload.to_vec(); @@ -4789,7 +4776,7 @@ impl H2FrameParser { ) } - // PORT NOTE: hoisted from three identical switch blocks in read_bytes for borrowck/DRY. + // Note: hoisted from three identical switch blocks in read_bytes for borrowck/DRY. // The `add` parameter is the number of bytes already consumed before `bytes` (0, `needed`, or BYTE_SIZE). fn dispatch_frame( &self, @@ -4869,7 +4856,7 @@ impl H2FrameParser { } } -// PORT NOTE: holds a `BackRef` so the borrow of the parser ends +// Note: holds a `BackRef` so the borrow of the parser ends // at `to_writer()`'s return — `Stream::flush_queue` interleaves field // reads/writes on the parser between `writer.write()` calls. R-2: `write()` // takes `&self` (Cell/JsCell-backed), so a shared back-reference is sufficient @@ -6717,7 +6704,7 @@ impl H2FrameParser { let mut single_value_headers = [false; SINGLE_VALUE_HEADERS_LEN]; for ignore_pseudo_headers in 0..2usize { - // PORT NOTE: `bun_jsc::JSPropertyIterator` (runtime-options variant) lacks `.reset()`; + // Note: `bun_jsc::JSPropertyIterator` (runtime-options variant) lacks `.reset()`; // re-initialize per pass instead — same observable property walk as the Zig two-pass loop. let mut iter = bun_jsc::JSPropertyIterator::init( global_object, @@ -7716,7 +7703,7 @@ impl H2FrameParser { .strong_this .with_mut(|s| s.set_strong(this_value, global_object)); - // PORT NOTE: `HPACK::init` returns a C-allocated wrapper that must be + // Note: `HPACK::init` returns a C-allocated wrapper that must be // torn down via `lshpack_wrapper_deinit` (runs `lshpack_{enc,dec}_cleanup` // before freeing). Wrapping it in `heap::take` and letting `Box` drop // would `mi_free` the struct but leak the encoder/decoder internals. @@ -7778,9 +7765,9 @@ impl H2FrameParser { bun_output::scoped_log!(H2FrameParser, "deinit"); self.detach(); - // PORT NOTE: JsRef::deinit() dropped — overwrite with empty(); Drop releases the Strong slot. + // Note: JsRef::deinit() dropped — overwrite with empty(); Drop releases the Strong slot. self.strong_this.set(JsRef::empty()); - // PORT NOTE: take the map out first so `self` is free for + // Note: take the map out first so `self` is free for // `free_resources(self)` while we walk the entries. let streams = self.streams.replace(BunHashMap::default()); for (_, item) in streams.iter() { @@ -7823,7 +7810,7 @@ impl H2FrameParser { pub(crate) fn finalize(self: Box) { bun_output::scoped_log!(H2FrameParser, "finalize"); - // PORT NOTE: JsRef::deinit() dropped — overwrite with empty(); Drop releases the Strong slot. + // Note: JsRef::deinit() dropped — overwrite with empty(); Drop releases the Strong slot. bun_ptr::finalize_js_box(self, |this| { this.strong_this.set(JsRef::empty()); // process.exit() never unwinds, so a stack-rooted ref can strand and deinit() diff --git a/src/runtime/api/bun/js_bun_spawn_bindings.rs b/src/runtime/api/bun/js_bun_spawn_bindings.rs index d2b7637997e5..852156792fbe 100644 --- a/src/runtime/api/bun/js_bun_spawn_bindings.rs +++ b/src/runtime/api/bun/js_bun_spawn_bindings.rs @@ -131,7 +131,6 @@ type SpawnOptionsStdio = spawn::PosixStdio; #[cfg(windows)] type SpawnOptionsStdio = spawn::WindowsStdio; -// TODO(port): move to runtime_sys // Reading the symbol address has no precondition (the value itself is a // rodata `const char*`); kept `safe` to match the identical declaration in // `runtime/shell/subproc.rs` so the two extern blocks don't diverge. @@ -432,7 +431,7 @@ pub(crate) fn spawn_maybe_sync( } }, ); - // PORT NOTE: reshaped for borrowck — re-borrow through the guard tuple. + // Note: reshaped for borrowck — re-borrow through the guard tuple. let (abort_signal, terminal_info) = &mut *defer_guard; // Owned ZBox for `cwd` held here so the `&[u8]` borrow stays valid until @@ -706,7 +705,7 @@ pub(crate) fn spawn_maybe_sync( i32::try_from((timeout_int as u32) & 0x7FFF_FFFF) .expect("int cast"), ); - // PORT NOTE: Zig `@intCast(@as(u31, @truncate(timeout_int)))` — truncate to u31 then widen to i32. + // Note: Zig `@intCast(@as(u31, @truncate(timeout_int)))` — truncate to u31 then widen to i32. } } } @@ -863,7 +862,7 @@ pub(crate) fn spawn_maybe_sync( Ok(m) => m, Err(_) => return Err(global_this.throw_out_of_memory()), }; - // PORT NOTE: Zig assigned `env_array.items = envp` (sentinel slice — the + // Note: Zig assigned `env_array.items = envp` (sentinel slice — the // trailing `null` lives at `[len]`, outside `.items`). The Rust port's // `as_slice()` *includes* the trailing null, so strip it; the common // tail below re-appends one after the optional NODE_CHANNEL_* entries. @@ -873,7 +872,7 @@ pub(crate) fn spawn_maybe_sync( } let _ = &inherited_env_storage; - // PORT NOTE: Zig `inline for (0..stdio.len)` — unrolled here as a regular for; const N=3. + // Note: Zig `inline for (0..stdio.len)` — unrolled here as a regular for; const N=3. for fd_index in 0..stdio.len() { if stdio[fd_index].can_use_memfd(IS_SYNC, fd_index > 0 && max_buffer.is_some()) { if stdio[fd_index].use_memfd(fd_index as u32) { @@ -889,7 +888,7 @@ pub(crate) fn spawn_maybe_sync( if *should_close_memfd { for fd_index in 0..stdio.len() { if matches!(stdio[fd_index], Stdio::Memfd(_)) { - // PORT NOTE: Zig closes the fd then writes + // Note: Zig closes the fd then writes // `stdio[i] = .ignore`. In Rust that assignment would // Drop the old `Stdio::Memfd` and re-close the same fd // (EBADF → fd.rs debug_assert). `Stdio`'s Drop already @@ -901,7 +900,7 @@ pub(crate) fn spawn_maybe_sync( } }, ); - // PORT NOTE: reshaped for borrowck — re-borrow through the guard tuple so the guard + // Note: reshaped for borrowck — re-borrow through the guard tuple so the guard // stays armed (runs on every early return) until disarmed by `**should_close_memfd = false` below. let (should_close_memfd, stdio) = &mut *memfd_guard; @@ -959,7 +958,7 @@ pub(crate) fn spawn_maybe_sync( // PERF(port): was assume_capacity env_array.push(match ipc_mode { - // PORT NOTE: Zig `inline else => |t| "..." ++ @tagName(t)` — written out per variant. + // Note: Zig `inline else => |t| "..." ++ @tagName(t)` — written out per variant. IPC::Mode::Json => c"NODE_CHANNEL_SERIALIZATION_MODE=json".as_ptr(), IPC::Mode::Advanced => c"NODE_CHANNEL_SERIALIZATION_MODE=advanced".as_ptr(), }); @@ -1008,7 +1007,7 @@ pub(crate) fn spawn_maybe_sync( // For spawnSync, use an isolated event loop to prevent JavaScript timers from firing // and to avoid interfering with the main event loop. // - // PORT NOTE: borrowck — `rare_data()` borrows `jsc_vm` mutably and the + // Note: borrowck — `rare_data()` borrows `jsc_vm` mutably and the // returned `&mut SpawnSyncEventLoop` keeps that borrow alive, so we cannot // also pass `jsc_vm` into `spawn_sync_event_loop`/`prepare`/`cleanup` while // holding it. Route through a raw `*mut VirtualMachineRef` for the duration. @@ -1018,7 +1017,7 @@ pub(crate) fn spawn_maybe_sync( // instead of the main loop — matches Zig // `&jsc_vm.rareData().spawnSyncEventLoop(jsc_vm).event_loop`. let event_loop: *mut jsc::event_loop::EventLoop = if IS_SYNC { - // SAFETY: see PORT NOTE above; `spawn_sync_event_loop` re-borrows the + // SAFETY: see note above; `spawn_sync_event_loop` re-borrows the // same VM via the raw pointer for its `vm` arg. unsafe { let sync_loop = (*jsc_vm_ptr) @@ -1037,7 +1036,7 @@ pub(crate) fn spawn_maybe_sync( jsc_vm.event_loop() }; - // PORT NOTE: reshaped for borrowck — `defer!` is non-`move`, so the closure + // Note: reshaped for borrowck — `defer!` is non-`move`, so the closure // would capture the *place* `*jsc_vm_ptr` and conflict with later // `&mut *jsc_vm_ptr` re-borrows below. Copy the raw pointer into a sibling // local so the closure's captured place is disjoint. @@ -1177,7 +1176,7 @@ pub(crate) fn spawn_maybe_sync( // Use the isolated loop for spawnSync operations // - // PORT NOTE: `PosixSpawnResult::to_process` consumes `self` but only reads + // Note: `PosixSpawnResult::to_process` consumes `self` but only reads // `pid`/`pidfd`/`has_exited`. Zig kept using `spawned.stdin/stdout/stderr/ // extra_pipes` afterward; in Rust, take those fields out first so the // partial move is explicit. @@ -1201,7 +1200,7 @@ pub(crate) fn spawn_maybe_sync( // When run synchronously, subprocess isn't garbage collected. // - // PORT NOTE: Zig built a placeholder struct, took its address for + // Note: Zig built a placeholder struct, took its address for // `MaxBuf::create_for_subprocess`, then overwrote `subprocess.*` with the // real aggregate. In Rust that whole-struct reassignment would (a) move // `process` twice and (b) run Drop on every field of the placeholder. Build @@ -1344,7 +1343,7 @@ pub(crate) fn spawn_maybe_sync( subprocess.stderr_maxbuf.set(mb); subprocess.deref(); subprocess.deref(); - // PORT NOTE: Zig returned `err` directly (`bun.JSError` or + // Note: Zig returned `err` directly (`bun.JSError` or // `error.OutOfMemory`); the Rust port's `Writable::init` returns // `bun_core::Error`. Map non-thrown to OOM. if global_this.has_exception() { @@ -1355,7 +1354,7 @@ pub(crate) fn spawn_maybe_sync( } } - // PORT NOTE: Zig passed `allocator` (unused/autofix) — dropped in Rust port of Readable::init. + // Note: Zig passed `allocator` (unused/autofix) — dropped in Rust port of Readable::init. // event_loop points to the live JSC EventLoop for this thread. let event_loop_nn = NonNull::new(event_loop).expect("event_loop is null"); subprocess.stdout.set(Readable::init( @@ -1417,7 +1416,7 @@ pub(crate) fn spawn_maybe_sync( return Err(global_this.throw_value(err)); } - // PORT NOTE: Zig left this `undefined` and only read it on the assigned path; Rust uses + // Note: Zig left this `undefined` and only read it on the assigned path; Rust uses // Option since `IPC::Socket` is a tagged union (zeroed enum is UB). #[cfg(unix)] let mut posix_ipc_info: Option = None; @@ -1516,7 +1515,7 @@ pub(crate) fn spawn_maybe_sync( } if matches!(subprocess.stdin.get(), Writable::Pipe(_)) && promise_for_stream == JSValue::ZERO { - // PORT NOTE: Zig writes `subprocess.stdin.pipe.signal = + // Note: Zig writes `subprocess.stdin.pipe.signal = // Signal.init(&subprocess.stdin)` and the callback `@fieldParentPtr`s // back to the `Subprocess`. In Rust the SignalHandler impl is on // `Subprocess` and the stored back-pointer is the `*mut Subprocess` @@ -1563,7 +1562,7 @@ pub(crate) fn spawn_maybe_sync( if let Some(timeout_val) = timeout { let ts = Timespec::ms_from_now(TimespecMockMode::AllowMockedTime, i64::from(timeout_val)); - // PORT NOTE: `EventLoopTimer.next` is a local-stub Timespec until + // Note: `EventLoopTimer.next` is a local-stub Timespec until // `bun_event_loop` switches to `bun_core::Timespec`; copy fieldwise. subprocess.event_loop_timer.with_mut(|t| { t.next = crate::timer::ElTimespec { @@ -1621,7 +1620,7 @@ pub(crate) fn spawn_maybe_sync( } } - // PORT NOTE: reshaped for borrowck — copy `subprocess_ptr` so the + // Note: reshaped for borrowck — copy `subprocess_ptr` so the // non-`move` `defer!` closure captures a disjoint place from the // `(*subprocess_ptr).abort_signal = …` writes that follow. let subprocess_ptr_exit = subprocess_ptr; @@ -1651,7 +1650,7 @@ pub(crate) fn spawn_maybe_sync( } if let Readable::Pipe(pipe) = subprocess.stdout.get() { - // PORT NOTE: pass `subprocess_nn` (the `NonNull>` + // Note: pass `subprocess_nn` (the `NonNull>` // captured above) instead of the live `&mut subprocess`, which would // alias with the `&mut subprocess.stdout` borrow held by `pipe`. if let Err(err) = Readable::pipe_reader_mut(pipe).start(subprocess_nn, event_loop_nn) { @@ -1667,7 +1666,7 @@ pub(crate) fn spawn_maybe_sync( } if let Readable::Pipe(pipe) = subprocess.stderr.get() { - // PORT NOTE: see stdout arm above — avoid aliased &mut. + // Note: see stdout arm above — avoid aliased &mut. if let Err(err) = Readable::pipe_reader_mut(pipe).start(subprocess_nn, event_loop_nn) { let _ = subprocess.try_kill(subprocess.kill_signal); let _ = global_this.throw_value(err.to_js(global_this)); @@ -1711,7 +1710,7 @@ pub(crate) fn spawn_maybe_sync( return Ok(out); } - // PORT NOTE: Zig `comptime bun.assert(is_sync)` — anonymous const items cannot capture + // Note: Zig `comptime bun.assert(is_sync)` — anonymous const items cannot capture // const-generic params, so use a runtime debug_assert (the !IS_SYNC path returned above). debug_assert!(IS_SYNC); @@ -1773,7 +1772,7 @@ pub(crate) fn spawn_maybe_sync( // This does mean if an AbortSignal times out it will throw if let Some(signal) = subprocess.abort_signal_ref() { if let Some(abort_signal_timeout) = signal.get_timeout() { - // PORT NOTE: `AbortSignal::Timeout.event_loop_timer` uses the + // Note: `AbortSignal::Timeout.event_loop_timer` uses the // bun_event_loop-local `Timespec` stub; convert fieldwise. if abort_signal_timeout.event_loop_timer.state == crate::timer::EventLoopTimerState::ACTIVE @@ -2039,7 +2038,7 @@ pub(crate) fn append_envp_from_js( .throw()); } - // PORT NOTE: Zig `std.fmt.allocPrintSentinel(envp.allocator, "{f}={f}", .{key, value}, 0)` + // Note: Zig `std.fmt.allocPrintSentinel(envp.allocator, "{f}={f}", .{key, value}, 0)` // PERF(port): was arena bulk-free — profile if it shows up on a hot path. let line: ZBox = { let mut buf: Vec = Vec::new(); diff --git a/src/runtime/api/bun/spawn/stdio.rs b/src/runtime/api/bun/spawn/stdio.rs index 13e6f4bceb8f..5c778fcbe440 100644 --- a/src/runtime/api/bun/spawn/stdio.rs +++ b/src/runtime/api/bun/spawn/stdio.rs @@ -33,8 +33,9 @@ bun_output::define_scoped_log!(log, SYS, visible); /// Anonymous payload of `Stdio::Capture` in Zig: `struct { buf: *bun.Vec }`. #[derive(Clone, Copy)] pub struct Capture { - // TODO(port): lifetime — Zig holds a raw `*bun.Vec` backref owned - // elsewhere (shell). LIFETIMES.tsv has no row; treating as BACKREF. + // BACKREF: raw pointer to a capture buffer owned by the shell interpreter + // (Zig: `*bun.Vec`). The shell keeps the buffer alive for the lifetime + // of the spawned process; this struct never frees it. pub buf: *mut Vec, } @@ -185,11 +186,11 @@ impl Stdio { } } - // PORT NOTE: reshaped for borrowck — `remain` borrows `*self`, so we + // Note: reshaped for borrowck — `remain` borrows `*self`, so we // must drop it before mutating `self`. Shadowing ends the borrow here. let _ = remain; - // PORT NOTE: in Zig only `.array_buffer` / `.blob` are explicitly + // Note: in Zig only `.array_buffer` / `.blob` are explicitly // deinit'd before reassignment. In Rust, assigning to `*self` drops // the previous variant via `Drop`, which has equivalent behaviour // for those arms and is a no-op for others (and additionally closes diff --git a/src/runtime/api/bun/subprocess.rs b/src/runtime/api/bun/subprocess.rs index ebc32887d8a7..1aea5f2d54c7 100644 --- a/src/runtime/api/bun/subprocess.rs +++ b/src/runtime/api/bun/subprocess.rs @@ -105,7 +105,7 @@ pub enum ObservableGetter { pub use bun_spawn::process::StdioKind; -// PORT NOTE: `#[bun_jsc::JsClass]` does not yet handle generic structs (it emits the +// Note: `#[bun_jsc::JsClass]` does not yet handle generic structs (it emits the // bare ident in extern signatures). The `JsClass` impl + finalize/construct C-ABI // hooks are hand-expanded below for `Subprocess<'_>`. // @@ -148,7 +148,8 @@ pub struct Subprocess<'a> { pub ipc_data: JsCell>, pub flags: Cell, - // TODO(port): lifetime — weak observer, nulled in onStdinDestroyed; no ownership + /// Weak observer of the stdin `FileSink` — holds no ownership/ref. `onStdinDestroyed` + /// nulls this before the sink is freed, so it is never dereferenced after the sink dies. pub weak_file_sink_stdin_ptr: Cell>>, /// +1 C++-intrusive ref held; released in `clear_abort_signal` via /// `AbortSignal::unref()`. Not `Arc` — `AbortSignal` is an opaque FFI @@ -170,8 +171,8 @@ pub struct Subprocess<'a> { bun_event_loop::impl_timer_owner!(Subprocess<'_>; from_timer_ptr => event_loop_timer); -// PORT NOTE: no `Default` impl for `Subprocess`. `js_bun_spawn_bindings:: -// spawn_maybe_sync` fills every field explicitly (see PORT NOTE there), and +// Note: no `Default` impl for `Subprocess`. `js_bun_spawn_bindings:: +// spawn_maybe_sync` fills every field explicitly (see note there), and // `*mut Process` has no sound placeholder anyway. pub type SubprocessRc<'a> = RefPtr>; @@ -586,7 +587,7 @@ impl Subprocess<'_> { } this.observable_getters .set(this.observable_getters.get() | ObservableGetter::Stdin); - // PORT NOTE: reshaped for borrowck — Zig passed `&stdin` and `*Subprocess` + // Note: reshaped for borrowck — Zig passed `&stdin` and `*Subprocess` // separately (aliasing). `Writable::to_js` takes only the parent and // projects `stdin` internally so no two `&mut` overlap here. Ok(Writable::to_js(this, global_this)) diff --git a/src/runtime/api/bun/subprocess/Readable.rs b/src/runtime/api/bun/subprocess/Readable.rs index eec27c859921..09dd16296b47 100644 --- a/src/runtime/api/bun/subprocess/Readable.rs +++ b/src/runtime/api/bun/subprocess/Readable.rs @@ -113,7 +113,6 @@ impl Readable { max_size: Option>, _is_sync: bool, ) -> Readable { - // PORT NOTE: Zig `allocator` param dropped (was unused / autofix); global mimalloc assumed. super::assert_stdio_result!(result); // Ownership of any resource inside `stdio` (notably `.memfd`) is being @@ -214,7 +213,6 @@ impl Readable { *self = Readable::Closed; } Readable::Pipe(_) => { - // PORT NOTE: reshaped for borrowck — Zig captures `pipe` by-copy then overwrites `this.*`. let Readable::Pipe(pipe) = mem::replace(self, Readable::Closed) else { unreachable!() }; @@ -240,8 +238,8 @@ impl Readable { Self::pipe_detach(&pipe); } Readable::Buffer(_) => { - // PORT NOTE: Zig calls `buf.deinit(default_allocator)` without resetting the tag. - // In Rust, dropping the CowString (via overwrite) is the equivalent; finalize is terminal. + // Dropping the CowString (via the overwrite) frees the buffer; + // finalize is terminal. *self = Readable::Closed; } _ => {} @@ -255,7 +253,6 @@ impl Readable { Readable::Fd(fd) => Ok(fd.to_js(global)), Readable::Pipe(_) => { - // PORT NOTE: reshaped for borrowck. let Readable::Pipe(pipe) = mem::replace(self, Readable::Closed) else { unreachable!() }; @@ -264,7 +261,6 @@ impl Readable { result } Readable::Buffer(_) => { - // PORT NOTE: reshaped for borrowck — `defer this.* = .closed` becomes take-then-use. let Readable::Buffer(mut buffer) = mem::replace(self, Readable::Closed) else { unreachable!() }; @@ -297,7 +293,6 @@ impl Readable { } } Readable::Pipe(_) => { - // PORT NOTE: reshaped for borrowck. let Readable::Pipe(pipe) = mem::replace(self, Readable::Closed) else { unreachable!() }; @@ -306,7 +301,6 @@ impl Readable { Ok(result) } Readable::Buffer(_) => { - // PORT NOTE: reshaped for borrowck. let Readable::Buffer(mut buf) = mem::replace(self, Readable::Closed) else { unreachable!() }; @@ -315,9 +309,8 @@ impl Readable { Err(_) => return Err(global.throw_out_of_memory()), }; - // PORT NOTE: ownership of the mimalloc-backed buffer transfers to - // JSC (freed via `MarkedArrayBuffer_deallocator`) — matches Zig - // `fromBytes(own, .Uint8Array)`. + // Ownership of the mimalloc-backed buffer transfers to JSC + // (freed via `MarkedArrayBuffer_deallocator`). Ok(jsc::MarkedArrayBuffer { buffer: jsc::ArrayBuffer::from_owned_bytes(own, jsc::JSType::Uint8Array), owns_buffer: true, diff --git a/src/runtime/api/bun/subprocess/SubprocessPipeReader.rs b/src/runtime/api/bun/subprocess/SubprocessPipeReader.rs index a38b2cd5bc0c..8c0332d1426f 100644 --- a/src/runtime/api/bun/subprocess/SubprocessPipeReader.rs +++ b/src/runtime/api/bun/subprocess/SubprocessPipeReader.rs @@ -183,10 +183,6 @@ impl PipeReader { // will drop the last ref and deinit() closes the handle. return bun_sys::Result::Ok(()); } - // PORT NOTE: `PollOrFd` is an enum in the Rust port; the Zig - // `this.reader.handle.poll` field projection becomes a variant - // pattern. `FilePoll` is an opaque vtable-backed handle (Copy) - // with `set_flag` standing in for `poll.flags.insert(...)`. if let Some(poll) = self.reader.handle.get_poll() { poll.set_flag(FilePollFlag::Socket); poll.set_flag(FilePollFlag::Nonblocking); @@ -238,8 +234,8 @@ impl PipeReader { pub(crate) fn to_owned_slice(&mut self) -> Vec { if let State::Done(bytes) = core::mem::replace(&mut self.state, State::Pending) { - // PORT NOTE: reshaped for borrowck — Zig reads `state.done` in place; here we - // take it out and restore Pending (caller immediately overwrites state anyway). + // Take the bytes out and restore Pending — the caller immediately + // overwrites the state anyway. return bytes; } // we do not use .toOwnedSlice() because we don't want to reallocate memory. @@ -283,15 +279,15 @@ impl PipeReader { match &self.state { State::Pending => { - // PORT NOTE: `_parent` is unused in `from_pipe` (Zig `anytype` discard); pass the - // raw ptr instead of `self` so borrowck allows `&mut self.reader` alongside it. + // `_parent` is unused in `from_pipe`; pass the raw ptr instead + // of `self` so borrowck allows `&mut self.reader` alongside it. let stream = ReadableStream::from_pipe(global_object, this_ptr, &mut self.reader); self.state = State::Done(Vec::new()); stream } State::Done(_) => { - // PORT NOTE: reshaped for borrowck — take the payload only in this arm so the - // Pending arm above observes `state == Pending` when `from_pipe` reads `self`. + // Take the payload only in this arm so the Pending arm above + // observes `state == Pending` when `from_pipe` reads `self`. let State::Done(bytes) = core::mem::replace(&mut self.state, State::Done(Vec::new())) else { @@ -315,7 +311,7 @@ impl PipeReader { State::Done(bytes) => { let bytes = core::mem::take(bytes); // `defer this.state = .{ .done = &.{} }` — state.done is now empty via take(). - // PORT NOTE: `MarkedArrayBuffer::from_bytes` takes a borrowed `&mut [u8]` + // `MarkedArrayBuffer::from_bytes` takes a borrowed `&mut [u8]` // with `owns_buffer = true` (freed via mimalloc on the JS side); leak the // boxed slice so JS becomes the owner — same pattern as // `MarkedArrayBuffer::from_string`. diff --git a/src/runtime/api/bun/subprocess/Writable.rs b/src/runtime/api/bun/subprocess/Writable.rs index 0e5f3caef548..656f67abb6f6 100644 --- a/src/runtime/api/bun/subprocess/Writable.rs +++ b/src/runtime/api/bun/subprocess/Writable.rs @@ -18,7 +18,7 @@ use bun_io::pipe_writer::BaseWindowsPipeWriter as _; use super::{Flags, StaticPipeWriter, StdioResult, Subprocess, js}; pub enum Writable<'a> { - // PORT NOTE: Zig uses intrusive-refcounted `*FileSink` (manual ref/deref). + // Note: Zig uses intrusive-refcounted `*FileSink` (manual ref/deref). // Keep a raw NonNull and call `FileSink::deref` explicitly to mirror that. Pipe(NonNull), Fd(Fd), @@ -137,7 +137,7 @@ impl<'a> Writable<'a> { // When the stream has closed we need to be notified to prevent a use-after-free // We can test for this use-after-free by enabling hot module reloading on a file and then saving it twice // - // PORT NOTE: reshaped for borrowck — Zig `@fieldParentPtr("stdin", this)` + // Note: reshaped for borrowck — Zig `@fieldParentPtr("stdin", this)` // recovers `*Subprocess` from `*Writable` and freely interleaves access to // both. In Rust, deriving the parent from `&mut self` is out-of-provenance // (the `&mut` only covers the `stdin` field). Instead the `SignalHandler` @@ -182,7 +182,6 @@ impl<'a> Writable<'a> { result: StdioResult, promise_for_stream: &mut JSValue, ) -> Result, bun_core::Error> { - // TODO(port): narrow error set super::assert_stdio_result!(result); let global = event_loop.global_ref(); @@ -399,7 +398,7 @@ impl<'a> Writable<'a> { } pub fn to_js(subprocess: &Subprocess<'a>, global_this: &JSGlobalObject) -> JSValue { - // PORT NOTE: reshaped for borrowck — Zig passed `*Writable` (== `&stdin`) + // Note: reshaped for borrowck — Zig passed `*Writable` (== `&stdin`) // and `*Subprocess` separately, which alias. Take only the parent and // project `stdin` here so no two `&mut` overlap at any point. match subprocess.stdin.replace(Writable::Ignore) { @@ -495,7 +494,7 @@ impl<'a> Writable<'a> { } } - // PORT NOTE: reshaped for borrowck — see `on_close`. Zig + // Note: reshaped for borrowck — see `on_close`. Zig // `@fieldParentPtr("stdin", this)` is replaced by the caller passing the // parent; deriving it from `&mut self` on `Writable` would be // out-of-provenance. @@ -520,7 +519,7 @@ impl<'a> Writable<'a> { } Writable::Buffer(buffer) => { Self::buffer_writer_mut(&buffer).update_ref(false); - // PORT NOTE: Zig calls `buffer.deref()` without reassigning to `.ignore`; + // Note: Zig calls `buffer.deref()` without reassigning to `.ignore`; // RefPtr::deref drops the held ref. buffer.deref(); } @@ -558,7 +557,7 @@ impl<'a> Writable<'a> { } } -// PORT NOTE: Zig wires `pipe.signal = Signal.init(&subprocess.stdin)` and the +// Note: Zig wires `pipe.signal = Signal.init(&subprocess.stdin)` and the // callbacks then `@fieldParentPtr` back to the `Subprocess`. Registering the // `*mut Writable` and recovering the parent inside the callback is // out-of-provenance in Rust (the `&mut Writable` formed by the vtable thunk diff --git a/src/runtime/api/bun/x509.rs b/src/runtime/api/bun/x509.rs index 3e0b38098a78..38cbdfed6535 100644 --- a/src/runtime/api/bun/x509.rs +++ b/src/runtime/api/bun/x509.rs @@ -13,7 +13,6 @@ pub(crate) fn to_js_object(cert: &mut X509, global_object: &JSGlobalObject) -> J Ok(Bun__X509__toJS(cert, global_object)) } -// TODO(port): move to runtime_sys (or bun_boringssl_sys) // `X509`/`JSGlobalObject` are opaque `repr(C)` handles; `&mut`/`&` are // ABI-identical to non-null pointers, so the validity proof is in the type. unsafe extern "C" { diff --git a/src/runtime/api/crash_handler_jsc.rs b/src/runtime/api/crash_handler_jsc.rs index f570f45bef7b..71a9f6f0e751 100644 --- a/src/runtime/api/crash_handler_jsc.rs +++ b/src/runtime/api/crash_handler_jsc.rs @@ -13,7 +13,6 @@ pub mod js_bindings { pub fn generate(global: &JSGlobalObject) -> JSValue { let obj = JSValue::create_empty_object(global, 8); - // PORT NOTE: `inline for` over homogeneous (name, host_fn) tuples → const array + plain `for`. // `#[bun_jsc::host_fn]` emits an `extern "C"` shim named `__jsc_host_`; that // shim is the `JSHostFn` value passed to `JSFunction::create`. const ENTRIES: &[(&str, bun_jsc::JSHostFn)] = &[ diff --git a/src/runtime/api/cron.rs b/src/runtime/api/cron.rs index 4023b3cf8a22..2342fac26b1f 100644 --- a/src/runtime/api/cron.rs +++ b/src/runtime/api/cron.rs @@ -67,7 +67,7 @@ use crate::jsc_hooks::timer_all_mut as timer_all; /// Shared base for [`CronRegisterJob`] and [`CronRemoveJob`]. // Zig: `fn CronJobBase(comptime Self: type) type { return struct { ... } }` // -// PORT NOTE: every method on the path to `finish()` (which `heap::take`- +// Note: every method on the path to `finish()` (which `heap::take`- // drops `this`) takes a raw `*mut Self` receiver, mirroring the Zig `*Self`. // A `&mut self` *parameter* would carry a Stacked Borrows FnEntry protector, // making the in-flight dealloc UB; a *local* `let s = &mut *this` reborrow @@ -215,7 +215,7 @@ impl CronRegisterJob { } } - /// May free `this`. Raw-ptr receiver: see [`CronJobBase`] PORT NOTE. + /// May free `this`. Raw-ptr receiver: see [`CronJobBase`] note. unsafe fn maybe_finished(this: *mut Self) { // SAFETY: local reborrow (no FnEntry protector); not used after any // call below that may free `this`. @@ -308,7 +308,7 @@ impl CronRegisterJob { unsafe { Self::advance_state(this) }; } - /// May free `this`. Raw-ptr receiver: see [`CronJobBase`] PORT NOTE. + /// May free `this`. Raw-ptr receiver: see [`CronJobBase`] note. unsafe fn advance_state(this: *mut Self) { // SAFETY: local reborrow; last use precedes any self-freeing call. let s = unsafe { &mut *this }; @@ -390,7 +390,7 @@ impl CronRegisterJob { // -- Linux -- - /// May free `this`. Raw-ptr receiver: see [`CronJobBase`] PORT NOTE. + /// May free `this`. Raw-ptr receiver: see [`CronJobBase`] note. #[cfg(all(not(target_os = "macos"), not(windows)))] unsafe fn start_linux(this: *mut Self) { // SAFETY: local reborrow; not used after `spawn_cmd`/`finish`. @@ -408,7 +408,7 @@ impl CronRegisterJob { unsafe { Self::spawn_cmd(this, &mut argv, spawn::Stdio::Ignore, spawn::Stdio::Buffer) }; } - /// May free `this`. Raw-ptr receiver: see [`CronJobBase`] PORT NOTE. + /// May free `this`. Raw-ptr receiver: see [`CronJobBase`] note. #[cfg(not(target_os = "macos"))] unsafe fn process_crontab_and_install(this: *mut Self) { // SAFETY: local reborrow; not used after `spawn_cmd`/`finish`. @@ -473,7 +473,7 @@ impl CronRegisterJob { let _ = file.close(); // close error is non-actionable (Zig parity: discarded) s.state = RegisterState::InstallingCrontab; - // PORT NOTE: explicit deinit of old reader before reassign — Drop handles it. + // Note: explicit deinit of old reader before reassign — Drop handles it. s.stdout_reader = OutputReader::init::(); let Some(crontab_path) = find_crontab() else { s.set_err(format_args!("crontab not found in PATH")); @@ -487,7 +487,7 @@ impl CronRegisterJob { // -- macOS -- - /// May free `this`. Raw-ptr receiver: see [`CronJobBase`] PORT NOTE. + /// May free `this`. Raw-ptr receiver: see [`CronJobBase`] note. #[cfg(target_os = "macos")] unsafe fn start_mac(this: *mut Self) { // SAFETY: local reborrow; not used after `spawn_bootout`/`finish`. @@ -618,7 +618,7 @@ impl CronRegisterJob { unsafe { Self::spawn_bootout(this) }; } - /// May free `this`. Raw-ptr receiver: see [`CronJobBase`] PORT NOTE. + /// May free `this`. Raw-ptr receiver: see [`CronJobBase`] note. #[cfg(target_os = "macos")] unsafe fn spawn_bootout(this: *mut Self) { // SAFETY: local reborrow; not used after `spawn_cmd`/`finish`. @@ -647,7 +647,7 @@ impl CronRegisterJob { drop(uid_str); } - /// May free `this`. Raw-ptr receiver: see [`CronJobBase`] PORT NOTE. + /// May free `this`. Raw-ptr receiver: see [`CronJobBase`] note. #[cfg(target_os = "macos")] unsafe fn spawn_bootstrap(this: *mut Self) { // SAFETY: local reborrow; not used after `spawn_cmd`/`finish`. @@ -834,7 +834,7 @@ pub fn cron_register(global: &JSGlobalObject, frame: &CallFrame) -> JsResult JsResult(); @@ -1619,7 +1619,7 @@ impl CronJob { None => return, }; for job in jobs { - // PORT NOTE: stored as opaque `rare_data::high_tier::CronJob`; the + // Note: stored as opaque `rare_data::high_tier::CronJob`; the // concrete type is this `CronJob` (see `register` push site). let job = job.cast::(); // List holds a ref for each entry. @@ -1893,7 +1893,7 @@ impl CronJob { // so skip the list ref + append entirely. if vm.hot_reload == HOT_RELOAD_HOT || vm.worker.is_some() { job_ref.ref_(); // owned by cron_jobs entry - // PORT NOTE: `RareData::cron_jobs` stores the opaque high-tier + // Note: `RareData::cron_jobs` stores the opaque high-tier // placeholder type; cast through `*mut ()` and let inference pick // the element type. vm.rare_data().cron_jobs.push(job.cast::<()>().cast()); @@ -2068,7 +2068,6 @@ pub fn cron_parse(global: &JSGlobalObject, frame: &CallFrame) -> JsResult); @@ -2149,7 +2148,7 @@ impl SpawnCmdTarget for CronRemoveJob { /// /// May free `this` (synchronously, via either an early `T::finish` on setup /// error or `watch_or_reap` → exit handler → `maybe_finished` → `finish`). -/// Raw-ptr receiver: see [`CronJobBase`] PORT NOTE. Callers must not touch +/// Raw-ptr receiver: see [`CronJobBase`] note. Callers must not touch /// `this` after this returns. unsafe fn spawn_cmd_generic( this: *mut T, @@ -2217,7 +2216,7 @@ unsafe fn spawn_cmd_generic( } }; - // PORT NOTE / OWNERSHIP: Zig stashes the heap libuv pipe in + // Ownership note: Zig stashes the heap libuv pipe in // `stderr_reader.source.?.pipe` and reuses the same pointer for // `SpawnOptions.stderr = .{ .buffer = pipe }`. In the Rust port BOTH // `Source::Pipe` and `WindowsStdioResult::Buffer` own a `Box`, @@ -2441,8 +2440,6 @@ fn alloc_print_z(args: core::fmt::Arguments<'_>) -> Result Result { let mut name_buf = PathBuffer::uninit(); - // PORT NOTE: Zig used `prefix ++ "tmp"` at comptime; concat at runtime here. - // TODO(port): use const_format::concatcp! once call sites pass a const. let mut full_prefix = Vec::with_capacity(prefix.len() + 3); full_prefix.extend_from_slice(prefix.as_bytes()); full_prefix.extend_from_slice(b"tmp"); @@ -2551,7 +2548,6 @@ pub enum CalendarError { #[error("OutOfMemory")] OutOfMemory, } -// TODO(port): narrow error set impl From for bun_core::Error { fn from(e: CalendarError) -> Self { @@ -3073,7 +3069,6 @@ fn append_calendar_trigger_with_schedule( /// Local stand-in for the planned `bun_core::BitOps` trait — only what /// `compute_step_interval` needs, implemented for the two integer widths the /// cron bitfields use. -// TODO(port): replace with `bun_core::BitOps` once that trait lands. trait StepBits: Copy + core::ops::BitAnd + core::ops::Sub + PartialEq { diff --git a/src/runtime/api/cron_parser.rs b/src/runtime/api/cron_parser.rs index 472fd5947956..ac65c4cd69f5 100644 --- a/src/runtime/api/cron_parser.rs +++ b/src/runtime/api/cron_parser.rs @@ -277,7 +277,6 @@ enum NameKind { /// Parse a single cron field (e.g. "1,5-10,*/3") into a bitset. fn parse_field(field: &[u8], min: u8, max: u8, kind: NameKind) -> Result { - // TODO(port): Zig used u7 for min/max/step/i; using u8 here (values 128-255 behavior may differ for step) if field.is_empty() { return Err(CronError::InvalidField); } @@ -299,7 +298,11 @@ fn parse_field(field: &[u8], min: u8, max: u8, kind: NameKind) -> Res if s.is_empty() { return Err(CronError::InvalidStep); } - bun_core::parse_decimal::(s).ok_or(CronError::InvalidStep)? + // Zig parsed the step as `u7`, so 128-255 overflow into InvalidStep too. + match bun_core::parse_decimal::(s) { + Some(v @ 0..=127) => v, + _ => return Err(CronError::InvalidStep), + } } else { 1 }; diff --git a/src/runtime/api/filesystem_router.rs b/src/runtime/api/filesystem_router.rs index 49e58f47db22..9d5bac3d9cd8 100644 --- a/src/runtime/api/filesystem_router.rs +++ b/src/runtime/api/filesystem_router.rs @@ -44,7 +44,7 @@ use crate::api::bun_object; use crate::webcore::{Request, Response}; use bun_bundler as Transpiler; -// PORT NOTE: `FrameworkFileSystemRouter` is declared in this file's +// Note: `FrameworkFileSystemRouter` is declared in this file's // `filesystem_router.classes.ts`, so codegen looks for the backing struct here // (`crate::api::filesystem_router::FrameworkFileSystemRouter`). In Zig the // implementation lives in `bake/FrameworkRouter.zig` as `JSFrameworkRouter` and @@ -103,20 +103,21 @@ pub struct FileSystemRouter { // router (we hold +1 via `claim` in `constructor`, released in `finalize`). pub origin: Option>, pub base_dir: Option>, - // PORT NOTE: Router<'a> only borrows the global FileSystem singleton — `'static` is faithful. + // Note: Router<'a> only borrows the global FileSystem singleton — `'static` is faithful. pub router: JsCell>, // PERF(port): was arena bulk-free — Router borrows slices from this arena across calls; - // kept as boxed arena per LIFETIMES.tsv (OWNED). TODO(port): confirm bumpalo vs ArenaAllocator. + // kept as boxed arena per LIFETIMES.tsv (OWNED). `bun_alloc::Arena` (mimalloc heap) is + // the runtime-wide arena type, so allocations here are individually freeable too. pub arena: JsCell>, - // PORT NOTE: dropped `std.mem.Allocator param` field — it was always `arena.arena()`. + // Note: dropped `std.mem.Allocator param` field — it was always `arena.arena()`. pub asset_prefix: Option>, } impl FileSystemRouter { - // PORT NOTE: `pub const js = jsc.Codegen.JSFileSystemRouter; toJS/fromJS/fromJSDirect` + // Note: `pub const js = jsc.Codegen.JSFileSystemRouter; toJS/fromJS/fromJSDirect` // are wired by `#[bun_jsc::JsClass]` codegen — deleted per PORTING.md. - // PORT NOTE: no `#[bun_jsc::host_fn]` here — the `Free` shim it emits calls + // Note: no `#[bun_jsc::host_fn]` here — the `Free` shim it emits calls // a bare `constructor(...)` which cannot resolve inside an `impl`. The // `#[bun_jsc::JsClass]` macro already emits the `::constructor` shim. pub fn constructor( @@ -219,7 +220,8 @@ impl FileSystemRouter { .throw_invalid_arguments(format_args!("Expected assetPrefix to be a string"))); } - // TODO(port): `clone_if_borrowed` not on `ZigStringSlice` — copy into arena. + // Copy into the arena so the slice always owns stable bytes (ZigStringSlice + // has no clone-if-borrowed helper; the copy only happens at construction). let s = asset_prefix.to_slice(global_this)?; // SAFETY: arena is boxed and moved into the returned `FileSystemRouter`; allocation // outlives this slice. Detach borrow via raw ptr so `arena` can be moved below. @@ -264,7 +266,7 @@ impl FileSystemRouter { }; let mut router = Router::Router::init( - // PORT NOTE: `vm.transpiler.fs` — the resolver's `FileSystem` singleton. + // Note: `vm.transpiler.fs` — the resolver's `FileSystem` singleton. Fs::FileSystem::instance(), RouteConfig { dir: Box::from(&path_to_use[..]), @@ -317,7 +319,7 @@ impl FileSystemRouter { root_dir_info.abs_path }; - // PORT NOTE: `vm.refCountedString` is an interning cache — on a cache HIT it + // Note: `vm.refCountedString` is an interning cache — on a cache HIT it // returns the existing `*mut RefString` WITHOUT bumping the refcount. The Zig // spec gets away with this because `getScriptSrc`/`getOrigin` call `.slice()` // (which leaks +1 each access) so the impl never reaches 0. The Rust port uses @@ -353,7 +355,7 @@ impl FileSystemRouter { arena: JsCell::new(arena), }); - // PORT NOTE: `base_dir.?.ref()` — Zig borrowed the RefString bytes into + // Note: `base_dir.?.ref()` — Zig borrowed the RefString bytes into // `router.config.dir` and bumped the refcount. RouteConfig::dir is now an owned // `Box<[u8]>`, so copy the bytes; the `claim` above already took our +1. // `base_dir` was just set to Some above. @@ -378,7 +380,7 @@ impl FileSystemRouter { let normalized: Vec; #[cfg(windows)] { - // PORT NOTE: Zig used a `ThreadlocalBuffers` slot. `with_borrow_mut` can't + // Note: Zig used a `ThreadlocalBuffers` slot. `with_borrow_mut` can't // hand back a slice that outlives the closure, so copy out (cold reload path). normalized = WIN32_NORMALIZE_BUF .with_borrow_mut(|buf| vm.fs().normalize_buf(&mut buf[..], input_path).to_vec()); @@ -439,7 +441,7 @@ impl FileSystemRouter { pub fn bust_dir_cache(&self, global_this: &JSGlobalObject) { let dir = strings::paths::without_trailing_slash_windows_path(&self.router.get().config.dir); - // PORT NOTE: reshaped for borrowck — `dir` borrows `self.router.config.dir`; the + // Note: reshaped for borrowck — `dir` borrows `self.router.config.dir`; the // recursive walk re-derives the path from the resolver per-iteration so a one-time // copy is sufficient. let dir = dir.to_vec(); @@ -469,7 +471,7 @@ impl FileSystemRouter { }; this.bust_dir_cache(global_this); - // PORT NOTE: `bust_dir_cache` re-derives the VM borrow internally; rebind here so + // Note: `bust_dir_cache` re-derives the VM borrow internally; rebind here so // our `vm` borrow is fresh under Stacked Borrows. let vm = global_this.bun_vm().as_mut(); @@ -525,7 +527,7 @@ impl FileSystemRouter { } // `this.router.deinit(); this.arena.deinit(); destroy(this.arena)` — drop old values. - // PORT NOTE: order matters — old router borrows slices from old arena, so it must drop + // Note: order matters — old router borrows slices from old arena, so it must drop // first (matches Zig teardown order). this.router.set(router); this.arena.set(arena); @@ -554,7 +556,8 @@ impl FileSystemRouter { let mut path: ZigStringSlice = 'brk: { if argument.is_string() { - // TODO(port): `clone_if_borrowed` not on `ZigStringSlice`; force-own via into_vec. + // Force ownership via into_vec: ZigStringSlice has no clone-if-borrowed + // helper, and `path` must outlive the JS string rope it came from. break 'brk ZigStringSlice::init_owned(argument.to_slice(global_this)?.into_vec()); } @@ -605,7 +608,7 @@ impl FileSystemRouter { Err(err) => { return Err(global_this.throw(format_args!( "{} parsing path: {}", - err.name(), + bun_core::Error::from(err).name(), bstr::BStr::new(path.slice()) ))); } @@ -634,7 +637,7 @@ impl FileSystemRouter { path = ZigStringSlice::init_owned(decoded.into_vec()); } - // PORT NOTE: Zig leaked `path` here (TODO comment in spec) and pointer-freed it + // Note: Zig leaked `path` here (TODO comment in spec) and pointer-freed it // in `MatchedRoute.deinit` via `mi_free(pathname.ptr)`. We instead MOVE `path` // into `MatchedRoute` so the bytes that `route.pathname`/`query_string`/param // values borrow are owned by the same heap-stable Box and freed on finalize. @@ -647,7 +650,7 @@ impl FileSystemRouter { ) .expect("unreachable"); - // PORT NOTE: `result` is a self-referential `Box` (`route` points + // Note: `result` is a self-referential `Box` (`route` points // at `route_holder` inside this very allocation). The trait `JsClass::to_js(self)` // would deref-move the value OUT of the Box and re-box it at a new address, // leaving the self-ref pointers dangling (ASAN use-after-poison). Hand the @@ -706,7 +709,7 @@ impl FileSystemRouter { // false positive on that contract. #[allow(clippy::boxed_local)] pub fn finalize(mut self: Box) { - // PORT NOTE: RefString deref()s — Zig `?.deref()` on each. `BackRef` Derefs + // Note: RefString deref()s — Zig `?.deref()` on each. `BackRef` Derefs // to `&RefString`; use `.get()` to avoid resolving to `::deref`. if let Some(p) = self.asset_prefix.take() { p.get().deref(); @@ -723,12 +726,12 @@ impl FileSystemRouter { #[bun_jsc::JsClass(no_construct, no_constructor)] pub struct MatchedRoute { /// Self-referential: always points at `self.route_holder`. See `init`. - // PORT NOTE: `Match<'a>` borrows (a) the resolver's process-lifetime DirnameStore for + // Note: `Match<'a>` borrows (a) the resolver's process-lifetime DirnameStore for // `name`/`file_path`/`basename`/`path` and (b) `self.pathname_backing` for // `pathname`/`query_string`/param values. Both are stable for `Self`'s lifetime, so // the stored `'static` is the standard self-referential erasure — see `init`. pub route: *const RouterMatch<'static>, - // PORT NOTE: `route_holder`/`params_list_holder` are wrapped in `UnsafeCell` because + // Note: `route_holder`/`params_list_holder` are wrapped in `UnsafeCell` because // `route` (above) and `route_holder.params` hold raw self-referential pointers into // them. Without `UnsafeCell`, taking `&mut MatchedRoute` (as `get_params`/`get_query` // do) would assert unique access to these fields under Stacked Borrows and invalidate @@ -751,7 +754,7 @@ pub struct MatchedRoute { } impl MatchedRoute { - // PORT NOTE: `pub const js = jsc.Codegen.JSMatchedRoute; toJS/fromJS/fromJSDirect` + // Note: `pub const js = jsc.Codegen.JSMatchedRoute; toJS/fromJS/fromJSDirect` // wired by `#[bun_jsc::JsClass]` — deleted. #[inline] @@ -821,7 +824,7 @@ impl MatchedRoute { pathname_backing, needs_deinit: true, }); - // PORT NOTE: `base_dir.ref()` / `o.ref()` / `prefix.ref()` — bump refcounts. + // Note: `base_dir.ref()` / `o.ref()` / `prefix.ref()` — bump refcounts. // Each is a live interned `RefString` (caller-provided BackRef). base_dir.ref_(); if let Some(o) = origin { @@ -840,7 +843,7 @@ impl MatchedRoute { Ok(route) } - // PORT NOTE: `deinit` is called only from `finalize`; not exposed as `Drop` because + // Note: `deinit` is called only from `finalize`; not exposed as `Drop` because // `MatchedRoute` is a JsClass m_ctx payload (finalize owns teardown per PORTING.md). fn deinit(this: *mut MatchedRoute) { // SAFETY: called from finalize on mutator thread. @@ -848,7 +851,7 @@ impl MatchedRoute { this_ref.query_string_map.set(None); this_ref.param_map.set(None); if this_ref.needs_deinit { - // PORT NOTE: Zig did `if mi_is_in_heap_region(pathname.ptr) { mi_free(pathname.ptr) }` + // Note: Zig did `if mi_is_in_heap_region(pathname.ptr) { mi_free(pathname.ptr) }` // to free the leaked `path` from `match`. We own that allocation as // `pathname_backing`; dropping it (and `params_list_holder`) here releases the // borrowed bytes BEFORE `route_holder`'s slices would dangle on Box drop. @@ -951,7 +954,7 @@ impl MatchedRoute { pub fn get_script_src_string( origin: &URL, - // PORT NOTE: Zig used `comptime Writer: type, writer: Writer` over a fixedBufferStream of + // Note: Zig used `comptime Writer: type, writer: Writer` over a fixedBufferStream of // path bytes; `bun_object::get_public_path` takes `core::fmt::Write`. writer: &mut impl core::fmt::Write, file_path: &[u8], @@ -981,7 +984,7 @@ impl MatchedRoute { #[bun_jsc::host_fn(getter)] pub fn get_script_src(this: &Self, global_this: &JSGlobalObject) -> JsResult { - // PORT NOTE: Zig used `std.io.fixedBufferStream` over a PathBuffer. The accessible + // Note: Zig used `std.io.fixedBufferStream` over a PathBuffer. The accessible // `bun_object::get_public_path_with_asset_prefix` takes `core::fmt::Write`, so write // into a `String` (path components are UTF-8 in practice). let mut writer = String::with_capacity(MAX_PATH_BYTES); @@ -1068,7 +1071,7 @@ impl MatchedRoute { } } -// PORT NOTE: `bun.ThreadlocalBuffers(struct { buf: if (isWindows) [MAX_PATH_BYTES*2]u8 else void })` +// Note: `bun.ThreadlocalBuffers(struct { buf: if (isWindows) [MAX_PATH_BYTES*2]u8 else void })` // Heap-backed so only a Box pointer lives in TLS — a `const { [0u8; MAX_PATH_BYTES*2] }` // initializer here would put ~192 KB of zeros directly into the PE `.tls` section // (PE/COFF has no TLS-BSS). See test/js/bun/binary/tls-segment-size. diff --git a/src/runtime/api/glob.rs b/src/runtime/api/glob.rs index 7daf31452b82..d8624561d65e 100644 --- a/src/runtime/api/glob.rs +++ b/src/runtime/api/glob.rs @@ -193,8 +193,7 @@ impl ScanOpts { } pub(crate) struct WalkTask<'a> { - // PORT NOTE: Zig `WalkTask.deinit` did `walker.deinit(true); destroy(walker)`. - // `Box` drop runs `GlobWalker::Drop` (≡ `deinit(true)`) then frees. + // `Box` drop runs `GlobWalker::Drop` then frees the box. walker: Box, err: Option, global: &'a JSGlobalObject, @@ -220,16 +219,11 @@ impl WalkTaskErr { pub(crate) type AsyncGlobWalkTask<'a> = ConcurrentPromiseTask<'a, WalkTask<'a>>; impl<'a> WalkTask<'a> { - // PORT NOTE: Zig returned `!*AsyncGlobWalkTask` (the only `try` was the heap - // allocation). With the global mimalloc allocator `Box::new` is infallible - // (panics on OOM), so the Rust port returns the boxed task directly. pub(crate) fn create( global_this: &'a JSGlobalObject, glob_walker: Box, has_pending_activity: &'a AtomicUsize, ) -> Box> { - // PORT NOTE: Zig returned `!*AsyncGlobWalkTask` (alloc OOM); Rust `Box::new` - // is infallible (panics on OOM via mimalloc), so no error variant. let walk_task = Box::new(WalkTask { walker: glob_walker, global: global_this, @@ -243,7 +237,6 @@ impl<'a> WalkTask<'a> { impl<'a> ConcurrentPromiseTaskContext for WalkTask<'a> { const TASK_TAG: bun_event_loop::TaskTag = bun_event_loop::task_tag::AsyncGlobWalkTask; fn run(&mut self) { - // PORT NOTE: `defer decrPendingActivityFlag(...)` — runs on all paths. let guard = scopeguard::guard(self.has_pending_activity, |hpa| { decr_pending_activity_flag(hpa); }); @@ -265,8 +258,7 @@ impl<'a> ConcurrentPromiseTaskContext for WalkTask<'a> { } fn then(&mut self, promise: &mut JSPromise) -> Result<(), JsTerminated> { - // PORT NOTE: Zig `defer this.deinit()` freed walker + self. Ownership of - // `Box` is held by `ConcurrentPromiseTask.ctx`; the wrapper is + // Ownership of `Box` is held by `ConcurrentPromiseTask.ctx`; the wrapper is // freed via `ConcurrentPromiseTask::destroy` on the `.manual_deinit` path // after `run_from_js` returns, which drops `ctx` (and thus `walker`). @@ -277,7 +269,7 @@ impl<'a> ConcurrentPromiseTaskContext for WalkTask<'a> { let js_strings = match glob_walk_result_to_js(&mut self.walker, self.global) { Ok(v) => v, - // PORT NOTE: `error.JSError` → pass the JsError through; reject() pulls the pending exception. + // `reject()` pulls the pending exception off the VM. Err(e) => return promise.reject(self.global, Err(e)), }; promise.resolve(self.global, js_strings) @@ -293,9 +285,6 @@ fn glob_walk_result_to_js( return JSValue::create_empty_array(global_this, 0); } - // PORT NOTE: Zig keyed `MatchedMap` on `bun.String` so it could call - // `BunString.toJSArray(keys)` directly. The Rust `MatchedMap` is - // `StringArrayHashMap<()>` (Box<[u8]> keys), so rebuild the JS array here. JSValue::create_array_from_iter(global_this, keys.iter(), |key| { bun_string_jsc::create_utf8_for_js(global_this, key) }) @@ -322,11 +311,6 @@ impl Glob { let error_on_broken_symlinks = match_opts.error_on_broken_symlinks; let only_files = match_opts.only_files; - // PORT NOTE: Zig stack-inits `GlobWalker = .{}` then calls `.init()` / - // `.initWithCwd()` as out-param mutators. The Rust `GlobWalker` reshaped - // those into associated constructors returning `Result>`, so - // there is no `Default` and no separate allocation step. - // `errdefer alloc.destroy(globWalker)` is handled by Box drop on `?` paths. let _ = arena; // arena ownership is no longer threaded through GlobWalker init. if let Some(cwd) = cwd { @@ -365,7 +349,7 @@ impl Glob { Ok(Some(glob_walker)) } - // PORT NOTE: no `#[bun_jsc::host_fn]` here — the `#[bun_jsc::JsClass]` derive on + // No `#[bun_jsc::host_fn]` here — the `#[bun_jsc::JsClass]` derive on // the struct already emits the `GlobClass__construct` shim that calls // `::constructor(..)`. The free-fn `host_fn` expansion can't name an // associated fn without a receiver. @@ -428,8 +412,8 @@ impl Glob { // `arguments` drops at scope exit. let mut arena = Arena::new(); - // PORT NOTE: GlobWalker::init/init_with_cwd own their allocations (Box) in - // the Rust port; the arena here is vestigial and only mirrors Zig structure. + // GlobWalker::init/init_with_cwd own their allocations (Box); the + // arena here is vestigial. let glob_walker = match self.make_glob_walker(global_this, &mut arguments, "scan", &mut arena) { Err(err) => { @@ -444,15 +428,12 @@ impl Glob { }; incr_pending_activity_flag(&self.has_pending_activity); - // PORT NOTE: Zig `catch { decr; deinit; throwOOM }` handled alloc failure. - // Rust `Box::new` is infallible (panics via mimalloc on OOM), so the error - // arm collapses; `glob_walker` is moved in and dropped on unwind. let mut task = WalkTask::create(global_this, glob_walker, &self.has_pending_activity); let promise = task.promise.value(); task.schedule(); // Ownership passes to the work pool / event loop; freed via // `ConcurrentPromiseTask::destroy` on the `.manual_deinit` path. - // PORT NOTE: lifetime — WalkTask<'_> borrows `&self.has_pending_activity` + // WalkTask<'_> borrows `&self.has_pending_activity` // and `global_this`. Both referents outlive the task: `Glob` is GC-rooted // via `hasPendingActivity()`, and `JSGlobalObject` lives until VM teardown. // `into_raw` erases the stack-tied `'_` once the heap allocation escapes. diff --git a/src/runtime/api/html_rewriter.rs b/src/runtime/api/html_rewriter.rs index 40d271a355d8..18bd069e9e12 100644 --- a/src/runtime/api/html_rewriter.rs +++ b/src/runtime/api/html_rewriter.rs @@ -14,7 +14,7 @@ use bun_jsc::{ self as jsc, CallFrame, GlobalRef, JSGlobalObject, JSValue, JsCell, JsResult, ProtectedJSValue, StrongOptional, SystemError, bun_string_jsc, }; -// PORT NOTE: `bun_jsc::VirtualMachine` is a *module* re-export +// Note: `bun_jsc::VirtualMachine` is a *module* re-export // (`pub use self::virtual_machine as VirtualMachine;`). The struct lives at // `bun_jsc::virtual_machine::VirtualMachine` — import that directly so the // name resolves as a type at `&mut VirtualMachine` annotations and as the @@ -24,7 +24,7 @@ use bun_jsc::virtual_machine::VirtualMachine; // (`to_js`, `with_encoding`, …) come from the `ZigStringJsc` extension trait. use bun_jsc::ZigStringJsc as _; use bun_jsc::zig_string::ZigString; -// PORT NOTE: there is no `bun_lolhtml` safe-wrapper crate yet — the safe +// Note: there is no `bun_lolhtml` safe-wrapper crate yet — the safe // surface lives directly in `bun_lolhtml_sys::lol_html`. Code below references // both `lolhtml::Foo` (safe wrappers) and `lolhtml_sys::Foo` (raw opaque // handles); they resolve to the same module, so alias both names. @@ -64,7 +64,7 @@ type SelectorMap = Vec<*mut lolhtml::HTMLSelector>; // ─────────────────── wrapInstanceMethod arg-decode helpers ─────────────── // -// PORT NOTE: Zig's `host_fn.wrapInstanceMethod` is a comptime +// Note: Zig's `host_fn.wrapInstanceMethod` is a comptime // type-directed argument decoder (see host_fn.zig:493-648). The // `#[bun_jsc::host_fn(method)]` proc-macro that will eventually replace it // hasn't landed, so the per-type decode arms used by HTMLRewriter @@ -233,7 +233,7 @@ pub struct HTMLRewriter { } impl HTMLRewriter { - // PORT NOTE: no `#[bun_jsc::host_fn]` here — `#[bun_jsc::JsClass]` on the + // Note: no `#[bun_jsc::host_fn]` here — `#[bun_jsc::JsClass]` on the // struct already emits the C-ABI constructor shim that calls // `::constructor(__g, __f)`. pub fn constructor( @@ -344,12 +344,12 @@ impl HTMLRewriter { pub fn finalize_without_destroy(&self) { // context: Rc drop happens via field drop; builder needs explicit FFI deinit. + // Zig called `context.deref()` here explicitly; the Rc release happens + // when the HTMLRewriter box is dropped instead. The only caller is + // `finalize` above, which consumes `Box` and drops it + // immediately after this returns, so the two are equivalent. // SAFETY: builder was created by Builder::init() and not yet freed. unsafe { lolhtml::HTMLRewriterBuilder::destroy(self.builder) }; - // TODO(port): Zig calls context.deref() here explicitly; with Rc the - // drop happens when HTMLRewriter is dropped. If finalize_without_destroy - // is called without immediate drop, we'd want to swap context to a - // fresh Rc. Verify call sites. } pub fn begin_transform( @@ -369,7 +369,7 @@ impl HTMLRewriter { global: &JSGlobalObject, response_value: JSValue, ) -> JsResult { - // PORT NOTE: `Response` doesn't yet impl `JsClass`, so use the + // Note: `Response` doesn't yet impl `JsClass`, so use the // codegen `from_js` directly instead of `JSValue::as_::()`. if let Some(response) = webcore::response::js::from_js(response_value).map(|p| p.cast::()) @@ -513,9 +513,11 @@ pub struct HTMLRewriterLoader { pub context: Rc>, pub chunk_size: usize, pub failed: bool, - // TODO(port): lifetime — Zig `Sink` stores `*anyopaque` (no borrow). Rust - // `Sink<'a>` borrows its handler; the destination handler outlives this - // loader (set in `setup()`), so the lifetime is erased to `'static` here. + // Invariant: Zig `Sink` stores `*anyopaque` (no borrow). Rust `Sink<'a>` + // borrows its handler; the destination handler outlives this loader (the + // caller of `setup()` guarantees it), so `'static` stands in for that + // cross-object lifetime — the handler registration is type-erased through + // FFI, so a real lifetime parameter cannot be threaded here. pub output: webcore::Sink<'static>, pub signal: Signal, pub backpressure: LinearFifo>, @@ -571,7 +573,7 @@ impl HTMLRewriterLoader { self.done(); } Writable::Pending(pending) => { - // PORT NOTE: Zig calls `pending.applyBackpressure(allocator, + // Note: Zig calls `pending.applyBackpressure(allocator, // &this.output, pending, bytes)` — that decl does not exist in // the Zig source (dead code; HTMLRewriterLoader.sink() is never // referenced so Zig never compiles this arm). Mirror the call @@ -592,7 +594,7 @@ impl HTMLRewriterLoader { ); } Writable::Done => { - // PORT NOTE: Zig switch omits `.done` (dead code never + // Note: Zig switch omits `.done` (dead code never // compiled there); route it through `done()` like the other // *AndDone arms rather than silently swallowing it. self.done(); @@ -630,7 +632,7 @@ impl HTMLRewriterLoader { Ok(r) => r, Err(_) => { let _ = output.end(None); // error already surfaced via signal/fail path - // PORT NOTE: Zig returned a borrowed `[]const u8` into + // Note: Zig returned a borrowed `[]const u8` into // lol-html's threadlocal last-error buffer. Rust can't return a // slice tied to a temporary, so return the owning `HTMLString` // (caller calls `.slice()` then `.deinit()`). @@ -652,7 +654,7 @@ impl HTMLRewriterLoader { webcore::Sink::init(self) } - // PORT NOTE: The Zig spec (html_rewriter.zig:346-356) does not deinit on + // Note: The Zig spec (html_rewriter.zig:346-356) does not deinit on // the error path at all — matched here exactly: only the Owned* arms free, // and only on success (caller wraps owned bytes in `ManuallyDrop` and // takes them back out on the success path). @@ -693,7 +695,7 @@ impl HTMLRewriterLoader { } pub fn end(&mut self, err: Option) -> bun_sys::Result<()> { - // PORT NOTE: Zig HTMLRewriterLoader has no `end` (sink() is dead code + // Note: Zig HTMLRewriterLoader has no `end` (sink() is dead code // there). On input-stream end, flush the rewriter (which calls // OutputSink::done → self.done()) or fail. if let Some(e) = err { @@ -733,7 +735,10 @@ pub struct BufferOutputSink { pub response: *mut Response, // BORROW_FIELD: kept alive by response_value Strong pub response_value: StrongOptional, pub body_value_bufferer: Option>, - pub tmp_sync_error: Option>, // TODO(port): lifetime — points at a stack local in init() + // Points at the `sink_error` stack local in `init()` (mirrors the Zig + // `?*JSC.JSValue` field); only written while `init()` is on the stack. + // See `write_tmp_sync_error` for the full liveness/provenance argument. + pub tmp_sync_error: Option>, } impl BufferOutputSink { @@ -778,7 +783,7 @@ impl BufferOutputSink { // defer sink.deref(); // SAFETY: `sink` is the `heap::into_raw` allocation above; refcount >= 1. let _sink_guard = unsafe { bun_ptr::ScopedRef::::adopt(sink) }; - // PORT NOTE: do not hold a long-lived `&mut *sink` here — the same + // Note: do not hold a long-lived `&mut *sink` here — the same // allocation is also written through the raw pointer by the lol-html // output-sink callback during `bufferer.run()` and by `deref(sink)` // below. Access fields via raw-pointer place expressions instead. @@ -799,7 +804,7 @@ impl BufferOutputSink { // SAFETY: sink was just allocated via heap::alloc above; refcount==1. unsafe { (*sink).response = result }; - // PORT NOTE (Stacked Borrows): `sink_error` is written via raw pointer + // Note (Stacked Borrows): `sink_error` is written via raw pointer // by the unhandled-rejection handler during `bufferer.run()` and via // `tmp_sync_error` from `on_finished_buffering`. Use a `Cell` so the // exported `*mut` (via `Cell::as_ptr`, i.e. `UnsafeCell::get`) carries @@ -838,7 +843,7 @@ impl BufferOutputSink { // SAFETY: builder valid; sink outlives rewriter (deinit in Drop). Pass // the raw `sink` (heap::alloc root) directly so the userdata pointer // stored in the C rewriter shares provenance with every other - // `(*sink).field` access in this module — see the PORT NOTE on + // `(*sink).field` access in this module — see the Note on // `HTMLRewriterBuilder::build`. let built = unsafe { (*builder).build( @@ -880,7 +885,7 @@ impl BufferOutputSink { ); // https://github.com/oven-sh/bun/issues/3334 - // PORT NOTE: `clone_this` takes `&mut self`, so use the `_mut` + // Note: `clone_this` takes `&mut self`, so use the `_mut` // accessor (original is `*mut Response`). `clone_this` only reads // `self` (FFI mutates a freshly-allocated clone, not the receiver). if let Some(headers) = (*original).get_init_headers_mut() { @@ -910,7 +915,7 @@ impl BufferOutputSink { (*sink).ref_(); (*sink).body_value_bufferer = Some(webcore::body::ValueBufferer::init( sink.cast::(), - // PORT NOTE: `ValueBuffererCallback` takes `*mut c_void` for ctx; + // Note: `ValueBuffererCallback` takes `*mut c_void` for ctx; // `on_finished_buffering` takes `*mut BufferOutputSink`. The // wrapper trampoline restores the concrete type. Self::on_finished_buffering_trampoline, @@ -950,7 +955,7 @@ impl BufferOutputSink { } // sync error occurs — read via the Cell (shares SharedReadWrite - // provenance with the raw-pointer writers; see PORT NOTE above). + // provenance with the raw-pointer writers; see Note above). let captured = sink_error.get(); if !captured.is_empty() { captured.ensure_still_alive(); @@ -987,7 +992,7 @@ impl BufferOutputSink { // SAFETY: `sink` was ref'd in `init()` before scheduling this callback; // refcount > 0 so the allocation is live. `adopt` consumes that +1 on Drop. let _g = unsafe { bun_ptr::ScopedRef::::adopt(sink) }; - // PORT NOTE: do not materialise `&mut *sink` here — the lol-html + // Note: do not materialise `&mut *sink` here — the lol-html // write/end FFI calls below re-enter `::write/done` through the userdata pointer, which forms // its own `&mut *sink`. Holding an outer `&mut` across that re-entry @@ -1046,7 +1051,7 @@ impl BufferOutputSink { } } - /// PORT NOTE: takes `*mut Self` (not `&mut self`) because + /// Note: takes `*mut Self` (not `&mut self`) because /// `lolhtml::HTMLRewriter::write/end` re-enter /// `::write/done(&mut self)` through the /// userdata pointer registered at build time. A `&mut self` receiver here @@ -1399,7 +1404,7 @@ where // (R-2); aliased `&H` is sound, aliased `&mut H` is not. let this = unsafe { &*this }; let global = this.global(); - // PORT NOTE: spec (html_rewriter.zig:938,954,969,972) re-derives + // Note: spec (html_rewriter.zig:938,954,969,972) re-derives // `this.global.bunVM()` at each use site rather than caching a `&mut`. // `cb.call(...)` and `wait_for_promise(...)` re-enter JS / the event loop, // which mutate the same VirtualMachine through `global.bun_vm()` (and a @@ -1466,7 +1471,7 @@ where } if !result.is_undefined_or_null() { - // PORT NOTE: spec is `result.isError() or result.isAggregateError(global)` + // Note: spec is `result.isError() or result.isAggregateError(global)` // (html_rewriter.zig:964) — NOT `isAnyError`, which has different // coverage (Exception cells / `Symbol.error` vs cross-realm // AggregateError). @@ -1865,7 +1870,7 @@ impl Comment { html_string_to_js(comment.get_text(), global_object) } - // PORT NOTE: no `#[bun_jsc::host_fn(setter)]` — generated_classes.rs already + // Note: no `#[bun_jsc::host_fn(setter)]` — generated_classes.rs already // emits `CommentPrototype__setText` via `host_setter_result` (which wants // `JsResult<()>`); the proc-macro shim would emit a second, conflicting // `JsResult` wrapper. @@ -1907,9 +1912,9 @@ pub struct EndTag { } pub struct EndTagHandler { - // TODO(port): bare JSValue heap field kept alive via JSC gcProtect — - // evaluate bun_jsc::Strong (see DocumentHandler note). - pub callback: Option, + // GC-rooted via `ProtectedJSValue` (RAII protect/unprotect), matching + // `DocumentHandler`/`ElementHandler` — self-unprotects on drop. + pub callback: Option, pub global: GlobalRef, // JSC_BORROW } @@ -1919,7 +1924,7 @@ impl EndTagHandler { this, value, |w| w.end_tag.set(core::ptr::null_mut()), - |h| h.callback, + |h| h.callback.as_ref().map(ProtectedJSValue::value), ) } @@ -1973,7 +1978,7 @@ impl EndTag { html_string_to_js(end_tag.get_name(), global_object) } - // PORT NOTE: no `#[bun_jsc::host_fn(setter)]` — generated_classes.rs already + // Note: no `#[bun_jsc::host_fn(setter)]` — generated_classes.rs already // emits `EndTagPrototype__setName` via `host_setter_result`. pub fn set_name(&self, global: &JSGlobalObject, value: JSValue) -> JsResult<()> { let Some(end_tag) = lolhtml::EndTag::from_ptr(self.end_tag.get()) else { @@ -2184,9 +2189,11 @@ impl Element { return Ok(ZigString::init_utf8(b"Expected a function").to_js(global_object)); } + // `protected()` takes the gcProtect ref up front; if registration + // fails below, dropping the handler box unprotects it again. let end_tag_handler = bun_core::heap::into_raw(Box::new(EndTagHandler { global: GlobalRef::from(global_object), - callback: Some(function), + callback: Some(function.protected()), })); if el @@ -2202,7 +2209,6 @@ impl Element { return Err(global_object.throw_value(err)); } - function.protect(); Ok(call_frame.this()) } @@ -2387,7 +2393,7 @@ impl Element { html_string_value(el.tag_name(), global_object) } - // PORT NOTE: no `#[bun_jsc::host_fn(setter)]` — generated_classes.rs already + // Note: no `#[bun_jsc::host_fn(setter)]` — generated_classes.rs already // emits `ElementPrototype__setTagName` via `host_setter_result`. pub fn set_tag_name(&self, global: &JSGlobalObject, value: JSValue) -> JsResult<()> { let Some(el) = lolhtml::Element::from_ptr(self.element.get()) else { diff --git a/src/runtime/api/js_bundle_completion_task.rs b/src/runtime/api/js_bundle_completion_task.rs index 54318402d906..a9cc1d0e22f0 100644 --- a/src/runtime/api/js_bundle_completion_task.rs +++ b/src/runtime/api/js_bundle_completion_task.rs @@ -275,9 +275,6 @@ impl JSBundleCompletionTask { /// Port of `JSBundleCompletionTask.doCompilation`. fn do_compilation(&mut self, output_files: &mut Vec) -> CompileResult { - // PORT NOTE: reshaped for borrowck — `self.config` is reborrowed for - // every field projection so the `&mut self` receiver stays usable for - // `self.env` below. let compile_options = self .config .compile @@ -303,8 +300,6 @@ impl JSBundleCompletionTask { // Always get an absolute path for the outfile to ensure it works // correctly with PE metadata operations. // Add .exe extension for Windows targets if not already present. - // PORT NOTE: collapsed to a single owned `Box<[u8]>` so the - // `&mut outbuf` borrow ends before the rest of `self` is touched. let full_outfile_path: Box<[u8]> = { let outdir_slice = &self.config.outdir.list; let outfile_slice = &compile_options.outfile.list; @@ -442,10 +437,8 @@ impl JSBundleCompletionTask { // keep them in the output array. Destroy all other non-entry-point files. // With --splitting, there can be multiple sourcemap files (one per chunk). let mut kept: usize = 0; - // PORT NOTE: reshaped for borrowck — Zig wrote `output_files.items[kept] - // = current.*` while iterating `&mut output_files.items`. Swap-compact in - // place via index iteration so each loop body holds at most one `&mut` - // into `output_files`. + // Swap-compact in place via index iteration so each loop body holds + // at most one `&mut` into `output_files`. for i in 0..output_files.len() { let keep_this = if i == entry_point_index { true @@ -533,8 +526,7 @@ impl JSBundleCompletionTask { output_files.swap(kept, i); kept += 1; } - // PORT NOTE: Zig called `current.deinit()` on dropped entries; in - // Rust the trailing entries are dropped by `truncate` below. + // Trailing (dropped) entries are freed by `truncate` below. } output_files.truncate(kept); @@ -571,18 +563,17 @@ impl JSBundleCompletionTask { // instead of `*this` — `do_compilation`/`to_js_error` below need `&mut *this`. let global_this_ref = this.global_this; let global_this = global_this_ref.get(); - // PORT NOTE: `Strong::swap` ties the returned `&mut JSPromise` to + // `Strong::swap` ties the returned `&mut JSPromise` to // `&mut this.promise` even though the cell lives on the GC heap (raw // ptr deref inside). Detach via raw ptr so `this` can be reborrowed - // for `result`/`config`/`log` below — Zig stored `*JSPromise`. + // for `result`/`config`/`log` below. let promise: *mut JSPromise = this.promise.swap(); // SAFETY: GC-owned cell; valid for the duration of this JS-thread callback. let promise = unsafe { &mut *promise }; - // PORT NOTE: reshaped for borrowck — `do_compilation` borrows - // `&mut self` while needing `&mut output_files` from inside - // `self.result`. Temporarily move the Vec out via `take` so the - // method gets a disjoint `&mut self`. + // `do_compilation` borrows `&mut self` while needing + // `&mut output_files` from inside `self.result`. Temporarily move the + // Vec out via `take` so the method gets a disjoint `&mut self`. if matches!(this.result, BundleV2Result::Value(_)) && this.config.compile.is_some() { let mut output_files = match &mut this.result { BundleV2Result::Value(build) => core::mem::take(&mut build.output_files), @@ -614,9 +605,9 @@ impl JSBundleCompletionTask { } } - // PORT NOTE: reshaped for borrowck — `to_js_error` borrows `&mut self`, - // which would overlap a `&mut this.result` match scrutinee. Dispatch - // the pending/err arms first, then take a fresh `&mut` for Value. + // `to_js_error` borrows `&mut self`, which would overlap a + // `&mut this.result` match scrutinee. Dispatch the pending/err arms + // first, then take a fresh `&mut` for Value. if matches!(this.result, BundleV2Result::Pending) { unreachable!(); } @@ -637,10 +628,10 @@ impl JSBundleCompletionTask { ); } - // PORT NOTE: reshaped for borrowck — `output_file.to_js()` needs - // `&mut OutputFile` while the path computation reads - // `this.config`. Snapshot the config slices once outside the - // loop so the per-file `&mut` doesn't overlap `&this.config`. + // `output_file.to_js()` needs `&mut OutputFile` while the path + // computation reads `this.config`. Snapshot the config slices + // once outside the loop so the per-file `&mut` doesn't overlap + // `&this.config`. let outdir_is_abs = !this.config.outdir.is_empty() && bun_paths::is_absolute(&this.config.outdir.list); let outdir = this.config.outdir.list.clone(); @@ -825,9 +816,8 @@ impl CompletionStruct for JSBundleCompletionTask { transpiler.options.env.behavior = config.env_behavior; transpiler.options.env.prefix = Box::from(config.env_prefix.list.as_slice()); - // PORT NOTE: Zig `&config.features` (a `*StringSet` borrow). The Rust - // `BundleOptions.bundler_feature_flags: Option>` owns, - // so clone the set rather than alias. + // `BundleOptions.bundler_feature_flags: Option>` owns + // its set, so clone rather than alias `config.features`. transpiler.options.bundler_feature_flags = Some(Box::new(config.features.clone()?)); if config.force_node_env != options::ForceNodeEnv::Unspecified { transpiler.options.force_node_env = config.force_node_env; @@ -953,10 +943,8 @@ impl CompletionStruct for JSBundleCompletionTask { transpiler.options.compile = false; config.compile = None; } - // PORT NOTE: `BundleOptions.{banner,footer}` are `Cow<'static, [u8]>`; - // Zig assigned a borrow into `*JSBundleCompletionTask` (`this.config` - // outlives the build). Clone into Owned so the static bound holds - // without tying `&mut self` to `'a`. + // `BundleOptions.{banner,footer}` are `Cow<'static, [u8]>`; clone into + // Owned so the static bound holds without tying `&mut self` to `'a`. transpiler.options.banner = std::borrow::Cow::Owned(config.banner.list.clone()); transpiler.options.footer = std::borrow::Cow::Owned(config.footer.list.clone()); transpiler.options.react_fast_refresh = config.react_fast_refresh; @@ -1061,13 +1049,13 @@ impl CompletionStruct for JSBundleCompletionTask { extension_order: Vec::new(), env_files: Vec::new(), conditions: config.conditions.keys().to_vec(), - // PORT NOTE: Zig read `transpiler.options.ignore_dce_annotations` + // The Zig original read `transpiler.options.ignore_dce_annotations` // off the *uninitialized* out-param (i.e. whatever the previous - // build left there). The Rust port has no prior `Transpiler` here; - // use the config value, which `configure_bundler` reapplies anyway. + // build left there). There is no prior `Transpiler` here; use the + // config value, which `configure_bundler` reapplies anyway. ignore_dce_annotations: config.ignore_dce_annotations, drop: config.drop.keys().to_vec(), - // PORT NOTE: same uninitialized-read for `bunfig_path`; default empty. + // Same uninitialized-read in the original for `bunfig_path`; default empty. bunfig_path: Box::default(), jsx: Some(config.jsx.clone()), ..Default::default() @@ -1082,7 +1070,7 @@ impl CompletionStruct for JSBundleCompletionTask { let transpiler: &'a mut Transpiler<'a> = bump.alloc(t); // Post-init field wiring (the rest of Zig `configureBundler`). - // PORT NOTE: reborrow through a raw ptr so `&mut self` is usable + // Reborrow through a raw ptr so `&mut self` is usable // again after handing `&'a mut Transpiler` (which is tied to `bump`, // not `self`) to the trait method. let tp: *mut Transpiler<'a> = transpiler; diff --git a/src/runtime/api/output_file_jsc.rs b/src/runtime/api/output_file_jsc.rs index 59eb7ecd8020..a3bd7507b4ba 100644 --- a/src/runtime/api/output_file_jsc.rs +++ b/src/runtime/api/output_file_jsc.rs @@ -80,9 +80,9 @@ impl SavedFile { .expect("unreachable"); let blob = Blob::init_with_store(store, global_this); - // PORT NOTE: Zig overwrites `blob.content_type = mime.value` here; + // The Zig original overwrites `blob.content_type = mime.value` here; // `init_with_store` already populated it from the store's `File` - // mime (which is the same value), so the overwrite is a no-op. + // mime (the same value), so the overwrite is intentionally omitted. blob.size.set(byte_size as BlobSizeType); let ptr = Blob::new(blob); // SAFETY: `ptr` is a freshly heap-allocated `*mut Blob` from @@ -109,10 +109,9 @@ impl OutputFileJsc for OutputFile { _ => {} } - // PORT NOTE: each Zig arm reassigns `this.value = .buffer{.{}}` after - // consuming the payload. Taking the value out up-front avoids the - // borrowck conflict between `&mut self.value` (match scrutinee) and - // `self.{hash,loader,...}` reads inside the arms. + // Taking the value out up-front avoids the borrowck conflict between + // `&mut self.value` (match scrutinee) and `self.{hash,loader,...}` + // reads inside the arms. let value = core::mem::replace( &mut self.value, OutputFileValue::Buffer { diff --git a/src/runtime/api/standalone_graph_jsc.rs b/src/runtime/api/standalone_graph_jsc.rs index b2859394ce92..5dcfdcd60a6a 100644 --- a/src/runtime/api/standalone_graph_jsc.rs +++ b/src/runtime/api/standalone_graph_jsc.rs @@ -8,7 +8,7 @@ use bun_core::{self as bstring, strings}; use bun_http::MimeType; use bun_jsc::JSGlobalObject; -// PORT NOTE: `StandaloneModuleGraph` is the inner *module* (so +// `StandaloneModuleGraph` here is the inner *module* (so // `StandaloneModuleGraph::BASE_PUBLIC_PATH_WITH_DEFAULT_SUFFIX` resolves); // `File` is re-exported at the crate root. use crate::webcore::Blob; @@ -42,7 +42,7 @@ impl FileJsc for File { bun_alloc::basic::C_ALLOCATOR, ) }; - // PORT NOTE: cannot use `..Default::default()` — `Store: Drop` + // Cannot use `..Default::default()` — `Store: Drop` // forbids partial moves out of the temporary default. let store = StoreRef::from(Store::new(Store { data: Data::Bytes(bytes), diff --git a/src/runtime/bake/DevServer.rs b/src/runtime/bake/DevServer.rs index ce1504f82b38..215cd0e259d3 100644 --- a/src/runtime/bake/DevServer.rs +++ b/src/runtime/bake/DevServer.rs @@ -184,7 +184,7 @@ pub struct Options<'a> { pub dump_state_on_crash: Option, } -// PORT NOTE: Zig `Options` only had field-level defaults for `dump_sources` +// Note: Zig `Options` only had field-level defaults for `dump_sources` // (`if (Environment.isDebug) ".bake-debug" else null`) and `dump_state_on_crash` // (`null`). The remaining fields (`arena`, `root`, `vm`, `framework`, // `bundler_options`, `broadcast_console_log_from_browser_to_server`) are @@ -249,7 +249,7 @@ pub enum TestingBatchEvents { /// while in-flight. All allocations held by `.bv2.graph.heap`'s arena pub struct CurrentBundle { /// OWNED (LIFETIMES.tsv): `BundleV2.init()` → `deinitWithoutFreeingArena()`. - /// PORT NOTE: `'static` is a stand-in for the DevServer-self lifetime — + /// Note: `'static` is a stand-in for the DevServer-self lifetime — /// `BundleV2<'a>` borrows the three `Transpiler<'_>` fields stored inline /// in `DevServer`, so the true bound is the `Box` allocation /// (stable address, never moved post-init). Threading a real `'dev` would @@ -296,7 +296,7 @@ pub struct NextBundle { pub promise: DeferredPromise, } -// PORT NOTE: this is the **canonical** `DevServer` struct. `dev_server/mod.rs` +// Note: this is the **canonical** `DevServer` struct. `dev_server/mod.rs` // re-exports it (`pub use super::dev_server_body::DevServer`) so the // `container_of` submodules (`incremental_graph`, `assets`, …) and the // 4.8 kL of method bodies in this file all name the same type. The @@ -385,7 +385,7 @@ pub struct DevServer { pub server_register_update_callback: jsc::StrongOptional, // Watching - // PORT NOTE: Zig stores `*bun.Watcher` and calls `deinit(true)` in + // Note: Zig stores `*bun.Watcher` and calls `deinit(true)` in // `DevServer.deinit`, which *transfers* ownership to the watcher thread // (the thread frees the allocation in `threadMain`). Auto-dropping a // `Box` here would free the heap block while the watcher thread @@ -410,7 +410,7 @@ pub struct DevServer { pub bundler_framework_views: Vec<*mut bun_bundler::bake_types::Framework>, pub bundler_options: bake::SplitBundlerOptions, // Each logical graph gets its own bundler configuration. - // PORT NOTE: `'static` is the DevServer-self lifetime stand-in (see + // Note: `'static` is the DevServer-self lifetime stand-in (see // `CurrentBundle.bv2`). `Transpiler<'a>` borrows the global // `Fs::FileSystem` singleton + `dot_env::Loader`, both of which outlive // the server. @@ -498,7 +498,7 @@ impl DeferredPromise { /// DevServer is stored on the heap, storing its allocator. pub fn init(options: Options) -> JsResult> { - // PORT NOTE: `Features.dev_server +|= 1` (saturating add). AtomicUsize has + // Note: `Features.dev_server +|= 1` (saturating add). AtomicUsize has // no `saturating_inc`; on a 64-bit counter overflow is unreachable, so a // relaxed `fetch_add(1)` is equivalent in practice. bun_core::analytics::Features::DEV_SERVER.fetch_add(1, ::core::sync::atomic::Ordering::Relaxed); @@ -525,7 +525,7 @@ pub fn init(options: Options) -> JsResult> { .map(|sc| sc.separate_ssr_graph) .unwrap_or(false); - // PORT NOTE: Zig used `bun.new(DevServer, .{ ... = undefined })` then assigned + // Note: Zig used `bun.new(DevServer, .{ ... = undefined })` then assigned // `server_transpiler` / `client_transpiler` / `ssr_transpiler` / `bun_watcher` / // `router` / `watcher_atomics` after the heap address was stable. Rust forbids // partial struct literals and `mem::zeroed()` here is UB (`Box` is @@ -704,7 +704,7 @@ pub fn init(options: Options) -> JsResult> { // `bake_body.rs`), so the previous (uninitialized) bytes are never dropped. // `framework`/`log`/`bundler_options` were written above; reborrowing each // individually via `addr_of_mut!` is sound because no `&mut DevServer` exists. - // PORT NOTE: `Transpiler<'static>` erases the arena lifetime — `options.arena` + // Note: `Transpiler<'static>` erases the arena lifetime — `options.arena` // is the `UserOptions.arena` which is moved into / outlives the `DevServer` // box (Zig had no lifetime). Widen `'a → 'static` here once. // SAFETY: `options.arena` outlives every `Transpiler` field it backs (see @@ -755,7 +755,7 @@ pub fn init(options: Options) -> JsResult> { Err(err) => return Err(global.throw_error(err, generic_action)), } } else { - // PORT NOTE: Zig left `ssr_transpiler` `undefined` when + // Note: Zig left `ssr_transpiler` `undefined` when // `!separate_ssr_graph` and never read it. Rust must still write a // valid value before `assume_init()`. Bitwise-alias the server // transpiler. This is sound only because `Drop for DevServer` gates @@ -785,7 +785,7 @@ pub fn init(options: Options) -> JsResult> { let mut dev: Box = unsafe { dev_uninit.assume_init() }; let dev_ptr: *mut DevServer = &raw mut *dev; - // PORT NOTE: Zig asserted `*.owner() == dev` (intrusive parent-ptr derived + // Note: Zig asserted `*.owner() == dev` (intrusive parent-ptr derived // via `container_of`). The Rust port stores these by value; `owner()` // is provided on `IncrementalGraph` via `offset_of!` so the invariant is // structural. Retain the field touches so the addresses are stable for @@ -822,7 +822,7 @@ pub fn init(options: Options) -> JsResult> { debug_assert!(dev.server_transpiler().resolver.opts.target != bun_ast::Target::Browser); debug_assert!(dev.client_transpiler().resolver.opts.target == bun_ast::Target::Browser); - // PORT NOTE: reborrow `framework` and the two resolvers via `dev_ptr` so + // Note: reborrow `framework` and the two resolvers via `dev_ptr` so // borrowck doesn't see three overlapping `&mut dev`. // SAFETY: `dev_ptr` is the live `Box` heap address; the three // fields are disjoint. @@ -879,7 +879,7 @@ pub fn init(options: Options) -> JsResult> { Ok(s) => s, Err(e) => Output::panic(format_args!("unhandled {}", e)), }; - // PORT NOTE: `sys::Stat` is `libc::stat` on POSIX / `uv_stat_t` on + // Note: `sys::Stat` is `libc::stat` on POSIX / `uv_stat_t` on // Windows (where mtime is `mtim.sec`). Debug-only cache-bust key. #[cfg(not(windows))] bun_core::write_any_to_hasher(&mut h, stat.st_mtime as i64); @@ -982,7 +982,7 @@ pub fn init(options: Options) -> JsResult> { let mut types: Vec = Vec::with_capacity(dev.framework.file_system_router_types.len()); - // PORT NOTE: the loop body mutates `server_transpiler.resolver`, + // Note: the loop body mutates `server_transpiler.resolver`, // `server_graph`, `client_graph`, and `route_lookup` while iterating // `framework.file_system_router_types` by shared ref. All five fields // are disjoint; reborrow them through `dev_ptr` so the shared @@ -1082,8 +1082,63 @@ impl Drop for DevServer { // at usize::MAX is unreachable in practice, so a plain fetch_add is fine. DEV_SERVER_DEINIT_COUNT_FOR_TESTING.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - // TODO(port): Zig used `useAllFields(DevServer, .{...})` to ensure every field - // was visited. Only side-effecty cleanup is kept here. + // Exhaustiveness check (Zig: `useAllFields(DevServer, .{...})`): + // destructuring without `..` fails to compile when a field is added, + // removed, or renamed, forcing this Drop to be reviewed. All bindings + // are `_` so nothing is moved; cleanup not done explicitly below + // happens via the implicit field drops after this body returns. + { + let DevServer { + magic: _, + root: _, + inspector_server_id: _, + configuration_hash_key: _, + vm: _, + server: _, + router: _, + route_bundles: _, + graph_safety_lock: _, + client_graph: _, + server_graph: _, + barrel_files_with_deferrals: _, + barrel_needed_exports: _, + incremental_result: _, + route_lookup: _, + html_router: _, + assets: _, + source_maps: _, + bundling_failures: _, + frontend_only: _, + has_tailwind_plugin_hack: _, + server_fetch_function_callback: _, + server_register_update_callback: _, + bun_watcher: _, + directory_watchers: _, + watcher_atomics: _, + testing_batch_events: _, + generation: _, + bundles_since_last_error: _, + framework: _, + bundler_framework_views: _, + bundler_options: _, + server_transpiler: _, + client_transpiler: _, + ssr_transpiler: _, + log: _, + plugin_state: _, + current_bundle: _, + next_bundle: _, + deferred_request_pool: _, + active_websocket_connections: _, + dump_dir: _, + emit_incremental_visualizer_events: _, + emit_memory_visualizer_events: _, + memory_visualizer_timer: _, + has_pre_crash_handler: _, + assume_perfect_incremental_bundling: _, + broadcast_console_log_from_browser_to_server: _, + } = &*self; + } // WebSockets should be deinitialized before other parts. // `websocket.close()` synchronously dispatches `HmrSocket.onClose`, @@ -1133,7 +1188,7 @@ impl Drop for DevServer { bun_crash_handler::remove_pre_crash_handler(std::ptr::from_mut(self).cast::()); } - // PORT NOTE: Zig looped `failure.deinit()` then `clearAndFree`. The + // Note: Zig looped `failure.deinit()` then `clearAndFree`. The // map's `Drop` runs `SerializedFailure::drop` for each value, so the // explicit loop is folded into field drop. @@ -1287,7 +1342,7 @@ impl DevServer { /// Deferred one tick so that the server can be up faster fn scan_initial_routes(&mut self) -> Result<(), bun_core::Error> { - // PORT NOTE: reshaped for borrowck — Zig passed `dev` as the + // Note: reshaped for borrowck — Zig passed `dev` as the // `InsertionContext` while also borrowing `&mut dev.router` and // `&mut dev.server_transpiler.resolver`. Reborrow each via the raw // self-ptr (the three are disjoint fields of the heap-stable `*self`). @@ -1318,7 +1373,7 @@ impl DevServer { let app = unsafe { &mut *server.app.unwrap() }; let dev = std::ptr::from_mut::(self).cast::(); - // PORT NOTE: Zig's `wrapGenericRequestHandler(fn, is_ssl)` produced a + // Note: Zig's `wrapGenericRequestHandler(fn, is_ssl)` produced a // monomorphized `extern fn(*DevServer, *Response, *Request)` per // handler. The Rust equivalent is the ZST-fn-item trampoline pattern // used by `server::server_body::AppRouteExt`: a generic `extern "C"` @@ -1607,7 +1662,7 @@ impl bun_uws_sys::web_socket::WebSocketHandler for HmrSocket { const HAS_ON_PING: bool = false; const HAS_ON_PONG: bool = false; - // PORT NOTE (noalias re-entrancy): the trait now hands the handler a raw + // Note (noalias re-entrancy): the trait now hands the handler a raw // `*mut Self` (see `WebSocketHandler` doc). `HmrSocket::on_close` already // takes `*mut Self`; `on_open`/`on_message` still take `&mut self` so // re-derive a fresh `&mut *this` here (not carried in from a `noalias` @@ -1759,7 +1814,7 @@ fn on_js_request(dev: &mut DevServer, req: &mut Request, resp: AnyResponse) { if is_map { let source_id = source_map_store::SourceId(id); - // PORT NOTE: `render_json` needs `&mut DevServer` while `entry` borrows + // Note: `render_json` needs `&mut DevServer` while `entry` borrows // `dev.source_maps.entries[_]`. The entry is read-only (`&self`) and // `render_json` does not touch `source_maps.entries`, so erase the // entry borrow to a raw pointer and reborrow `dev` for the call. @@ -1877,7 +1932,7 @@ fn on_memory_visualizer_corked(resp: AnyResponse) { } struct RequestEnsureRouteBundledCtx { - // PORT NOTE: erased to raw pointer — the Zig code freely re-borrowed `dev` + // Note: erased to raw pointer — the Zig code freely re-borrowed `dev` // across the ctx; a `&mut DevServer` field would alias the caller's borrow. dev: *mut DevServer, req: ReqOrSaved, @@ -1899,7 +1954,7 @@ impl RequestEnsureRouteBundledCtx { } fn on_defer(&mut self, bundle_field: BundleQueueType) -> JsResult<()> { - // PORT NOTE: reshaped for borrowck — captured args before re-borrowing dev + // Note: reshaped for borrowck — captured args before re-borrowing dev let route_bundle_index = self.route_bundle_index; let kind = self.kind; let req = ::core::mem::replace(&mut self.req, ReqOrSaved::Aborted); @@ -1931,7 +1986,7 @@ impl RequestEnsureRouteBundledCtx { deferred_request::HandlerKind::ServerHandler => { let route_bundle_index = self.route_bundle_index; let resp = self.resp; - // PORT NOTE: Zig copied `SavedRequest` by value (POD); Rust's + // Note: Zig copied `SavedRequest` by value (POD); Rust's // `Strong` field is move-only. Take ownership out of `self.req` // (it is consumed by `on_framework_request_with_bundle`). let req = match ::core::mem::replace(&mut self.req, ReqOrSaved::Aborted) { @@ -1957,7 +2012,7 @@ impl RequestEnsureRouteBundledCtx { } fn on_failure(&mut self) -> JsResult<()> { - // PORT NOTE: Zig held two `*DevServer`-derived borrows at once + // Note: Zig held two `*DevServer`-derived borrows at once // (route_bundle slot + send_serialized_failures). Reborrow via raw // pointer so the failure slice and the `&mut DevServer` don't alias. let route_bundle_index = self.route_bundle_index; @@ -2250,7 +2305,7 @@ impl DevServer { weakly_referenced_by_requestcontext: false, handler: match kind { deferred_request::HandlerKind::BundledHtmlPage => 'brk: { - // PORT NOTE: `on_aborted` rejects `DeferredRequest`; + // Note: `on_aborted` rejects `DeferredRequest`; // erase to `c_void` and cast back inside the trampoline. resp.on_aborted( |p: *mut c_void, r: AnyResponse| { @@ -2347,12 +2402,12 @@ fn check_route_failures( ) -> Result { // PERF(port): was stack-fallback (65536) let mut gts = dev.init_graph_trace_state(0)?; - // PORT NOTE: erase to a raw pointer so the deferred cleanup only fires on + // Note: erase to a raw pointer so the deferred cleanup only fires on // scope exit when no other borrow of `dev` is live (Zig `defer` had no // aliasing check). let dev_ptr = std::ptr::from_mut::(dev); scopeguard::defer! { - // SAFETY: see PORT NOTE above. + // SAFETY: see Note above. unsafe { (*dev_ptr).incremental_result.failures_added.clear() } }; let _lock_guard = dev.graph_safety_lock.guard(); @@ -2409,7 +2464,7 @@ impl DevServer { let client_file_names = self.client_graph.bundled_files.keys(); // Build a list of all files that have not yet been bundled. - // PORT NOTE: split borrow — `route_bundle_ptr`/`type_ptr` are `&mut self`; + // Note: split borrow — `route_bundle_ptr`/`type_ptr` are `&mut self`; // index `route_bundles`/`types` immutably so the SoA key slices above stay live. match &self.route_bundles[rbi.get() as usize].data { route_bundle::Data::Framework(bundle) => { @@ -2467,7 +2522,6 @@ impl DevServer { } } -// TODO(port): move to _sys // C++ side defines `extern "C" SYSV_ABI` (BakeAdditionsToGlobalObject.cpp). bun_jsc::jsc_abi_extern! { safe fn Bake__getEnsureAsyncLocalStorageInstanceJSFunction(global: &JSGlobalObject) -> JSValue; @@ -2487,7 +2541,7 @@ struct FrameworkRequestArgs { } impl DevServer { - /// PORT NOTE: raw-pointer receiver. Zig freely held aliasing `*DevServer` / + /// Note: raw-pointer receiver. Zig freely held aliasing `*DevServer` / /// `*RouteBundle` / `*Router.Type` heap pointers across this body; the /// previous Rust port bound long-lived `&mut RouteBundle` / `&mut Type` from /// `&mut *self_ptr` and then reborrowed `&mut *self_ptr` again — overlapping @@ -2695,7 +2749,7 @@ impl DevServer { req: SavedRequestUnion, resp: AnyResponse, ) -> JsResult<()> { - // PORT NOTE: erase the `&mut RouteBundle` to a raw pointer so subsequent + // Note: erase the `&mut RouteBundle` to a raw pointer so subsequent // `&self` accesses (vm/router/callback) don't trip borrowck — Zig freely // aliased `dev` across this scope. let route_bundle: *mut RouteBundle = self.route_bundle_ptr(route_bundle_index); @@ -2745,7 +2799,7 @@ impl DevServer { // SAFETY: `self` is live; `framework_bundle` points into // `self.route_bundles[route_bundle_index].data`. Raw-ptr receiver — see - // PORT NOTE on `compute_arguments_for_framework_request`. + // Note on `compute_arguments_for_framework_request`. let args = unsafe { Self::compute_arguments_for_framework_request( self, @@ -2783,7 +2837,7 @@ impl DevServer { resp: AnyResponse, method: Method, ) { - // PORT NOTE: erase `self` to a raw pointer so the `route_bundle` borrow + // Note: erase `self` to a raw pointer so the `route_bundle` borrow // doesn't conflict with the `&mut self` calls below (Zig held these as // plain heap pointers). Per docs/PORTING.md §Global mutable state: hold // `*mut T` and deref per-access; do not bind a long-lived `&mut`. @@ -2853,7 +2907,7 @@ impl DevServer { route_bundle_index: route_bundle::Index, route_bundle: &RouteBundle, ) -> Result, bun_core::Error> { - // PORT NOTE: Zig passed `*RouteBundle` and `*RouteBundle.HTML` separately + // Note: Zig passed `*RouteBundle` and `*RouteBundle.HTML` separately // (overlapping); re-derive `html` here from `route_bundle.data` so the // caller never holds two `&mut` into the same allocation. `route_bundle` // is read-only in this fn — `&RouteBundle` suffices. @@ -3005,7 +3059,7 @@ impl DevServer { resp: AnyResponse, method: Method, ) { - // PORT NOTE: erase `self` to a raw pointer so `route_bundle` borrow + // Note: erase `self` to a raw pointer so `route_bundle` borrow // doesn't conflict with `generate_client_bundle(&mut self, ..)`. let self_ptr = std::ptr::from_mut::(self); // SAFETY: `self_ptr` accesses below touch disjoint fields of `*self`. @@ -3177,7 +3231,7 @@ impl DeferredRequest { "DeferredRequest(0x{:x}) deinitImpl", std::ptr::from_ref(self) as usize ); - // PORT NOTE: the pool stores `MaybeUninit` (no `Drop`), + // Note: the pool stores `MaybeUninit` (no `Drop`), // so the `Handler` payload must be torn down explicitly here. Swap to // `Aborted` (zero-payload) and let the moved-out value drop — for // `ServerHandler` this releases `saved.js_request: Strong` (the GC @@ -3209,7 +3263,7 @@ impl DeferredRequest { saved .ctx .set_signal_aborted(jsc::CommonAbortReason::ConnectionClosed); - // PORT NOTE: saved.js_request (jsc::Strong) drops at end of arm + // Note: saved.js_request (jsc::Strong) drops at end of arm drop(saved); } Handler::BundledHtmlPage(r) => { @@ -3259,7 +3313,7 @@ impl DevServer { } // Boxed so its address is stable: `bv2.graph.heap: &'static Arena` - // borrows it (self-ref via `CurrentBundle`, see PORT NOTE on + // borrows it (self-ref via `CurrentBundle`, see Note on // `CurrentBundle.bv2`). let heap: Box = Box::new(bun_alloc::MimallocArena::new()); // Borrows `heap` (Zig parity), so AST nodes built during bundle setup @@ -3299,7 +3353,7 @@ impl DevServer { // `CurrentBundle` (which also owns `bv2`); the boxed arena's address // is stable for the lifetime of `bv2.graph.heap`'s borrow. let heap_ptr: *const bun_alloc::Arena = &raw const *heap; - // PORT NOTE: split `&mut self` into disjoint field reborrows so + // Note: split `&mut self` into disjoint field reborrows so // `server_transpiler` (`&'a mut`) and `client/ssr_transpiler` // (NonNull) don't trip the single-`&mut self` rule. let self_ptr = std::ptr::from_mut::(self); @@ -3484,7 +3538,7 @@ impl DevServer { } } - // PORT NOTE: iterate by index — the loop bodies need + // Note: iterate by index — the loop bodies need // `&mut self.route_bundles` / `&mut self.router`, which conflicts // with the `&self.incremental_result` iterator borrow. for i in 0..self.incremental_result.framework_routes_affected.len() { @@ -3548,7 +3602,7 @@ impl DevServer { debug_assert!(route_bundle.server_state == route_bundle::State::Loaded); let _lock = self.graph_safety_lock.guard(); - // PORT NOTE: scopeguard closures capture `self` by ref, wedging borrowck for + // Note: scopeguard closures capture `self` by ref, wedging borrowck for // the rest of the fn. Erase to a raw pointer (Zig `defer` had no aliasing check). let self_ptr: *mut Self = self; @@ -3561,7 +3615,7 @@ impl DevServer { // `current_chunk_parts`/`current_chunk_len` are scratch buffers shared with // the HMR pipeline. We must leave them cleared on every exit path. // SAFETY: see `self_ptr` SAFETY above. - // PORT NOTE: copy `self_ptr` so the `defer!` closure captures a distinct + // Note: copy `self_ptr` so the `defer!` closure captures a distinct // local — otherwise borrowck treats `*self_ptr` as held for the guard's // lifetime and rejects later `&mut (*self_ptr).…` reborrows. let self_ptr_defer: *mut Self = self_ptr; @@ -3624,7 +3678,7 @@ impl DevServer { source_map_store::PutOrIncrementRefCount::Shared(_) => {} } - // PORT NOTE: `take_js_bundle` mutably borrows `client_graph` while + // Note: `take_js_bundle` mutably borrows `client_graph` while // `initial_entry` would alias `client_graph.bundled_files.keys()[idx]`. // Clone the key (a short path string) so the borrow ends before the // `&mut client_graph` call; cold path (per-route bundle finalize). @@ -3706,7 +3760,7 @@ impl DevServer { match &route_bundle.data { route_bundle::Data::Framework(fw) => { let mut route = self.router.route_ptr(fw.route_index); - // PORT NOTE: copy the two `Type` fields up front so the + // Note: copy the two `Type` fields up front so the // `&self.router` borrow doesn't overlap `&mut self.*_graph`. let (rt_server_file, rt_client_file) = { let rt = self.router.type_ptr_const(route.r#type); @@ -3857,19 +3911,26 @@ pub(super) fn finalize_bundle( result: &mut bundler::bundle_v2::DevServerOutput, ) -> JsResult<()> { debug_assert!(dev.magic == Magic::Valid); - // PORT NOTE: `had_sent_hmr_event` is read inside the outer-defer scopeguard + // Note: `had_sent_hmr_event` is read inside the outer-defer scopeguard // and mutated later in the body. Use a `Cell` so the closure capture is `&Cell` // (shared), letting the body keep writing through `.set()`. let had_sent_hmr_event = ::core::cell::Cell::new(false); - // TODO(port): the giant `defer` block at the start of finalizeBundle has been - // moved into a scopeguard. Phase B must verify ordering relative to ?-returns. - // PORT NOTE: erase `dev`/`bv2` to raw pointers inside the guard so the + // The giant `defer` block at the start of Zig finalizeBundle is a + // scopeguard here. Ordering relative to `?`-returns verified against + // DevServer.zig:2247-2290: both Zig defers (outer cleanup, then + // current_bundle request drain) are registered before the first fallible + // operation, as are both `defer!`s below — the only statements in between + // are infallible (raw-pointer copies and the `expect` on an invariant Zig + // also asserts). Guards run LIFO (drain → outer), matching Zig, and the + // `graph_safety_lock` guard declared after them drops first, matching + // Zig's `defer unlock`. + // Note: erase `dev`/`bv2` to raw pointers inside the guard so the // long-lived closure capture doesn't lock borrowck for the entire fn body // (Zig `defer` had no aliasing analysis). let dev_ptr = std::ptr::from_mut::(dev); let bv2_ptr = std::ptr::from_mut::(bv2); - // PORT NOTE: copy the raw ptrs into closure-only locals so `defer!`'s by-ref + // Note: copy the raw ptrs into closure-only locals so `defer!`'s by-ref // capture does not hold `*dev_ptr`/`*bv2_ptr` borrowed for the entire fn body. let dev_ptr_outer: *mut DevServer = dev_ptr; let bv2_ptr_outer: *mut BundleV2 = bv2_ptr; @@ -3915,7 +3976,7 @@ pub(super) fn finalize_bundle( } }; - // PORT NOTE: holding `&mut CurrentBundle` for the rest of the fn locks `*dev` + // Note: holding `&mut CurrentBundle` for the rest of the fn locks `*dev` // mutably. Erase to a raw pointer and reborrow at each use site via the // `current_bundle!()` macro (Zig freely re-aliased `dev.current_bundle.?`). let current_bundle_ptr: *mut CurrentBundle = dev @@ -3932,7 +3993,7 @@ pub(super) fn finalize_bundle( unsafe { &mut *current_bundle_ptr } }; } - // PORT NOTE: see `dev_ptr_outer` rationale above — separate copy for the + // Note: see `dev_ptr_outer` rationale above — separate copy for the // `defer!` capture so `current_bundle!()` keeps reborrowing freely. let current_bundle_ptr_defer: *mut CurrentBundle = current_bundle_ptr; scopeguard::defer! { @@ -3956,13 +4017,13 @@ pub(super) fn finalize_bundle( let _lock = dev.graph_safety_lock.guard(); - // PORT NOTE: `js_pseudo_chunk()`/`css_chunks()`/`html_chunks()` each take + // Note: `js_pseudo_chunk()`/`css_chunks()`/`html_chunks()` each take // `&mut DevServerOutput`, so calling more than one wedges borrowck. Split // `result.chunks` once up front (the three regions are disjoint: // `[0] | [1..1+n_css] | [1+n_css..1+n_css+n_html]`). let n_css = result.css_file_list.count(); let n_html = result.html_files.count(); - // PORT NOTE: snapshot `result.chunks` ptr/len before `split_at_mut` so the + // Note: snapshot `result.chunks` ptr/len before `split_at_mut` so the // CSS-chunk loop can re-form the full slice for `intermediate_output.code()` // without re-borrowing `result.chunks` (already split). let chunks_ptr: *mut bundler::chunk::Chunk = result.chunks.as_mut_ptr(); @@ -4003,7 +4064,7 @@ pub(super) fn finalize_bundle( let mut resolved_index_cache = vec![CachedFileIndex::NONE; input_file_sources.len() * 2]; - // PORT NOTE: ctx fields `server_seen_bit_set`/`gts` were `undefined` in Zig then + // Note: ctx fields `server_seen_bit_set`/`gts` were `undefined` in Zig then // assigned AFTER Pass 1 (receive_chunk grows `bundled_files`, so the trace bitsets // must be sized post-Pass-1). Seed with empty placeholders; real init below. let mut gts_storage = GraphTraceState { @@ -4101,7 +4162,7 @@ pub(super) fn finalize_bundle( let index = bun_ast::Index::init(chunk.entry_point.source_index()); - // PORT NOTE: `IntermediateOutput::code` takes `&mut self` (a field of + // Note: `IntermediateOutput::code` takes `&mut self` (a field of // `*chunk`) plus `chunk: &Chunk` and `chunks: &[Chunk]`; borrowck still // rejects the overlap (`&mut chunk.intermediate_output` aliases the // shared `&chunk` / `&chunks[..]`). Zig aliased freely. Split via raw @@ -4137,7 +4198,7 @@ pub(super) fn finalize_bundle( // TODO: use a hash mix with the first half being a path hash and the second half content hash let h = hash(key); // Track css files that look like tailwind files. - // PORT NOTE: hoisted before `replace_path` because that consumes + // Note: hoisted before `replace_path` because that consumes // `code.buffer`; same observable order as Zig (buffer is identical // pre- and post-asset-registration there). let looks_like_tailwind = dev.has_tailwind_plugin_hack.is_some() && { @@ -4158,7 +4219,7 @@ pub(super) fn finalize_bundle( if let Some(map) = &mut dev.has_tailwind_plugin_hack { if looks_like_tailwind { - // PORT NOTE: `get_or_put` consumes the key by value; on miss the key + // Note: `get_or_put` consumes the key by value; on miss the key // already lives in the map so the explicit `*key_ptr =` is redundant. let _ = map.get_or_put(Box::from(key))?; } else { @@ -4187,10 +4248,11 @@ pub(super) fn finalize_bundle( code: compile_result_code, script_injection_offset: compile_result_offset, .. - } = &chunk.compile_results_for_chunk[0] + } = chunk.compile_results_for_chunk.get_mut(0) else { unreachable!() }; + let compile_result_offset = *compile_result_offset; let generated_js = dev.generate_javascript_code_for_html_file( index, import_records, @@ -4213,7 +4275,7 @@ pub(super) fn finalize_bundle( let route_bundle_index = dev.client_graph.html_route_bundle_index(client_index); let route_bundle = dev.route_bundle_ptr(route_bundle_index); debug_assert!(route_bundle.data.html().bundled_file == client_index); - // PORT NOTE: split borrow — `invalidate_client_bundle` needs `&mut RouteBundle` + // Note: split borrow — `invalidate_client_bundle` needs `&mut RouteBundle` // so the `cached_response` take is done before the long-lived `html` borrow. if route_bundle .data @@ -4230,9 +4292,12 @@ pub(super) fn finalize_bundle( route_bundle::Data::Html(h) => h, _ => unreachable!(), }; - if let Some(_slice) = html.bundled_html_text.take() {} - html.bundled_html_text = Some(compile_result_code.clone()); // TODO(port): ownership transfer - html.script_injection_offset = Some(route_bundle::ByteOffset::init(*compile_result_offset)); + // Drops the previous text (Zig: `allocator.free(slice)`) and transfers + // ownership of the chunk's compile-result code (Zig: + // `html.bundled_html_text = compile_result.code`), leaving an empty + // boxed slice behind in the chunk — nothing reads it after this. + html.bundled_html_text = Some(std::mem::take(compile_result_code)); + html.script_injection_offset = Some(route_bundle::ByteOffset::init(compile_result_offset)); chunk .entry_point @@ -4325,7 +4390,7 @@ pub(super) fn finalize_bundle( // Fill the source map entry // PERF(port): was ArenaAllocator dev.server_graph.take_source_map(&mut source_map_entry)?; - // PORT NOTE: erase to raw ptr so `render_json` can borrow the entry + // Note: erase to raw ptr so `render_json` can borrow the entry // while the cleanup guard is armed (Zig `defer` had no aliasing check). let entry_ptr: *mut source_map_store::Entry = &raw mut source_map_entry; scopeguard::defer! { @@ -4368,7 +4433,7 @@ pub(super) fn finalize_bundle( // `print_error_like_object_to_console` needs `&mut`. dev.vm_mut() .print_error_like_object_to_console(global.take_exception(err)); - // PORT NOTE: Zig `@panic` aborts; Rust `panic!()` would unwind + // Note: Zig `@panic` aborts; Rust `panic!()` would unwind // through the `extern "C"` boundary above (`nounwind` UB). bun_core::Output::panic(format_args!( "Error thrown while evaluating server code. This is always a bug in the bundler." @@ -4383,7 +4448,7 @@ pub(super) fn finalize_bundle( // `print_error_like_object_to_console` needs `&mut`. dev.vm_mut() .print_error_like_object_to_console(global.take_exception(err)); - // PORT NOTE: Zig `@panic` aborts; Rust `panic!()` would unwind + // Note: Zig `@panic` aborts; Rust `panic!()` would unwind // through the `extern "C"` boundary above (`nounwind` UB). bun_core::Output::panic(format_args!( "Error thrown while evaluating server code. This is always a bug in the bundler." @@ -4391,7 +4456,7 @@ pub(super) fn finalize_bundle( } } }; - // PORT NOTE: `make_array_for_server_components_patch` needs `&mut self` + // Note: `make_array_for_server_components_patch` needs `&mut self` // while reading `self.incremental_result.client_components_{added,removed}`. // It only touches `server_graph.bundled_files.keys()`, so reborrow the // index slices via `dev_ptr` (disjoint fields). @@ -4546,13 +4611,13 @@ pub(super) fn finalize_bundle( // `route_bits` will have all of the routes that were modified. if has_route_bits_set && (will_hear_hot_update || dev.incremental_result.had_adjusted_edges) { - // PORT NOTE: copy out before the loop so the `&mut RouteBundle` borrow + // Note: copy out before the loop so the `&mut RouteBundle` borrow // below doesn't overlap a `&dev.incremental_result` read. let had_adjusted_edges = dev.incremental_result.had_adjusted_edges; let mut it = route_bits.iterator::(); // List 2 while let Some(i) = it.next() { - // PORT NOTE: erase to raw ptr — `trace_all_route_imports` below needs + // Note: erase to raw ptr — `trace_all_route_imports` below needs // `&mut *dev` while `route_bundle` (a sub-borrow of `dev.route_bundles`) // is still live; the two do not actually alias. let route_bundle: *mut RouteBundle = dev.route_bundle_ptr(route_bundle::Index::init( @@ -4663,7 +4728,7 @@ pub(super) fn finalize_bundle( .put_or_increment_ref_count(script_id, sockets)? { source_map_store::PutOrIncrementRefCount::Uninitialized(entry) => 'brk: { - // PORT NOTE: reborrow `client_graph` via `dev_ptr` so the + // Note: reborrow `client_graph` via `dev_ptr` so the // `&mut Entry` borrow inside `source_maps` does not alias. // SAFETY: `dev_ptr` is live for this fn; disjoint fields. unsafe { &mut (*dev_ptr).client_graph }.take_source_map(entry)?; @@ -4696,14 +4761,14 @@ pub(super) fn finalize_bundle( dev.bundles_since_last_error = 0; // SAFETY: JS-thread only; sole `&mut` agent borrow in this scope. - // PORT NOTE: erase the agent borrow to a raw pointer so it can be passed + // Note: erase the agent borrow to a raw pointer so it can be passed // through `send_serialized_failures` (which also borrows `dev`) and then // re-used below — Zig passed the optional pointer by value. let mut inspector_agent_ptr: Option<*mut BunFrontendDevServerAgent> = unsafe { dev.inspector() }.map(std::ptr::from_mut); if current_bundle!().promise.strong.has_value() { // SAFETY: see `current_bundle!` SAFETY; guard runs before `_outer_defer`. - // PORT NOTE: copy the raw ptr so `defer!`'s by-ref capture does not + // Note: copy the raw ptr so `defer!`'s by-ref capture does not // hold `*current_bundle_ptr` borrowed across `current_bundle!()` uses. let cb_ptr_defer: *mut CurrentBundle = current_bundle_ptr; scopeguard::defer! { @@ -4714,7 +4779,7 @@ pub(super) fn finalize_bundle( .promise .set_route_bundle_state(dev, route_bundle::State::PossibleBundlingFailures); let global = dev.global(); - // PORT NOTE: `bundling_failures` lives on `*dev` but + // Note: `bundling_failures` lives on `*dev` but // `send_serialized_failures` needs `&mut self`; reborrow the keys // through `dev_ptr` (Zig passed `dev.bundling_failures.keys()` by // value with no aliasing check). The callee never touches @@ -4754,7 +4819,7 @@ pub(super) fn finalize_bundle( Handler::BundledHtmlPage(ram) => DevResponse::Http(ram.response), }; - // SAFETY: see PORT NOTE on `failures` above; `dev_ptr` is live and + // SAFETY: see Note on `failures` above; `dev_ptr` is live and // `send_serialized_failures` does not mutate `bundling_failures`. let failures = unsafe { (*dev_ptr).bundling_failures.values() }; dev.send_serialized_failures( @@ -4851,7 +4916,7 @@ pub(super) fn finalize_bundle( break 'rbi route_bundle_indices[0]; }; - // PORT NOTE: index `route_bundles` immutably so `dev.relative_path` + // Note: index `route_bundles` immutably so `dev.relative_path` // / `dev.router` / `dev.server_graph` reads below stay disjoint. break 'brk match &dev.route_bundles[route_bundle_index.get() as usize].data { route_bundle::Data::Html(html) => { @@ -4916,7 +4981,7 @@ pub(super) fn finalize_bundle( if current_bundle!().promise.strong.has_value() { // SAFETY: see `current_bundle!` SAFETY; guard runs before `_outer_defer`. - // PORT NOTE: copy the raw ptr so `defer!`'s by-ref capture does not + // Note: copy the raw ptr so `defer!`'s by-ref capture does not // hold `*current_bundle_ptr` borrowed across `current_bundle!()` uses. let cb_ptr_defer: *mut CurrentBundle = current_bundle_ptr; scopeguard::defer! { @@ -4945,7 +5010,7 @@ pub(super) fn finalize_bundle( let rb = dev.route_bundle_ptr(req.route_bundle_index); rb.server_state = route_bundle::State::Loaded; - // PORT NOTE: `SavedRequest` is move-only (`Strong` field). Take the + // Note: `SavedRequest` is move-only (`Strong` field). Take the // handler by value so the `Saved` payload moves into the union; the // node is being torn down via `_deref` regardless. match ::core::mem::replace(&mut req.handler, Handler::Aborted) { @@ -4953,7 +5018,7 @@ pub(super) fn finalize_bundle( Handler::ServerHandler(saved) => { let response = saved.response; let ctx = saved.ctx; - // PORT NOTE: Zig copied `saved` by value into the call and let + // Note: Zig copied `saved` by value into the call and let // `defer req.deref()` → `__deinit` → `saved.deinit()` release // the original (`js_request.deinit()` + `ctx.deref()`). The // Rust port moves `saved` out (so `__deinit` sees `Aborted`); @@ -5027,7 +5092,7 @@ impl DevServer { (false, Instant::now()) }; - // PORT NOTE: iterate by index — `route_bundle_ptr` / + // Note: iterate by index — `route_bundle_ptr` / // `append_route_entry_points_if_not_stale` need `&mut self`, // conflicting with the `keys()` iterator borrow. for i in 0..self.next_bundle.route_queue.len() { @@ -5137,14 +5202,7 @@ pub struct CacheEntry { impl DevServer { pub fn is_file_cached(&mut self, path: &[u8], side: bake::Graph) -> Option { // Barrel files with deferred records must always be re-parsed. - // TODO(port): `ArrayHashMap, ()>::contains_key` wants - // `&Box<[u8]>`; no `Borrow<[u8]>` adapter yet, so linear-scan keys. - if self - .barrel_files_with_deferrals - .keys() - .iter() - .any(|k| &**k == path) - { + if self.barrel_files_with_deferrals.contains_key(path) { return None; } @@ -5384,7 +5442,7 @@ impl DevServer { .insert_stale_extra(&html_ref.bundle.path, false, true)?; let file = &mut self.client_graph.bundled_files.values_mut() [incremental_graph_index.get() as usize]; - // PORT NOTE: Zig packs/unpacks; the un-gated `incremental_graph::File` + // Note: Zig packs/unpacks; the un-gated `incremental_graph::File` // is unpacked already. file.html_route_bundle_index = Some(bundle_index); // Zig `.initRef(html)` — bump intrusive refcount; matched by @@ -5579,7 +5637,7 @@ impl DevServer { } } -// PORT NOTE: FileKind/ChunkKind/TraceImportGoal/IncrementalResult/GraphTraceState +// Note: FileKind/ChunkKind/TraceImportGoal/IncrementalResult/GraphTraceState // are defined once in `crate::bake::dev_server` and re-exported here so the // Phase-A draft body and the keystone struct module agree on identity. pub(super) use crate::bake::dev_server::FileKind; @@ -5613,7 +5671,7 @@ impl DevServer { } } -// PORT NOTE: canonical `ChunkKind` lives in `crate::bake::dev_server`; the +// Note: canonical `ChunkKind` lives in `crate::bake::dev_server`; the // body module re-exports it so both modules name the same type. pub(super) use crate::bake::dev_server::ChunkKind; @@ -5713,7 +5771,7 @@ impl DevServer { pub fn emit_visualizer_message_if_needed(&mut self) { #[cfg(feature = "bake_debugging_features")] { - // PORT NOTE: erase `self` to a raw ptr so the `defer!` doesn't pin a + // Note: erase `self` to a raw ptr so the `defer!` doesn't pin a // unique borrow for the rest of the fn (Zig `defer` had no aliasing). let self_ptr: *mut Self = self; // SAFETY: `self_ptr` points to `*self`, live for the fn body. @@ -5792,7 +5850,7 @@ impl DevServer { /* source_maps */ cost.source_maps as u32, /* assets */ cost.assets as u32, /* other */ cost.other as u32, - // PORT NOTE: Zig populated this from a debug allocation-scope + // Note: Zig populated this from a debug allocation-scope // tracker; Rust ownership has no equivalent runtime counter. /* devserver_tracked */ 0, @@ -5847,7 +5905,7 @@ impl DevServer { .zip(g.bundled_files.values()) .enumerate() { - // PORT NOTE: un-gated `incremental_graph::File` is unpacked already. + // Note: un-gated `incremental_graph::File` is unpacked already. let file = v; let mut buf = paths::path_buffer_pool::get(); let normalized_key = self.relative_path(&mut *buf, k); @@ -5882,7 +5940,7 @@ impl DevServer { emit_files!(bake::Side::Client, &self.client_graph); emit_files!(bake::Side::Server, &self.server_graph); - // PORT NOTE: Zig used `inline for` over a `[2]bake.Side` tuple — written + // Note: Zig used `inline for` over a `[2]bake.Side` tuple — written // out as a small macro to avoid duplicating the per-side body while // still monomorphizing on the const-generic graph type. macro_rules! emit_edges { @@ -5911,7 +5969,7 @@ impl DevServer { } } -// PORT NOTE: MessageId/IncomingMessageId/ConsoleLogKind/HmrTopic are defined +// Note: MessageId/IncomingMessageId/ConsoleLogKind/HmrTopic are defined // once in `crate::bake::dev_server` and re-exported here. pub(super) use crate::bake::dev_server::{HmrTopic, MessageId}; @@ -6078,7 +6136,7 @@ impl DevServer { scopeguard::defer! { debug_log!("onFileUpdate end") }; let mut slice = watchlist.slice(); - // PORT NOTE: SoA columns are disjoint, but `items_mut` borrows the whole + // Note: SoA columns are disjoint, but `items_mut` borrows the whole // slice mutably. Erase each column to a raw `*mut [T]` and reborrow at // the use sites — the columns never alias (per `MultiArrayElement` layout). let file_paths: *const [std::borrow::Cow<'static, [u8]>] = slice.items_file_path(); @@ -6097,7 +6155,7 @@ impl DevServer { // SAFETY: `watcher_acquire_event` returns a valid `*mut HotReloadEvent` // into `self.watcher_atomics.events`; exclusive on the watcher thread. let ev = unsafe { &mut *ev_ptr }; - // PORT NOTE: erase `self` to a raw ptr in the deferred closures so the + // Note: erase `self` to a raw ptr in the deferred closures so the // loop body can keep using `self.bun_watcher` (Zig `defer` had no // aliasing check). let self_ptr: *mut Self = self; @@ -6147,7 +6205,7 @@ impl DevServer { ev.append_file(file_path); } bun_watcher::Kind::Directory => { - // PORT NOTE: Zig's `Environment.isLinux` is `os.tag == .linux`, + // Note: Zig's `Environment.isLinux` is `os.tag == .linux`, // which is *true* on Android (Zig encodes Android via the ABI // tag, not the OS tag). Rust's `target_os = "linux"` is false // on Android, so include `target_os = "android"` explicitly to @@ -6176,7 +6234,7 @@ impl DevServer { pub fn on_watch_error(&self, err: sys::Error) { if !err.path.is_empty() { - // PORT NOTE: split out path before moving `err` into `Output::err`. + // Note: split out path before moving `err` into `Output::err`. let path = err.path.clone(); Output::err( err, @@ -6410,11 +6468,39 @@ fn dump_state_due_to_crash(dev: &mut DevServer) -> Result<(), bun_core::Error> { } }; - // TODO(port): comptime brk: { @setEvalBranchQuota; @embedFile; lastIndexOf } + // Zig did this split at comptime (`comptime brk: { @setEvalBranchQuota; + // @embedFile; lastIndexOf }`); const-evaluate the lastIndexOf here so a + // missing `>>` frees via `Drop` (global mimalloc); if `allocatorForSize` - // returns a distinct arena for large buffers, matched-arena dealloc must be - // restored here. + // Buffers are freed via `Drop` (global mimalloc); if + // `Chunk::allocator_for_size` ever becomes size-dependent, matched-arena + // dealloc must be restored here. let mut standalone_chunk_contents: Option>>> = None; if is_standalone { @@ -810,7 +800,6 @@ pub fn generate_chunks_in_parallel( + b.len() + b"\n".len(); let mut buf: Vec = Vec::with_capacity(total_len); - // PERF(port): was appendSliceAssumeCapacity buf.extend_from_slice(&code_result.buffer); buf.extend_from_slice(source_map_start); buf.extend_from_slice(a); @@ -849,7 +838,6 @@ pub fn generate_chunks_in_parallel( code_result.buffer.len() + source_map_start.len() + encode_len + 1; let mut buf: Vec = Vec::with_capacity(total_len); - // PERF(port): was appendSliceAssumeCapacity buf.extend_from_slice(&code_result.buffer); buf.extend_from_slice(source_map_start); @@ -967,7 +955,7 @@ pub fn generate_chunks_in_parallel( })); } else { // an error - // logger OOM-only (Zig: catch unreachable) + // logger OOM-only // Split-borrow — `static_route_visitor.c` holds a // detached `&LinkerContext`; `log_disjoint` returns the // disjoint `Transpiler.log` backref so no `&mut c` is @@ -1104,8 +1092,7 @@ pub fn generate_chunks_in_parallel( None }, referenced_css_chunks: match &chunk.content { - // Zig: `@ptrCast(dupe(u32, js.css_chunks))` — `output_file::Index` - // is `#[repr(transparent)]` over u32. + // `output_file::Index` is `#[repr(transparent)]` over u32. crate::chunk::Content::Javascript(js) => js .css_chunks .iter() @@ -1161,13 +1148,10 @@ pub fn generate_chunks_in_parallel( // Deinit dropped items to free their heap allocations (paths, buffers). let mut result = output_files.take(); let mut write_idx: usize = 0; - // Reshaped for borrowck — Zig iterates by pointer and assigns into items[write_idx]. let len = result.len(); for i in 0..len { if result[i].loader == Loader::Html { result.swap(write_idx, i); - // Zig copies item then leaves original; using swap keeps semantics - // (the slot at `i` will be truncated/dropped below). write_idx += 1; } // else: item at `i` will be dropped by truncate below (impl Drop handles deinit) @@ -1185,4 +1169,3 @@ use crate::EntryPoint; use crate::options::SourceMapOption; use crate::output_file::BakeExtra; -// ported from: src/bundler/linker_context/generateChunksInParallel.zig diff --git a/src/bundler/linker_context/generateCodeForFileInChunkJS.rs b/src/bundler/linker_context/generateCodeForFileInChunkJS.rs index fad1011fac8c..6c14a1cf4aa4 100644 --- a/src/bundler/linker_context/generateCodeForFileInChunkJS.rs +++ b/src/bundler/linker_context/generateCodeForFileInChunkJS.rs @@ -39,7 +39,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( // Grab raw pointers to the SoA columns up front so // subsequent `&mut c` borrows (convert_stmts_for_chunk, print_code_for_file_in_chunk_js) - // don't conflict. Matches Zig which slices once at the top. + // don't conflict. // SAFETY: the underlying MultiArrayList storage is not resized for the duration of this // function (linking has already sized everything). let parts: *mut [Part] = { @@ -71,7 +71,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( if c.options.output_format == OutputFormat::InternalBakeDev { 'brk: { if part_range.source_index.is_runtime() { - // PERF(port): was @branchHint(.cold) debug_assert!(c.dev_server.is_none()); break 'brk; // this is from `bun build --format=internal_bake_dev` } @@ -93,7 +92,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( let all_stmts_len = main_stmts_len + stmts.outside_wrapper_prefix.len() + 1; stmts.all_stmts.reserve(all_stmts_len); - // PERF(port): was appendSliceAssumeCapacity stmts .all_stmts .extend_from_slice(stmts.inside_wrapper_prefix.stmts.as_slice()); @@ -104,7 +102,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( // Capture pointer/len now, re-slice after pushes (borrowck). // SAFETY: `inner` aliases the first `main_stmts_len` elements of `all_stmts`; // subsequent pushes only append past this range and capacity was reserved above - // so no reallocation occurs. Matches Zig which slices then continues appending. + // so no reallocation occurs. let inner = bun_ast::StoreSlice::new_mut( &mut stmts.all_stmts.as_mut_slice()[0..main_stmts_len], ); @@ -145,7 +143,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( }); } - // PERF(port): was temp_arena.dupe — `G::Arg` is not Copy in Rust + // `G::Arg` is not `Copy`; duplicate the args element-wise. let dup_args: &mut [G::Arg] = { let mut v = bun_alloc::ArenaVec::with_capacity_in( clousure_args.const_slice().len(), @@ -157,7 +155,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( v.into_bump_slice_mut() }; - // PERF(port): was appendAssumeCapacity stmts.all_stmts.push(Stmt::allocate_expr( temp_arena, Expr::init( @@ -174,7 +171,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( bun_ast::Loc::EMPTY, ), )); - // PERF(port): was appendSliceAssumeCapacity stmts .all_stmts .extend_from_slice(stmts.outside_wrapper_prefix.as_slice()); @@ -184,8 +180,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( // TODO: there is a weird edge case where the pretty path is not computed // it does not reproduce when debugging. let source_ref = c.get_source(source_index as u32); - // The Zig original copies the `Source` by value, - // mutates `.path`, and passes `&source`. `bun_ast::Source` is not `Clone` + // `bun_ast::Source` is not `Clone` // (its `Cow` fields would deep-copy `Owned` data); instead, build a // borrowed-field shadow only when the path needs fixing. let source_storage: bun_ast::Source; @@ -396,9 +391,8 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( // Be careful: the top-level value in a JSON file is not necessarily an object if let ExprData::EObject(e_object) = default_expr.data { - // Zig's `properties.clone(temp_arena)` is a memcpy into the - // temp arena. `G::Property` is not `Clone` (it embeds a `Vec`), so - // mirror the Zig bitwise copy directly. JSON object properties carry no + // `G::Property` is not `Clone` (it embeds a `Vec`), so + // copy the properties bitwise. JSON object properties carry no // owned heap data (`ts_decorators` is always empty, `class_static_block` // is `None`), so the duplicated bits do not alias any allocation. let src_len = e_object.properties.len(); @@ -516,7 +510,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( stmts .all_stmts .reserve(stmts.inside_wrapper_prefix.stmts.len() + stmts.inside_wrapper_suffix.len()); - // PERF(port): was appendSliceAssumeCapacity stmts .all_stmts .extend_from_slice(stmts.inside_wrapper_prefix.stmts.as_slice()); @@ -554,7 +547,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( .flags .intersects(AstFlags::USES_MODULE_REF | AstFlags::USES_EXPORTS_REF) { - // PERF(port): was appendAssumeCapacity args.push(G::Arg { binding: Binding::alloc( temp_arena, @@ -567,7 +559,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( }); if ast.flags.contains(AstFlags::USES_MODULE_REF) { - // PERF(port): was appendAssumeCapacity args.push(G::Arg { binding: Binding::alloc( temp_arena, @@ -750,8 +741,7 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( StmtData::SClass(mut class) => 'stmt: { // `class` is `StoreRef` — an arena-owned pointer. // `&mut class.class` (via DerefMut) yields a `&mut G::Class` into arena - // memory, so wrapping it in a StoreRef for `EClass` is sound and matches - // Zig's `&class.class`. + // memory, so wrapping it in a StoreRef for `EClass` is sound. if class.class.can_be_moved() { stmts.append(StmtListWhich::OutsideWrapperPrefix, stmt); continue 'hoist; @@ -1053,8 +1043,6 @@ impl DeclCollector { if name.is_empty() { return; } - // Zig: `catch return` — silently drop on alloc failure. With std Vec - // push aborts on OOM, so there is nothing to catch. self.decls.push(DeclInfo { name: name.to_vec().into_boxed_slice(), kind, @@ -1089,7 +1077,6 @@ fn merge_adjacent_local_stmts(stmts: &mut Vec, _arena: &Bump) { let mut clone = Vec::::init_capacity(before.decls.len() + after.decls.len()); - // PERF(port): was appendSliceAssumeCapacity clone.append_slice_assume_capacity(before.decls.slice()); clone.append_slice_assume_capacity(after.decls.slice()); // we must clone instead of overwrite in-place incase the same S.Local is used across threads @@ -1119,10 +1106,9 @@ fn merge_adjacent_local_stmts(stmts: &mut Vec, _arena: &Bump) { stmts.truncate(end); } -// Type aliases / re-imports for readability of match arms (mirrors Zig naming). +// Type aliases / re-imports for readability of match arms. use bun_ast::LocalKind; use bun_ast::binding::Data as BindingData; use bun_ast::expr::Data as ExprData; use bun_ast::stmt::Data as StmtData; -// ported from: src/bundler/linker_context/generateCodeForFileInChunkJS.zig diff --git a/src/bundler/linker_context/generateCodeForLazyExport.rs b/src/bundler/linker_context/generateCodeForLazyExport.rs index 4412218a7351..c1c767aeeb5b 100644 --- a/src/bundler/linker_context/generateCodeForLazyExport.rs +++ b/src/bundler/linker_context/generateCodeForLazyExport.rs @@ -100,7 +100,6 @@ pub fn generate_code_for_lazy_export( struct Visitor<'a> { inner_visited: &'a mut BitSet, - // Zig: `std.AutoArrayHashMap(Ref, void)` → `ArrayHashMap` per collections map. composes_visited: &'a mut ArrayHashMap, parts: &'a mut Vec, all_import_records: &'a [bun_ast::import_record::List<'a>], @@ -111,7 +110,6 @@ pub fn generate_code_for_lazy_export( source_index: IndexInt, log: &'a mut Log, loc: Loc, - // PERF(port): was `std.mem.Allocator` (arena) — bundler is an AST crate; thread `&'bump Bump`. arena: &'a Arena, } @@ -132,7 +130,6 @@ pub fn generate_code_for_lazy_export( } self.visit_composes(ast, ref_, idx); - // PERF(port): was assume-OOM `catch |err| bun.handleOom(err)`; Vec::push aborts on OOM. self.parts.push(E::TemplatePart { value: Expr::init( E::NameOfSymbol { @@ -305,9 +302,8 @@ pub fn generate_code_for_lazy_export( } } - // Zig left `parts: undefined` and rebound per-iteration; Rust - // forbids uninit refs, so the Visitor is constructed inside the loop with - // a fresh `parts` borrow each time (reshaped for borrowck). + // The Visitor is constructed inside the loop with a fresh `parts` + // borrow each time (reshaped for borrowck). let all_symbols = this.graph.ast.items_symbols(); // SAFETY: `LinkerContext::arena()` returns a stable `&Arena` valid for the // link pass; detach via raw-pointer round-trip so it doesn't hold a `&self` @@ -318,7 +314,6 @@ pub fn generate_code_for_lazy_export( let ref_ = entry.ref_; debug_assert!(ref_.inner_index() < symbols.len() as u32); - // PERF(port): was arena-backed ArrayList (no deinit; `.items` moved into E.Template). let mut template_parts: Vec = Vec::new(); let mut value = Expr::init( E::NameOfSymbol { @@ -354,8 +349,7 @@ pub fn generate_code_for_lazy_export( tail_loc: stmt.loc, tail: E::TemplateContents::Cooked(E::String::init(b"")), }); - // Zig used an arena-backed ArrayList and moved `.items` - // into `E.Template`; mirror that by moving into the linker arena + // Move the parts into the linker arena // (freed when the linker arena drops). let parts_slice = bun_ast::StoreSlice::new_mut(arena.alloc_slice_fill_iter(template_parts)); @@ -436,8 +430,7 @@ pub fn generate_code_for_lazy_export( let _: &G::Property = property; // `Expr`/`ExprData`/`StoreRef<_>` are `Copy`. Copy `key` out so // `key_str: StoreRef` is a mutable local — `slice()` resolves - // the rope in-place via `DerefMut` into the arena slot (matches Zig's - // `property.key.?.data.e_string.slice(...)` which takes `*String`). + // the rope in-place via `DerefMut` into the arena slot. let Some(key) = property.key else { continue }; let ExprData::EString(mut key_str) = key.data else { continue; @@ -474,7 +467,6 @@ pub fn generate_code_for_lazy_export( // happened yet). So we need to wait until after tree shaking happens. let generated = this.generate_named_export_in_file(source_index, module_ref, name, name)?; - // PERF(port): was `this.arena().alloc(Stmt, 1)` (arena). let new_stmts: &mut [Stmt] = alloc.alloc_slice_fill_iter(core::iter::once(Stmt::alloc( S::Local { @@ -499,7 +491,6 @@ pub fn generate_code_for_lazy_export( } { - // PERF(port): was `std.fmt.allocPrint` into arena; building into Vec then arena-dupe. let mut name_buf: Vec = Vec::new(); write!( &mut name_buf, @@ -541,4 +532,3 @@ pub use crate::DeferredBatchTask; pub use crate::ParseTask; pub use crate::ThreadPool; -// ported from: src/bundler/linker_context/generateCodeForLazyExport.zig diff --git a/src/bundler/linker_context/generateCompileResultForCssChunk.rs b/src/bundler/linker_context/generateCompileResultForCssChunk.rs index d03ad06be366..b921c8dd94c7 100644 --- a/src/bundler/linker_context/generateCompileResultForCssChunk.rs +++ b/src/bundler/linker_context/generateCompileResultForCssChunk.rs @@ -77,7 +77,6 @@ fn generate_compile_result_for_css_chunk_impl( // borrow via `BackRef::get` is fine. The heap is pinned for the worker's // lifetime; see `Worker::arena`. let arena = worker.arena.get(); - // PERF(port): was arena bulk-free (worker.temporary_arena.reset(.retain_capacity)). let _arena_reset = scopeguard::guard(&mut worker.temporary_arena, |arena| { // temporary_arena is initialized in Worker::create before any task runs. if let Some(a) = arena.as_mut() { @@ -229,7 +228,7 @@ fn generate_compile_result_for_css_chunk_impl( if !output.is_empty() { // CONCURRENCY: key set is frozen before parallel codegen; take a // shared `&AtomicUsize` so concurrent workers updating the same - // source counter never alias a `&mut` (Zig: @atomicRmw .Add .monotonic). + // source counter never alias a `&mut`. if let Some(bytes) = chunk.files_with_parts_in_chunk.get(&idx.get()) { let _ = bytes.fetch_add(output.len(), Ordering::Relaxed); } @@ -247,4 +246,3 @@ pub use crate::DeferredBatchTask; pub use crate::ParseTask; pub use crate::ThreadPool; -// ported from: src/bundler/linker_context/generateCompileResultForCssChunk.zig diff --git a/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs b/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs index c0ff1cb64467..c6c67bed4b50 100644 --- a/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs +++ b/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs @@ -240,7 +240,6 @@ impl<'a> HTMLLoader<'a> { } self.added_head_tags = true; - // PERF(port): was stack-fallback (std.heap.stackFallback(256)) let slices = self.get_head_tags(); for slice in slices.as_slice() { end_tag.before(slice, true)?; @@ -255,7 +254,6 @@ impl<'a> HTMLLoader<'a> { } self.added_body_script = true; - // PERF(port): was stack-fallback (std.heap.stackFallback(256)) // SAFETY: `self.chunks` raw `*mut [Chunk]` valid for the link step; sole live `&mut`. let chunks = unsafe { &mut *self.chunks }; if let Some(js_chunk) = self.chunk.get_js_chunk_for_html(chunks) { @@ -272,7 +270,6 @@ impl<'a> HTMLLoader<'a> { } fn get_head_tags(&self) -> Vec> { - // PERF(port): was stack-fallback arena; now heap Vec let mut array: Vec> = Vec::with_capacity(2); // `self.chunk` is a `BackRef` (safe `Deref`). let chunk: &Chunk = &self.chunk; @@ -402,8 +399,7 @@ fn generate_compile_result_for_html_chunk_impl<'a>( let source_index = chunk.entry_point.source_index(); // HTML bundles for the dev server must outlive the bundle task (see - // `DevServer.RouteBundle.HTML.bundled_html_text`). Zig picked `dev.arena()` - // vs `worker.arena` for the output buffer; here the output is built in a + // `DevServer.RouteBundle.HTML.bundled_html_text`). The output is built in a // plain `Vec` and returned as an owned `Box<[u8]>` inside // `CompileResult::Html`, so it lives as long as whoever holds the // `CompileResult` — no arena-lifetime distinction needed. @@ -464,8 +460,7 @@ fn generate_compile_result_for_html_chunk_impl<'a>( } else { 'brk: { if !html_loader.added_head_tags || !html_loader.added_body_script { - // PERF(port): @branchHint(.cold) — this is if the document is missing all head, body, and html elements. - // PERF(port): was stack-fallback (std.heap.stackFallback(256)) + // Cold path: the document is missing all of the head, body, and html elements. if !html_loader.added_head_tags { let slices = html_loader.get_head_tags(); for slice in slices.as_slice() { @@ -505,4 +500,3 @@ fn generate_compile_result_for_html_chunk_impl<'a>( pub use crate::{DeferredBatchTask, ParseTask, ThreadPool}; -// ported from: src/bundler/linker_context/generateCompileResultForHtmlChunk.zig diff --git a/src/bundler/linker_context/generateCompileResultForJSChunk.rs b/src/bundler/linker_context/generateCompileResultForJSChunk.rs index 849dd3d5f11c..1f6c5a782f30 100644 --- a/src/bundler/linker_context/generateCompileResultForJSChunk.rs +++ b/src/bundler/linker_context/generateCompileResultForJSChunk.rs @@ -34,9 +34,8 @@ pub unsafe fn generate_compile_result_for_js_chunk(task: *mut ThreadPoolLib::Tas let (part_range, c_ptr, chunk_ptr, mut worker) = unsafe { crate::linker_context_mod::pending_part_range_prologue(task) }; - // Mirrors Zig `Environment.show_crash_trace`; wired as a cargo feature - // through bun_runtime → bun_bundler → bun_crash_handler. Unlike Zig - // (auto-on in debug/test/asan), no build profile enables the feature by + // Wired as a cargo feature through bun_runtime → bun_bundler → + // bun_crash_handler. No build profile enables the feature by // default — it must be opted into explicitly. #[cfg(feature = "show_crash_trace")] let _crash_guard = { @@ -55,7 +54,7 @@ pub unsafe fn generate_compile_result_for_js_chunk(task: *mut ThreadPoolLib::Tas [part_range.part_range.source_index.get() as usize] .path; if bun_core::debug_flags::has_print_breakpoint(&path.pretty, &path.text) { - // @breakpoint() in Zig — no stable Rust equivalent; left as no-op + // No stable breakpoint intrinsic; left as a no-op. } } @@ -93,9 +92,8 @@ fn generate_compile_result_for_js_chunk_impl( let _trace = bun_core::perf::trace("Bundler.generateCodeForFileInChunkJS"); // `defer trace.end()` → handled by Drop on _trace - // Client and server bundles for Bake must outlive the bundle task. Zig - // selected the dev-server arena here when `c.dev_server` was set; the Rust - // port allocates `BufferWriter::init()` output from the global heap and + // Client and server bundles for Bake must outlive the bundle task. + // `BufferWriter::init()` output is allocated from the global heap and // `DeclCollector.decls` from the worker heap (`worker.arena`, alive until // bundle teardown) — both outlive the task's CompileResult consumption, // so a per-dev-server arena would only be a perf optimization. @@ -107,14 +105,13 @@ fn generate_compile_result_for_js_chunk_impl( .as_mut() .expect("Worker.temporary_arena set in create()"); let mut buffer_writer = js_printer::BufferWriter::init(); - // Zig: `defer _ = arena.reset(.retain_capacity)` on a `std.heap.ArenaAllocator` - // (O(1) bump rewind, chunks retained). `temporary_arena` is a `MimallocArena` + // `temporary_arena` is a `MimallocArena` // here because `temp_arena` flows into `Stmt::allocate`/`Expr::allocate`/ // `Binding::alloc`/`ArenaVec`, all of which take `&MimallocArena` concretely; // a plain `reset()` would be `mi_heap_destroy + mi_heap_new` *per part_range* - // (perf-probe: 46× for one elysia build). Use `reset_retain_with_limit` — the - // codebase's mapping for Zig's `.retain_*` modes (see `ModuleLoader`'s - // `transpile_source_code_arena`): keep the heap warm across part_ranges and + // (perf-probe: 46× for one elysia build). Use `reset_retain_with_limit` + // (see `ModuleLoader`'s `transpile_source_code_arena`): + // keep the heap warm across part_ranges and // only pay the destroy+new round-trip once accumulated scratch exceeds the // limit. 8 MiB matches the module-arena precedent and comfortably covers a // worker's full part_range set for typical bundles, so this is ~one @@ -154,8 +151,7 @@ fn generate_compile_result_for_js_chunk_impl( let collect_decls = c.options.generate_bytecode_cache && c.options.output_format == OutputFormat::Esm && c.options.compile; - // Zig threaded `arena` (dev_server or default) into DeclCollector; the - // Rust DeclCollector wants `*const Arena` and uses the worker heap (see + // DeclCollector wants `*const Arena` and uses the worker heap (see // the dev-server allocation note above). let mut dc = DeclCollector { arena: worker.arena.as_ptr(), @@ -167,8 +163,7 @@ fn generate_compile_result_for_js_chunk_impl( // a direct shared borrow is fine. Heap is pinned; see `Worker::arena`. let worker_alloc = worker.arena.get(); // SAFETY: split borrow of `chunk` — `generate_code_for_file_in_chunk_js` never - // touches `chunk.renamer` through its `chunk` parameter (Zig passes the renamer - // union by value alongside `*Chunk`); take a raw-ptr view so borrowck doesn't + // touches `chunk.renamer` through its `chunk` parameter; take a raw-ptr view so borrowck doesn't // see two overlapping `&mut chunk` borrows. let renamer_ptr: *mut crate::bun_renamer::ChunkRenamer = core::ptr::addr_of_mut!(chunk.renamer); let result = generate_code_for_file_in_chunk_js( @@ -222,4 +217,3 @@ fn generate_compile_result_for_js_chunk_impl( pub use crate::DeferredBatchTask::DeferredBatchTask; pub use crate::ParseTask; -// ported from: src/bundler/linker_context/generateCompileResultForJSChunk.zig diff --git a/src/bundler/linker_context/postProcessCSSChunk.rs b/src/bundler/linker_context/postProcessCSSChunk.rs index 53caa9219c99..2c0c0571a698 100644 --- a/src/bundler/linker_context/postProcessCSSChunk.rs +++ b/src/bundler/linker_context/postProcessCSSChunk.rs @@ -97,7 +97,7 @@ pub fn post_process_css_chunk( } // Save the offset to the start of the stored JavaScript - // Zig `j.push(.., bun.default_allocator)` — code() borrows from + // code() borrows from // compile_results which outlives the joiner; treat as static (no copy/free). j.push_static(compile_result.code()); @@ -108,7 +108,7 @@ pub fn post_process_css_chunk( // SAFETY: bitwise alias of `chunk.compile_results_for_chunk` // (read-only and outlives this fn); see `postProcessJSChunk.rs`. source_map_chunk: unsafe { source_map_chunk.alias() }, - // Zig reads `.value` payload directly — guaranteed `Value` here + // Guaranteed `Value` here // because `source_maps != None` implies `line_offset` was // initialised to `Value(_)` above. generated_offset: match line_offset { @@ -173,4 +173,3 @@ pub fn post_process_css_chunk( Ok(()) } -// ported from: src/bundler/linker_context/postProcessCSSChunk.zig diff --git a/src/bundler/linker_context/postProcessHTMLChunk.rs b/src/bundler/linker_context/postProcessHTMLChunk.rs index 1d77567bfba0..d8e9baa6e76c 100644 --- a/src/bundler/linker_context/postProcessHTMLChunk.rs +++ b/src/bundler/linker_context/postProcessHTMLChunk.rs @@ -23,8 +23,8 @@ pub fn post_process_html_chunk( let compile_results = &chunk.compile_results_for_chunk; for compile_result in compile_results.iter() { - // Zig `j.push(.., bun.default_allocator)` — code() borrows from - // chunk.compile_results_for_chunk which outlives `j.done()`; arena arg dropped. + // code() borrows from + // chunk.compile_results_for_chunk which outlives `j.done()`. j.push_static(compile_result.code()); } @@ -42,7 +42,7 @@ pub fn post_process_html_chunk( alloc, &mut j, ctx.chunks.len() as u32, // @truncate - )); // Zig: `catch |err| bun.handleOom(err)` + )); // Reshaped for borrowck (compute hash before assigning into chunk) let isolated_hash = c.generate_isolated_hash(chunk, alloc); @@ -51,4 +51,3 @@ pub fn post_process_html_chunk( Ok(()) } -// ported from: src/bundler/linker_context/postProcessHTMLChunk.zig diff --git a/src/bundler/linker_context/postProcessJSChunk.rs b/src/bundler/linker_context/postProcessJSChunk.rs index ed7a3b4f4620..bcd6526810dd 100644 --- a/src/bundler/linker_context/postProcessJSChunk.rs +++ b/src/bundler/linker_context/postProcessJSChunk.rs @@ -27,9 +27,8 @@ use bun_sourcemap as SourceMap; use crate::IndexInt; -/// Move the printed code out of a `PrintResult`. Mirrors Zig -/// `j.push(result.code, worker.allocator)` where the joiner takes ownership of -/// the slice — the Rust `PrintResultSuccess.code` is a `Box<[u8]>` that would +/// Move the printed code out of a `PrintResult` so the joiner takes ownership +/// of the slice — `PrintResultSuccess.code` is a `Box<[u8]>` that would /// otherwise drop at end of `post_process_js_chunk` and leave the deferred /// `IntermediateOutput::Joiner` path holding freed memory. fn print_result_take_code(r: &mut PrintResult) -> Box<[u8]> { @@ -62,13 +61,12 @@ pub fn post_process_js_chunk( // embedded `Vec`/`Vec` buffers leak from the global heap. let _ast_alloc_heap = js_ast::StoreAstAllocHeap::new(); - // Zig `defer chunk.renamer.deinit(bun.default_allocator)`: the renamer is + // The renamer is // not used after this function, so it is reset to `None` before returning // (success path below). `ChunkRenamer`'s variants free their owned storage // via Drop; the `symbols` view inside is `ManuallyDrop` (non-owning), so - // graph storage is never freed — matching Zig's deinit semantics. + // graph storage is never freed. - // PERF(port): was arena bulk-free — profile if hot. let arena = Arena::new(); // Also generate the cross-chunk binding code @@ -104,8 +102,7 @@ pub fn post_process_js_chunk( let loader = c.parse_graph().input_files.items_loader()[chunk.entry_point.source_index() as usize]; let is_typescript = loader.is_type_script(); - // Zig: ModuleInfo.create(bun.default_allocator, ...) returns heap-allocated *ModuleInfo, - // later stored on chunk.content.javascript.module_info — OWNED → Box. + // Stored on chunk.content.javascript.module_info — owned `Box`. let mut module_info: Option> = if generate_module_info { Some(ModuleInfo::create(is_typescript)) } else { @@ -116,8 +113,7 @@ pub fn post_process_js_chunk( let worker_arena: &Arena = worker.arena(); { - // Zig builds one `print_options` and passes it by-value twice. - // Rust `Options` is not `Copy` (holds `&mut ModuleInfo`), and a closure + // `Options` is not `Copy` (holds `&mut ModuleInfo`), and a closure // taking `&mut ModuleInfo` can't express "output lifetime = input // lifetime" — so build a base with `module_info: None` and override it // via FRU at each call site. @@ -140,9 +136,7 @@ pub fn post_process_js_chunk( let mut cross_chunk_import_records: Vec = Vec::with_capacity(chunk.cross_chunk_imports.len() as usize); - // PERF(port): was initCapacity catch unreachable for import_record in chunk.cross_chunk_imports.slice() { - // PERF(port): was appendAssumeCapacity cross_chunk_import_records.push(ImportRecord { kind: import_record.import_kind, // `ctx.chunks` is a `BackRef<[Chunk]>` (safe `Deref`); chunk_index is @@ -151,7 +145,7 @@ pub fn post_process_js_chunk( ctx.chunks[import_record.chunk_index as usize].unique_key, ), range: bun_ast::Range::NONE, - // Remaining fields take their Zig defaults (no Default impl): + // Remaining fields (`ImportRecord` has no `Default` impl): tag: ImportRecordTag::None, loader: None, source_index: Index::INVALID, @@ -325,7 +319,7 @@ pub fn post_process_js_chunk( continue; } // Skip barrel-optimized-away imports — marked is_unused by - // barrel_imports.zig. Never resolved (source_index invalid), + // `barrel_imports`. Never resolved (source_index invalid), // and removed by convertStmtsForChunk. Not in emitted code. if record.flags.contains(ImportRecordFlags::IS_UNUSED) { continue; @@ -524,7 +518,6 @@ pub fn post_process_js_chunk( let is_bun = c.graph.ast.items_target()[chunk.entry_point.source_index() as usize].is_bun(); if is_bun { if c.options.generate_bytecode_cache && output_format == options::OutputFormat::Cjs { - // Zig `++` literal concat → single byte literal (concat! yields &str, not &[u8]) const INPUT: &[u8] = b"// @bun @bytecode @bun-cjs\n(function(exports, require, module, __filename, __dirname) {"; j.push_static(INPUT); @@ -603,7 +596,6 @@ pub fn post_process_js_chunk( } { - // Zig `j.push(code, worker.allocator)` transferred ownership; // `cross_chunk_prefix` is a local that drops at fn exit, but the joiner // may be stashed on `chunk.intermediate_output` and consumed later // (`IntermediateOutput::Joiner` path). Move the Box into the joiner. @@ -755,9 +747,8 @@ pub fn post_process_js_chunk( { // `entry_point_tail` is a local `CompileResult` whose `code` - // is a `Box<[u8]>`; Zig `j.push(tail_code, worker.allocator)` handed - // ownership to the joiner. Move it so the deferred-joiner path doesn't - // read freed memory after this fn returns. + // is a `Box<[u8]>`. Move it into the joiner so the deferred-joiner + // path doesn't read freed memory after this fn returns. let tail_code = entry_point_tail.into_code(); if !tail_code.is_empty() { // Stick the entry point tail at the end of the file. Deliberately don't @@ -802,9 +793,7 @@ pub fn post_process_js_chunk( [chunk.entry_point.source_index() as usize] .path; let mut buf = MutableString::init_empty(); - // PERF(port): worker.arena is an arena in Zig let _ = js_printer::quote_for_json(input.pretty, &mut buf, true); // fmt::Result into Vec is infallible - // bun.handleOom dropped — Rust aborts on OOM let quoted = buf.take_slice(); line_offset.advance("ed); j.push_owned(quoted.into_boxed_slice()); @@ -881,8 +870,7 @@ pub fn post_process_js_chunk( )?; } - // Free the renamer now that printing is done (Zig: `defer - // chunk.renamer.deinit(bun.default_allocator)`); nothing reads it after + // Free the renamer now that printing is done; nothing reads it after // post-processing. chunk.renamer = Default::default(); @@ -935,7 +923,6 @@ pub fn generate_entry_point_tail_js<'a>( mut module_info: Option<&'a mut ModuleInfo>, ) -> CompileResult { let flags: crate::js_meta::Flags = c.graph.meta.items_flags()[source_index as usize]; - // PERF(port): was arena-backed ArrayList(Stmt) — profile if hot. let mut stmts: Vec = Vec::new(); // `MultiArrayList::get` returns `ManuallyDrop`; the // storage retains ownership of every Drop field, so neither this @@ -1028,7 +1015,6 @@ pub fn generate_entry_point_tail_js<'a>( // entry point is a CommonJS-style module, since that would generate an ES6 // export statement that's not top-level. Instead, we will export the CommonJS // exports as a default export later on. - // PERF(port): was arena-backed ArrayList(ClauseItem) — profile if hot. let mut items: Vec = Vec::new(); let cjs_export_copies = &c.graph.meta.items_cjs_export_copies()[source_index as usize]; @@ -1036,9 +1022,9 @@ pub fn generate_entry_point_tail_js<'a>( let mut had_default_export = false; for (i, alias) in sorted_and_filtered_export_aliases.iter().enumerate() { - // Zig `resolved_exports.get(alias).?` returns a by-value - // copy of `ExportData`; only `.data` (an `ImportTracker`, `Copy`) is - // read/mutated below, so copy that field instead of the whole struct. + // Only `.data` (an `ImportTracker`, `Copy`) is + // read/mutated below, so copy that field instead of + // the whole `ExportData`. let mut resolved_export_data = resolved_exports.get(alias).unwrap().data; @@ -1170,8 +1156,7 @@ pub fn generate_entry_point_tail_js<'a>( } // arena-owned `*mut [ClauseItem]` — move the - // collected Vec into the linker arena (Zig used - // `c.arena().alloc`). The arena slice is also iterated + // collected Vec into the linker arena. The arena slice is also iterated // below for the synthetic-default-export path. let items: &mut [bun_ast::ClauseItem] = arena.alloc_slice_fill_iter(items); stmts.push(Stmt::alloc( @@ -1184,7 +1169,6 @@ pub fn generate_entry_point_tail_js<'a>( if flags.needs_synthetic_default_export && !had_default_export { let mut properties = G::PropertyList::init_capacity(items.len()); - // PERF(port): was initCapacity catch unreachable let getter_fn_body: &mut [Stmt] = arena.alloc_slice_fill_default(items.len()); let mut remain_getter_fn_body = &mut getter_fn_body[..]; @@ -1206,7 +1190,6 @@ pub fn generate_entry_point_tail_js<'a>( }, bun_ast::Loc::EMPTY, ); - // PERF(port): was appendAssumeCapacity VecExt::append( &mut properties, G::Property { @@ -1412,4 +1395,3 @@ pub fn generate_entry_point_tail_js<'a>( } } -// ported from: src/bundler/linker_context/postProcessJSChunk.zig diff --git a/src/bundler/linker_context/prepareCssAstsForChunk.rs b/src/bundler/linker_context/prepareCssAstsForChunk.rs index fdff8befc886..00c498c1617e 100644 --- a/src/bundler/linker_context/prepareCssAstsForChunk.rs +++ b/src/bundler/linker_context/prepareCssAstsForChunk.rs @@ -20,8 +20,7 @@ use bun_resolver::DataURL; use crate::chunk::{Content, CssImportOrderKind}; -// Zig stores `*Chunk` / `*LinkerContext` (freely-aliasing mutable -// pointers). We mirror that with raw pointers rather than `&mut` / `&` so that +// Raw pointers rather than `&mut` / `&` so that // (a) the container_of `container_of` recovery of `*mut BundleV2` from // `linker` retains write provenance over the whole bundle, and (b) multiple // tasks may hold pointers to the same `LinkerContext` concurrently without @@ -56,8 +55,7 @@ unsafe impl Send for PrepareCssAstTask {} pub unsafe fn prepare_css_asts_for_chunk(task: *mut ThreadPoolLib::Task) { // SAFETY: `task` points to `PrepareCssAstTask.task` (intrusive thread-pool // node); the thread pool hands us exclusive access for the callback's - // duration. We only read the two raw-pointer fields, matching Zig's - // `*const PrepareCssAstTask`. + // duration. We only read the two raw-pointer fields. let prepare_css_asts: &PrepareCssAstTask = unsafe { &*bun_core::from_field_ptr!(PrepareCssAstTask, task, task) }; let linker: *mut LinkerContext = prepare_css_asts.linker; @@ -91,9 +89,7 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & // Remove duplicate rules across files. This must be done in serial, not // in parallel, and must be done from the last rule to the first rule. { - // Zig accesses `chunk.content.css.{imports_in_chunk_in_order,asts}` - // through the union field at each use site while also holding `entry` as a raw - // pointer into `imports_in_chunk_in_order`. In Rust, every `chunk.content.css.*` + // Every `chunk.content.css.*` access // re-enters the `Content` enum and re-borrows `chunk.content` as a whole, which // would alias the live `&mut entry`. Destructure the variant once so borrowck // can split the disjoint `CssChunk` struct fields (`imports_in_chunk_in_order` @@ -112,9 +108,7 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & let inner = layers.inner(); let len = inner.len(); let rules = if len > 0 { - // Zig `SmallList(LayerName,1).fromBabyListNoDeinit(layers.inner().*)` - // is a bitwise Vec→SmallList header transfer. In Rust the - // `Chunk::Layers` payload is the lifetime-erased shadow + // The `Chunk::Layers` payload is the lifetime-erased shadow // `bun_css::LayerName { v: Vec> }`, // not the real `css_parser::LayerName { v: SmallList<&'static [u8],1> }`, // so the layouts differ. Rebuild the real list element-by-element; @@ -162,13 +156,11 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & css_chunk.asts[i] = ast; } CssImportOrderKind::ExternalPath(p) => { - // Zig keeps `conditions: ?*ImportConditions` as a raw - // pointer to index 0 while the `while j != 1` loop reads - // `entry.conditions.len` / `.at(j)`. Taking `&mut` at index 0 here - // would exclusively borrow the whole `entry.conditions` Vec for - // the duration, aliasing those reads. The pointer is not actually - // dereferenced until after the loop (.zig:119), so defer acquiring - // the index-0 borrow until `actual_conditions` is built below. + // Taking `&mut` at index 0 here would exclusively borrow + // the whole `entry.conditions` Vec while the `while j != 1` + // loop below still reads `entry.conditions.len` / `.at(j)`. + // The borrow is not actually needed until after the loop, so + // defer acquiring it until `actual_conditions` is built below. let had_conditions = entry.conditions.len() > 0; if had_conditions { entry.condition_import_records.push(ImportRecord { @@ -195,15 +187,14 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & while j != 1 { j -= 1; - // Zig has no destructors, so when `ast_import` falls - // out of scope the bitwise-duplicated `ImportConditions` inside - // it (see `ptr::read` below) is simply abandoned. In Rust, - // dropping `ast_import` would run `Drop` on that aliased + // `ast_import` holds a bitwise-duplicated `ImportConditions` + // (see `ptr::read` below); + // dropping it would run `Drop` on that aliased // `ImportConditions` — freeing Global-backed buffers // (`MediaList.media_queries: Vec`, `SupportsCondition::{Box,Vec}`, // `LayerName.v: SmallList`) that are still owned by // `entry.conditions[j]`, i.e. a double-free / UAF. Wrap in - // `ManuallyDrop` to mirror Zig's leak-on-scope-exit; the rule + // `ManuallyDrop` so the duplicate is abandoned instead; the rule // slab itself is arena-owned so it is reclaimed on arena reset. let ast_import = core::mem::ManuallyDrop::new(BundlerStyleSheet { options: ParserOptions::default(None), @@ -218,7 +209,7 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & loc: Location::dummy(), ..Default::default() }; - // SAFETY: Zig `entry.conditions.at(j).*` — shallow struct + // SAFETY: shallow struct // copy. The duplicate is never dropped (`ManuallyDrop` // above), so the aliased heap stays singly-owned by // `entry.conditions[j]`. @@ -283,9 +274,8 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & b"text/css", strings::trim(print_result.code.as_slice(), b" \n\r\t"), ); - // Zig allocated into the worker arena (`arena`). // `encode_string_as_shortest_data_url` returns a heap `Vec`; - // copy it into the worker bump so ownership matches Zig (freed + // copy it into the worker bump (freed // at bundle teardown via arena reset). SAFETY: arena outlives // the chunk, so the `'bump → 'static` launder is sound — same // contract as every other CSS slice in this file. @@ -296,9 +286,8 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & } let mut empty_conditions = ImportConditions::default(); - // Index 0 is disjoint from every `at(j)` (j>=1) read above; only - // now do we materialize the exclusive borrow that Zig's raw pointer - // held the whole time. + // Index 0 is disjoint from every `at(j)` (j>=1) read above; + // only now do we materialize the exclusive borrow. let actual_conditions: &mut ImportConditions = if had_conditions { entry.conditions.mut_(0) } else { @@ -323,7 +312,12 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & p.pretty, entry.condition_import_records.len() as u32, ); - // SAFETY: Zig `actual_conditions.*` — shallow struct copy. + // SAFETY: shallow struct copy. The duplicate lives in an + // `ImportRule` inside the `arena_rule_list_one` slab assigned + // to `css_chunk.asts[i]`, whose elements never run `Drop` + // (`CssChunk::Drop` frees the slab via `set_len(0)`), so + // `entry.conditions[0]` / `empty_conditions` remain the sole + // owners of the interior heap. *import_rule.conditions_mut() = unsafe { core::ptr::read(actual_conditions) }; arena_rule_list_one(bump, BundlerCssRule::Import(import_rule)) @@ -348,7 +342,7 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & [source_index.get() as usize] .as_deref() .expect("css ast present"); - // SAFETY: Zig `original_stylesheet.*` — bitwise shallow copy of the + // SAFETY: bitwise shallow copy of the // stylesheet header. All interior allocations are arena-owned and never // freed via this view, so the duplicated `Vec`/`Vec` headers are // sound for read-only / reslice use below. @@ -405,20 +399,27 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & ); for rule in &original_rules[0..prefix_end] { if matches!(rule, BundlerCssRule::LayerStatement(_)) { - // SAFETY: Zig by-value copy of arena-backed rule. + // SAFETY: bitwise duplicate of a rule. The copy goes into + // an `arena_rule_list` slab installed in `css_chunk.asts[i]`, + // whose elements never run `Drop` (see `arena_rule_list` / + // `CssChunk::Drop`), so the rule's interior heap stays + // singly-owned by the original. new_rules.push(unsafe { core::ptr::read(rule) }); } } for rule in &original_rules[prefix_end..] { - // SAFETY: Zig by-value copy of arena-backed rule. + // SAFETY: bitwise duplicate of a rule. The copy goes into + // an `arena_rule_list` slab installed in `css_chunk.asts[i]`, + // whose elements never run `Drop` (see `arena_rule_list` / + // `CssChunk::Drop`), so the rule's interior heap stays + // singly-owned by the original. new_rules.push(unsafe { core::ptr::read(rule) }); } // `ast.rules` is the shallow-copied header aliasing the // source stylesheet's arena buffer (see `ptr::read` above). // Dropping it would `drop_in_place` the aliased rules and - // free the shared backing array. Leak the header (Zig - // semantics: bitwise overwrite) before installing the - // freshly-allocated list. + // free the shared backing array. Leak the header + // before installing the freshly-allocated list. let _ = core::mem::ManuallyDrop::new(core::mem::replace( &mut ast.rules, arena_rule_list(new_rules), @@ -478,7 +479,7 @@ fn wrap_rules_with_conditions( // Generate "@layer" wrappers. Note that empty "@layer" rules still have // a side effect (they set the layer order) so they cannot be removed. if let Some(l) = &item.layer { - // SAFETY: Zig `const layer = l.v;` — by-value `?LayerName` copy. The + // SAFETY: by-value `?LayerName` copy. The // `SmallList<&'static [u8],1>` payload is arena-backed and never // freed via this view, so the bitwise duplicate is sound (same as // every other `ptr::read` shallow-copy in this file). @@ -493,8 +494,7 @@ fn wrap_rules_with_conditions( // `ast.rules.v` may be the shallow-copied / offset-resliced // header aliasing the source stylesheet's buffer (see the // `ptr::read` / `Vec::from_raw_parts` above) — dropping it - // would free into another allocation. Zig's `= .{}` is a - // bitwise overwrite; mirror that by leaking the header. + // would free into another allocation; leak the header instead. let _ = core::mem::ManuallyDrop::new(core::mem::take(&mut ast.rules.v)); do_block_rule = false; } @@ -560,4 +560,3 @@ pub use crate::DeferredBatchTask; pub use crate::ParseTask; pub use crate::ThreadPool; -// ported from: src/bundler/linker_context/prepareCssAstsForChunk.zig diff --git a/src/bundler/linker_context/renameSymbolsInChunk.rs b/src/bundler/linker_context/renameSymbolsInChunk.rs index 43946ee509a4..394686317b37 100644 --- a/src/bundler/linker_context/renameSymbolsInChunk.rs +++ b/src/bundler/linker_context/renameSymbolsInChunk.rs @@ -22,9 +22,9 @@ use crate::{Chunk, LinkerContext, StableRef, WrapKind}; // symbol scope assignment). `files_in_order` is the chunk's own file list; // without code-splitting, files are partitioned across chunks so per-row // writes are disjoint. With code-splitting a `source_index` may appear in -// multiple chunks — the Zig original has the same overlap; the writes are +// multiple chunks; the writes are // idempotent (`declared_symbols` flag set, scope-member sort) so the race is -// benign there but is still a Stacked Borrows hazard here. Mitigation: never +// benign but is still a Stacked Borrows hazard. Mitigation: never // materialize `&mut LinkerContext` (would assert whole-context exclusivity // across N tasks); take `*mut LinkerContext` raw, deref to `&LinkerContext` // for reads, and access SoA columns via `split_raw()` root-provenance @@ -100,8 +100,7 @@ pub unsafe fn rename_symbols_in_chunk( ) }; - // `symbol::Map` is not `Clone`/`Copy`; Zig passed the struct - // (slice header) by value. Build a non-owning shallow view via + // `symbol::Map` is not `Clone`/`Copy`. Build a non-owning shallow view via // `from_bump_slice` so the renamer's `Map` does not free graph storage on // drop. // SAFETY: `c.graph.symbols` outlives the returned `ChunkRenamer` (both are @@ -147,7 +146,6 @@ pub unsafe fn rename_symbols_in_chunk( count += item.len() as u32; } - // PERF(port): Zig pre-set len and filled via slice writes; using push() here let mut list: Vec = Vec::with_capacity(count as usize); let stable_source_indices = c.graph.stable_source_indices.slice(); for item in imports_from_other_chunks { @@ -280,7 +278,6 @@ pub unsafe fn rename_symbols_in_chunk( r.add_top_level_symbol(stable_ref.r#ref); } - // Zig used `r.temp_arena` for this list; arena param dropped let mut sorted: Vec = Vec::new(); for &source_index in files_in_order { @@ -413,7 +410,6 @@ pub unsafe fn rename_symbols_in_chunk( &mut sorted, ); } - // Zig: `@TypeOf(r.number_scope_pool.hive.used).initEmpty()`. r.number_scope_pool.hive.used = bun_collections::hive_array::HiveBitSet::init_empty(); } } @@ -425,4 +421,3 @@ pub use crate::DeferredBatchTask; pub use crate::ParseTask; pub use crate::ThreadPool; -// ported from: src/bundler/linker_context/renameSymbolsInChunk.zig diff --git a/src/bundler/linker_context/scanImportsAndExports.rs b/src/bundler/linker_context/scanImportsAndExports.rs index 5205df726d7f..08166220d761 100644 --- a/src/bundler/linker_context/scanImportsAndExports.rs +++ b/src/bundler/linker_context/scanImportsAndExports.rs @@ -1,12 +1,10 @@ -//! Port of `src/bundler/linker_context/scanImportsAndExports.zig`. -// -// The Zig body takes ~20 simultaneous mutable column slices -// (`this.graph.ast.items(.field)`) and freely interleaves them with +// This pass works over ~20 simultaneous mutable column slices +// (`this.graph.ast.items(.field)`) freely interleaved with // `&mut LinkerContext` method calls. Rust's borrowck forbids both holding // overlapping `&mut [T]` columns from the same `MultiArrayList` and holding // any `&mut` column across a `&mut self` call into `this.graph`. The columns // are physically disjoint (SoA layout) and the underlying `MultiArrayList` -// never reallocates inside this function, so this port caches the column +// never reallocates inside this function, so we cache the column // base pointers once via `Slice::items_raw` and dereferences them at each // use site through `*mut [T]`. This is the documented escape hatch in // `bun_collections::multi_array_list::Slice::items_raw`. @@ -86,7 +84,6 @@ pub fn scan_imports_and_exports( // `reachable_files` is borrowed out of `this.graph` while the // body also calls `&mut this.graph` methods. Snapshot the indices. - // PERF(port): was zero-copy slice; profile. let mut reachable: Vec = this.graph.reachable_files.slice().to_vec(); // ── cache SoA column base pointers ──────────────────────────────────── @@ -115,8 +112,8 @@ pub fn scan_imports_and_exports( let module_refs: *mut [Ref] = ast.module_ref; let wrapper_refs: *mut [Ref] = ast.wrapper_ref; let parts_list: *mut [PartList] = ast.parts; - // Zig: `[]?*bun.css.BundlerStyleSheet` — element is a *mutable* nullable - // pointer (matches `BundledAst.css: Option<*mut BundlerStyleSheet>`). + // Element is a *mutable* nullable + // pointer (`BundledAst.css: Option<*mut BundlerStyleSheet>`). let css_asts: *mut [CssCol] = ast.css; let input_files: *mut [Source] = input.source; @@ -131,9 +128,6 @@ pub fn scan_imports_and_exports( let cjs_export_copies: *mut [js_meta::CjsExportCopies] = meta.cjs_export_copies; let entry_point_part_indices: *mut [Index] = meta.entry_point_part_index; - // Zig copies `symbols` to a local and `defer`-writes it back. - // In Rust `this.graph.symbols` is the same storage, so no copy-back needed. - { // Step 1: Figure out what modules must be CommonJS for source_index_ in &reachable { @@ -486,11 +480,9 @@ pub fn scan_imports_and_exports( // imported using an import star statement. // Note: `do` will wait for all to finish before moving forward // - // Zig dispatched via `worker_pool.each(arena, this, - // doStep5, reachable_files)` (parallel fan-out, blocks until done). // `do_step5` only touches distinct SoA rows per `source_index` (the - // columns are pre-sized and never reallocate during this step), - // matching the Zig invariant. We pass `*mut LinkerContext` through a + // columns are pre-sized and never reallocate during this step). + // We pass `*mut LinkerContext` through a // `Sync` wrapper; the callee derefs it to `&LinkerContext` (shared) // for reads and writes per-row cells via raw `split_raw()` pointers — // mirroring `GenerateChunkCtx` (`generate_js_renamer` likewise never @@ -523,11 +515,10 @@ pub fn scan_imports_and_exports( ); } - // Zig calls `takeAstOwnership` here because `doStep5` appends to - // `part.dependencies`/`declared_symbols` with the worker allocator. - // In the Rust port those are global-allocator `Vec`s (thread-safe to - // grow) and `do_step_5` never pushes to the arena-backed `PartList`/ - // import-record columns, so no transfer is needed. + // No post-fan-out ownership transfer is needed: `do_step5` only grows + // global-allocator `Vec`s (`part.dependencies`, `declared_symbols`), + // which are thread-safe to grow per-row, and never pushes to the + // arena-backed `PartList`/import-record columns. } if FeatureFlags::HELP_CATCH_MEMORY_ISSUES { @@ -602,7 +593,7 @@ pub fn scan_imports_and_exports( }; // Allocate the identifier-name buffer from the linker arena so it is - // reclaimed when the link pass ends (Zig: `this.arena().alloc(u8, ...)`). + // reclaimed when the link pass ends. // The slices handed out below are stored in `Symbol.original_name: *const [u8]`, // which is arena-lifetime by construction. let string_buffer: &mut [u8] = this @@ -613,8 +604,7 @@ pub fn scan_imports_and_exports( // `ptr`/`cap` and frees it via the global arena. Here the // backing buffer is arena-owned (bumpalo), so dropping would hand // mimalloc a pointer it never allocated. Wrap in `ManuallyDrop` — - // the arena reclaims the storage on reset, matching Zig's implicit - // no-destructor semantics. + // the arena reclaims the storage on reset. let mut builder = core::mem::ManuallyDrop::new(bun_core::StringBuilder { len: 0, cap: string_buffer.len(), @@ -696,7 +686,7 @@ pub fn scan_imports_and_exports( } } - // End-of-scope assert (Zig used `defer`); relies on there being no + // End-of-scope assert; relies on there being no // early returns inside this block. debug_assert!(builder.len == builder.cap); @@ -815,11 +805,10 @@ pub fn scan_imports_and_exports( this.top_level_symbols_to_parts(target_source_index.get(), target_ref); dependencies.reserve(top_to_parts.len()); for part_index in top_to_parts { - // PERF(port): was appendAssumeCapacity dependencies.push(Dependency { // `crate::Index` ↔ `bun_ast::Index` are both - // `#[repr(transparent)] u32` newtypes ported from the - // same Zig `ast.Index`; bridge by `.value` until B-3 + // `#[repr(transparent)] u32` newtypes; + // bridge by `.value` until B-3 // collapses them to a single re-export. source_index: bun_ast::Index(target_source_index.get()), part_index: *part_index, @@ -831,7 +820,6 @@ pub fn scan_imports_and_exports( // Ensure "exports" is included if the current output format needs it if force_include_exports { - // PERF(port): was appendAssumeCapacity dependencies.push(Dependency { source_index: bun_ast::Index::source(source_index as usize), part_index: bun_ast::NAMESPACE_EXPORT_PART_INDEX, @@ -840,7 +828,6 @@ pub fn scan_imports_and_exports( // Include the wrapper if present if add_wrapper { - // PERF(port): was appendAssumeCapacity dependencies.push(Dependency { source_index: bun_ast::Index::source(source_index as usize), part_index: col_ref!(wrapper_part_indices)[id].get(), @@ -1175,7 +1162,7 @@ fn should_call_runtime_require(format: options::Format) -> bool { } // ────────────────────────────────────────────────────────────────────────── -// DependencyWrapper — port of the inner Zig struct. +// DependencyWrapper // ────────────────────────────────────────────────────────────────────────── struct DependencyWrapper<'a> { flags: &'a mut [js_meta::Flags], @@ -1261,7 +1248,7 @@ impl DependencyWrapper<'_> { } // ────────────────────────────────────────────────────────────────────────── -// ExportStarContext — port of the inner Zig struct. Holds raw column ptrs. +// ExportStarContext — holds raw column ptrs. // ────────────────────────────────────────────────────────────────────────── struct ExportStarContext<'a> { import_records_list: *mut [ImportRecordList<'a>], @@ -1315,7 +1302,6 @@ impl<'a> ExportStarContext<'a> { // Collect (alias, name) pairs so the // loop body can mutably borrow `resolved_exports` / `imports_to_bind`. - // PERF(port): was zero-copy `iter()` over StringArrayHashMap; profile. let exports_len = col_ref!(self.named_exports)[other_id].keys().len(); 'next_export: for ne_i in 0..exports_len { // `BackRef<[u8]>` — points into the `named_exports` key @@ -1387,8 +1373,7 @@ impl<'a> ExportStarContext<'a> { self.add_exports(resolved_exports, target_id, other_source_index); } - // Zig: `defer this.source_index_stack.shrinkRetainingCapacity(stack_end_pos - 1)` - // — inlined at scope end (no early returns after the push). + // Scope-end truncation (no early returns after the push). self.source_index_stack.truncate(stack_end_pos - 1); } } @@ -1404,8 +1389,8 @@ mod __css_validation { use bun_ast::Log; use bun_collections::{ArrayHashMap, StringArrayHashMap}; - // Zig: `?*bun.css.BundlerStyleSheet` — keep the column element as a raw - // `*mut` (matches `BundledAst.css`), so we never launder a `&T` into `&mut T`. + // Keep the column element as a raw + // `*mut` (`BundledAst.css`), so we never launder a `&T` into `&mut T`. use crate::bundled_ast::CssCol; /// `ArrayHashAdapter` so `LocalScope` (`ArrayHashMap, LocalEntry>`) @@ -1712,7 +1697,6 @@ mod __css_validation { } } - // PERF(port): was stack-fallback arena (1024 bytes) — profile. // SAFETY: parse_graph backref valid for link step. Read-only. let input = this.parse_graph().input_files.split_raw(); let mut visitor = Visitor { @@ -1733,4 +1717,3 @@ mod __css_validation { } } -// ported from: src/bundler/linker_context/scanImportsAndExports.zig diff --git a/src/bundler/linker_context/writeOutputFilesToDisk.rs b/src/bundler/linker_context/writeOutputFilesToDisk.rs index ad44c32e0629..fa63f2c141ad 100644 --- a/src/bundler/linker_context/writeOutputFilesToDisk.rs +++ b/src/bundler/linker_context/writeOutputFilesToDisk.rs @@ -25,7 +25,7 @@ use bun_sys::{ write_file_with_path_buffer, }; -/// Zig: `bun.bytecode_extension` (".jsc"). Mirror of `src/bun.zig:bytecode_extension`. +/// Bytecode output file extension (also defined in `generateChunksInParallel.rs`). const BYTECODE_EXTENSION: &str = ".jsc"; pub fn write_output_files_to_disk( @@ -66,9 +66,8 @@ pub fn write_output_files_to_disk( } }; // Optimization: when writing to disk, we can re-use the memory - // PERF(port): MaxHeapAllocator reuses the largest allocation between - // iterations. Verify bun_alloc::MaxHeapAllocator semantics - // match (init/reset/deinit). DynAlloc is currently `()` so the arena + // between iterations: MaxHeapAllocator retains the largest allocation. + // DynAlloc is currently `()` so the arena // handles below are placeholders; allocation routes through global mimalloc. let mut max_heap_allocator = MaxHeapAllocator::init(); let mut _max_heap_allocator_source_map = MaxHeapAllocator::init(); @@ -79,8 +78,8 @@ pub fn write_output_files_to_disk( let bv2: &mut BundleV2 = unsafe { &mut *LinkerContext::bundle_v2_ptr(std::ptr::from_mut::(c)) }; - // Zig passes `chunk` (an element of `chunks`) and `chunks` - // together into `code()`/`code_standalone()`. The callee now takes + // `code()`/`code_standalone()` take both `chunk` (an element of `chunks`) + // and `chunks` as // `&Chunk` / `&[Chunk]` (read-only), so iterate by index and reborrow // shared; the only per-chunk mutation is the `intermediate_output` // take/restore done via `chunks[i]`. @@ -115,7 +114,7 @@ pub fn write_output_files_to_disk( } let _trace2 = bun_core::perf::trace("Bundler.writeChunkToDisk"); - // PERF(port): Zig `defer max_heap_allocator.reset()` — reset the reusable + // Reset the reusable // buffer after each chunk. `MaxHeapAllocator::scope()` returns an RAII // guard that resets on drop and derefs to the arena, so when // `code_allocator` is wired up it can borrow through `_code_allocator`. @@ -235,15 +234,11 @@ pub fn write_output_files_to_disk( + a.len() + b.len() + b"\n".len(); - // PERF(port): Zig used Chunk.IntermediateOutput.allocatorForSize(total_len) - // to pick a size-appropriate arena. Using Vec (global mimalloc) here. let mut buf: Vec = Vec::with_capacity(total_len); - // PERF(port): was appendSliceAssumeCapacity buf.extend_from_slice(&code_result.buffer); buf.extend_from_slice(source_map_start); buf.extend_from_slice(a); buf.extend_from_slice(b); - // PERF(port): was appendAssumeCapacity buf.push(b'\n'); code_result.buffer = buf.into_boxed_slice(); } @@ -298,11 +293,8 @@ pub fn write_output_files_to_disk( let source_map_start = b"//# sourceMappingURL=data:application/json;base64,"; let total_len = code_result.buffer.len() + source_map_start.len() + encode_len + 1; - // PERF(port): Zig used `code_with_inline_source_map_allocator` (MaxHeapAllocator) - // for this Vec to reuse across iterations. let mut buf: Vec = Vec::with_capacity(total_len); - // PERF(port): was appendSliceAssumeCapacity buf.extend_from_slice(&code_result.buffer); buf.extend_from_slice(source_map_start); @@ -310,7 +302,6 @@ pub fn write_output_files_to_disk( buf.resize(old_len + encode_len, 0); let _ = bun_base64::encode(&mut buf[old_len..], &output_source_map); - // PERF(port): was appendAssumeCapacity buf.push(b'\n'); code_result.buffer = buf.into_boxed_slice(); } @@ -515,8 +506,7 @@ pub fn write_output_files_to_disk( }, referenced_css_chunks: match &chunk.content { Content::Javascript(js) => { - // Zig: `@ptrCast(dupe(u32, js.css_chunks))` — `Index` is - // `#[repr(transparent)]` over u32. + // `Index` is `#[repr(transparent)]` over u32. js.css_chunks .iter() .map(|&i| OutputFileIndex::init(i)) @@ -618,4 +608,3 @@ pub fn write_output_files_to_disk( pub use crate::{DeferredBatchTask, ParseTask, ThreadPool}; -// ported from: src/bundler/linker_context/writeOutputFilesToDisk.zig diff --git a/src/bundler/options.rs b/src/bundler/options.rs index fa104ddab094..62e05c47ae10 100644 --- a/src/bundler/options.rs +++ b/src/bundler/options.rs @@ -11,7 +11,6 @@ use bun_resolver::fs as Fs; use bun_resolver::fs::PathResolverExt as _; use bun_resolver::package_json::{MacroMap as MacroRemap, PackageJSON}; use std::borrow::Cow; -// Zig: `analytics.Features.{define,loaders,macros,external} += n`. use bun_analytics as analytics; use enum_map::EnumMap; @@ -23,16 +22,16 @@ pub use defines::Define; use crate::defines::{DefineDataExt as _, DefineExt as _}; pub use bun_options_types::global_cache::GlobalCache; -// Zig `ESModule.ConditionsMap` — canonical alias lives in the resolver. +// Canonical alias lives in the resolver. pub use bun_resolver::package_json::ConditionsMap; // TODO(b2-blocked): bun_sys::Dir — directory handle. Mapped to Fd for now // (matches `bun.FD.fromStdDir` pattern). pub type Dir = bun_sys::Fd; -/// `Loader.HashTable` (Zig nested type alias). Unified with the canonical +/// Unified with the canonical /// `bun_ast::LoaderHashTable` so the resolver and /// bundler share one nominal map type (PORTING.md crate-tier rule). pub(crate) use bun_ast::LoaderHashTable; -/// `Loader.Map` (Zig nested type alias). +/// Per-[`Loader`] static byte-string map (e.g. the stdin synthetic file names). pub type LoaderEnumMap = EnumMap; /// `bun.http.MimeType` lives in `bun_http_types` (lower tier), not `bun_http`. @@ -42,7 +41,7 @@ mod bun_http { /// `bun.StringSet` (re-exported for `BundleOptions.bundler_feature_flags`). pub use bun_collections::StringSet; -/// `options.zig:Framework.ClientCssInJs` — TYPE_ONLY moved to top of module so +/// TYPE_ONLY moved to top of module so /// `entry_points.rs` (and the inline `options` mod) can resolve it before the /// gated `Framework` impl block below. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -71,9 +70,8 @@ pub fn validate_path( return Box::default(); } // TODO: switch to getFdPath()-based implementation - // Zig used `std.fs.path.resolve(arena, &.{cwd, rel_path})`; // `join_abs_string` resolves `.`/`..` against `cwd` into a threadlocal - // buffer which is then boxed (matches the arena.dupe in the Zig path). + // buffer which is then boxed. let _ = path_kind; let out = bun_paths::resolve_path::join_abs_string::(cwd, &[rel_path]); @@ -107,7 +105,7 @@ pub use bun_js_parser::options::AllowUnresolved; // plain `.clone()`. pub use bun_resolver::options::{ExternalModules, WildcardPattern}; -/// `options.zig` `ExternalModules.isNodeBuiltin`. Free fn (not an inherent +/// Free fn (not an inherent /// method) because `ExternalModules` is now a foreign type and Rust forbids /// inherent impls across crates (E0116). pub fn is_node_builtin(str: &[u8]) -> bool { @@ -130,7 +128,7 @@ fn default_wildcard_patterns() -> Vec { .collect() } -/// `options.zig` `ExternalModules.init`. Free fn for the same orphan-rule +/// Free fn for the same orphan-rule /// reason as [`is_node_builtin`]; stays at bundler tier because it needs /// `Fs`/`logger`/`NODE_BUILTIN_PATTERNS`. pub fn init_external_modules( @@ -157,13 +155,7 @@ pub fn init_external_modules( result.node_modules.insert(pattern).expect("unreachable"); } } - Target::Bun => { - // // TODO: fix this stupid copy - // result.node_modules.hash_map.ensureTotalCapacity(BunNodeBuiltinPatternsCompat.len) catch unreachable; - // for (BunNodeBuiltinPatternsCompat) |pattern| { - // result.node_modules.insert(pattern) catch unreachable; - // } - } + Target::Bun => {} _ => {} } @@ -172,7 +164,6 @@ pub fn init_external_modules( } let mut patterns: Vec = Vec::with_capacity(DEFAULT_WILDCARD_PATTERNS.len()); - // PERF(port): was appendSliceAssumeCapacity patterns.extend(default_wildcard_patterns()); for external in externals { @@ -230,7 +221,7 @@ pub use bun_options_types::bundle_enums::ModuleType; pub static MODULE_TYPE_LIST: phf::Map<&'static [u8], ModuleType> = ModuleType::LIST; // Re-export of `bun_ast::Target`. -// Spec options.zig:379 has exactly ONE `Target`; re-export the canonical enum so +// There is exactly ONE `Target`; re-export the canonical enum so // `BundleOptions.target`, `js_printer::Options.target`, the resolver, and css // targets all share one nominal type (kills the `to_bundle_enums_target` shim). pub(crate) use bun_ast::Target; @@ -361,7 +352,7 @@ pub use bun_options_types::Format; pub use bun_options_types::WindowsOptions; // Re-export of `bun_ast::Loader`. -// Spec options.zig:568 has exactly ONE `Loader`; re-export so the bundler's +// There is exactly ONE `Loader`; re-export so the bundler's // `BundleOptions.loaders` and the resolver's `Path::loader()` operate on the // same nominal type. pub(crate) use bun_ast::Loader; @@ -553,9 +544,8 @@ pub fn get_loader_and_virtual_source<'a>( if let Some(eval_source) = jsc_vm.eval_source() { // SAFETY: eval_source outlives jsc_vm let eval_source: &'a bun_ast::Source = unsafe { &*eval_source }; - // Spec: `bun.pathLiteral("/[eval]")` — the eval/stdin entry path is built - // via `bun.pathLiteral` (cli.zig / run_command.zig / bun.js.zig), which - // rewrites `/` → `\` on Windows, so the suffix uses the platform separator. + // The eval/stdin entry path uses the platform path separator + // (`/` becomes `\` on Windows), so the suffix is per-platform. const EVAL_SUFFIX: &[u8] = if cfg!(windows) { b"\\[eval]" } else { @@ -595,8 +585,7 @@ pub fn get_loader_and_virtual_source<'a>( if !jsc_vm.blob_needs_read_file(blob) { // SAFETY: `path.text` aliases jsc_vm-owned storage (blob filename // or normalized specifier), which outlives the `virtual_source` - // returned to the caller — matches Zig `getLoaderAndVirtualSource` - // where `Fs.Path` and `logger.Source.path` share one type. + // returned to the caller. let static_text: &'static [u8] = bun_ast::StoreStr::new(path.text).slice(); *virtual_source_to_use = Some(bun_ast::Source { path: bun_paths::fs::Path::init(static_text), @@ -674,9 +663,9 @@ const DEFAULT_LOADERS_POSIX: &[(&[u8], Loader)] = &[ #[cfg(all(windows, test))] const DEFAULT_LOADERS_WIN32_EXTRA: &[(&[u8], Loader)] = &[(b".sh", Loader::Bunsh)]; -/// File-extension → default [`Loader`] map (options.zig `defaultLoaders`). +/// File-extension → default [`Loader`] map. /// -/// PERF(port): was `phf::Map<&[u8], Loader>`. phf hashes the full key (SipHash +/// PERF: deliberately not a `phf::Map<&[u8], Loader>`. phf hashes the full key (SipHash /// over up to 9 bytes) + probes a displacement table + does a final memcmp on /// every lookup. With only 22 keys bucketing into 5 distinct lengths /// (3/4/5/6/9, all `.`-prefixed), a length-gated `match` is cheaper: one @@ -749,7 +738,7 @@ impl DefaultLoaders { #[test] fn default_loaders_match_table() { // Guard against drift between the length-gated match and the canonical - // tuple list above (Zig source of truth). + // tuple list above. for (ext, loader) in DEFAULT_LOADERS_POSIX { assert_eq!(DEFAULT_LOADERS.get(ext), Some(loader), "ext {:?}", ext); } @@ -799,7 +788,6 @@ impl ESMConditions { require_condition_map.reserve(defaults.len() + 2 + addon_extra + conditions.len()); style_condition_map.reserve(defaults.len() + 2 + conditions.len()); - // PERF(port): was assume_capacity import_condition_map.insert(b"import".as_slice(), ()); require_condition_map.insert(b"require".as_slice(), ()); style_condition_map.insert(b"style".as_slice(), ()); @@ -859,7 +847,6 @@ impl ESMConditions { self.style.reserve(conditions.len()); for condition in conditions { - // PERF(port): was assume_capacity self.default.insert(*condition, ()); self.import.insert(*condition, ()); self.require.insert(*condition, ()); @@ -902,7 +889,7 @@ pub use default_user_defines as DefaultUserDefines; pub fn defines_from_transform_options( log: &mut bun_ast::Log, - // PERF(port): borrowed, not owned — the caller (`load_defines`) holds + // PERF: borrowed, not owned — the caller (`load_defines`) holds // `transform_options` behind an `Arc`, so taking the `StringMap` by value // forced a full deep clone of the `--define` map *every* VM init even though // each value gets cloned again below on insert. Reading it through `&` keeps @@ -922,11 +909,9 @@ pub fn defines_from_transform_options( None => (&[], &[]), }; - // Zig stringHashMapFromArrays — inlined as concrete RawDefines build (over-reserves +4). let mut user_defines: defines::RawDefines = defines::RawDefines::default(); user_defines.reserve(input_keys.len() + 4); for (i, key) in input_keys.iter().enumerate() { - // PERF(port): was assume_capacity user_defines.insert(key.as_ref(), input_values[i].clone()); } @@ -1095,7 +1080,7 @@ impl ResolveFileExtensions { } /// Convert a static `&[&[u8]]` default into an owned `Box<[Box<[u8]>]>`. -/// PERF(port): the Zig kept these as borrowed `[]const string`; we own them so +/// We own them so /// user-provided lists (e.g. `transform.extension_order`) can be stored without /// `Box::leak` (PORTING.md §Forbidden patterns). #[inline] @@ -1148,13 +1133,12 @@ pub fn loaders_from_transform_options( let mut loaders = StringArrayHashMap::::default(); loaders.reserve(u32::try_from(total_capacity).expect("int cast") as usize); for (i, ext) in input_loaders.extensions.iter().enumerate() { - // PERF(port): was assume_capacity loaders.insert(ext, loader_values[i]); } - // Zig `getOrPutValue` → contains+insert; `Loader` is not `Default` + // contains+insert (only when absent); `Loader` is not `Default` // so the `V: Default`-gated `StringArrayHashMap::get_or_put_value` does not - // apply. Semantics are identical (insert only when absent). + // apply. for ext in DEFAULT_LOADER_EXT { if !loaders.contains(*ext) { loaders.insert(*ext, *DEFAULT_LOADERS.get(*ext).unwrap()); @@ -1259,10 +1243,8 @@ pub struct BundleOptions<'a> { /// Set of enabled feature flags for dead-code elimination via `import { feature } from "bun:bundle"`. /// Initialized once from the CLI --feature flags. /// - /// Zig: `*const bun.StringSet = &Runtime.Features.empty_bundler_feature_flags`. /// `None` ≡ the static empty set; `Some` is the owned `Box` returned by - /// `Runtime::Features::init_bundler_feature_flags` (freed on Drop, matching - /// options.zig:1888-1892 which frees iff distinct from the static empty set). + /// `Runtime::Features::init_bundler_feature_flags` (freed on Drop). pub bundler_feature_flags: Option>, pub loaders: LoaderHashTable, pub resolve_dir: Cow<'static, [u8]>, @@ -1278,8 +1260,7 @@ pub struct BundleOptions<'a> { pub hot_module_reloading: bool, pub react_fast_refresh: bool, pub inject: Option]>>, - // `bun_url::URL<'a>` borrows its input string; Zig stored it borrowing - // `transform_options.origin` (sibling field). The owned variant keeps the + // `bun_url::URL<'a>` borrows its input string; the owned variant keeps the // struct self-contained. pub origin: bun_url::OwnedURL, pub output_dir_handle: Option

, @@ -1303,7 +1284,7 @@ pub struct BundleOptions<'a> { pub target: Target, pub main_fields: Box<[Box<[u8]>]>, /// TODO: remove this in favor accessing bundler.log - /// raw `*mut` (not `&'a mut`) — Zig aliases the same `*Log` + /// raw `*mut` (not `&'a mut`) — the same `*Log` is aliased /// into `Transpiler.log` / `Resolver.log` / `Linker.log`. A stored /// `&'a mut` here would assert uniqueness for `'a` and make every access /// through those sibling raw pointers UB under stacked borrows. @@ -1352,7 +1333,7 @@ pub struct BundleOptions<'a> { pub global_cache: GlobalCache, pub prefer_offline_install: bool, pub prefer_latest_install: bool, - /// Spec `options.zig:1753`: `?*const Api.BunInstall`. Stored as a raw + /// Stored as a raw /// `NonNull` (not `Option<&'a _>`) because every CLI caller borrows the /// process-lifetime `ctx.install: Box` whose lifetime is /// unrelated to `'a`; a typed reference forced an `unsafe { &*(p as *const _) }` @@ -1460,8 +1441,7 @@ impl<'a> BundleOptions<'a> { /// them on the worker dropped the parent's allocation). Every owned field /// is `Clone`d; raw-pointer / `Copy` / `&'a` fields copy directly. /// - /// PERF(port): Zig's `transpiler.* = from.*` is a shallow struct copy - /// (slices alias the parent's arena). The Rust port owns these as `Box`, + /// These fields are owned as `Box`, /// so a per-worker clone allocates. Profile if it shows up on a hot path; /// the hot fields (`define`, `loaders`, `conditions`) are O(dozens) entries. pub fn for_worker(&self) -> BundleOptions<'a> { @@ -1590,8 +1570,9 @@ impl<'a> BundleOptions<'a> { /// Shared-borrow the per-Transpiler `Log`. /// - /// SAFETY: `self.log` is non-null once `from_api` / `Transpiler::init` has - /// run (Zig spec `options.zig:1714`: `log: *logger.Log`, non-optional). + /// SAFETY: `self.log` is non-null: `Transpiler::init_in_place` validates + /// the pointer via `NonNull::new(log).expect(..)` before storing it and + /// before calling `from_api` (which has no other callers). /// The pointee is the caller-owned arena `Log` which outlives `self`. The /// same allocation is aliased into `Transpiler.log` / `Resolver.log` / /// `Linker.log` as raw `*mut`; a `&` here is sound so long as no caller @@ -1666,9 +1647,8 @@ impl<'a> BundleOptions<'a> { arena: &bun_alloc::Arena, loader_: Option<&mut DotEnv::Loader>, ) -> Result<(), bun_core::Error> { - // spec `loadDefines(..., env: ?*const options.Env)` had its - // sole caller pass `&this.options.env` (transpiler.zig:334). Forwarding - // that as `Option<&Env>` forced the caller into an aliased-`&mut` UB + // Forwarding the env as an `Option<&Env>` parameter forced the + // caller into an aliased-`&mut` UB // raw-pointer dance under Stacked Borrows. Dropped the param and read // `&self.env` here instead — disjoint from the `self.define` / // `self.defines_loaded` writes below, so borrowck splits it cleanly. @@ -1680,7 +1660,7 @@ impl<'a> BundleOptions<'a> { if self.defines_loaded { return Ok(()); } - // PERF(port): the spec uses borrowed static literals for the three + // PERF: borrowed static literals for the three // constant cases; only the env-loader case needs an owned copy (it has // to outlive the `&mut loader_` we pass below, so it can't stay a borrow // into the loader). `Cow` keeps the literals zero-alloc — matters because @@ -1753,7 +1733,7 @@ impl<'a> BundleOptions<'a> { footer: Cow::Borrowed(b""), banner: Cow::Borrowed(b""), log, - // `define` is `undefined` in Zig and filled by `loadDefines` later; + // `define` is filled by `load_defines` later; // initialize empty so the struct is well-formed before `load_defines` runs. define: Box::new(defines::Define { identifiers: Default::default(), @@ -1878,9 +1858,7 @@ impl<'a> BundleOptions<'a> { opts.env.disable_default_env_files = transform.disable_default_env_files; if let Some(origin) = &transform.origin { - // ownership — `URL<'_>` borrows its input. The Zig - // `URL.parse` borrowed `transform.origin` (a sibling of - // `opts.transform_options`); here `OwnedURL` owns the href and + // ownership — `URL<'_>` borrows its input; `OwnedURL` owns the href and // callers borrow via `.url()`. opts.origin = bun_url::OwnedURL::from_href(origin.clone()); } @@ -1957,7 +1935,7 @@ impl<'a> BundleOptions<'a> { .collect(); } - // Zig passed `log` directly; reborrow the raw `*mut Log` + // Reborrow the raw `*mut Log` // for the duration of this call only. opts.external = init_external_modules( &mut fs.fs, @@ -1990,8 +1968,7 @@ impl<'a> BundleOptions<'a> { if opts.write && !opts.output_dir.is_empty() { let handle = open_output_dir(&opts.output_dir)?; opts.output_dir_handle = Some(handle); - // Zig `fs.getFdPath(.fromStdDir(handle))` interns into - // `dirname_store`; the inline `bun_resolver::fs::FileSystem` does + // The inline `bun_resolver::fs::FileSystem` does // not yet expose `get_fd_path`, so resolve via `bun_sys` and box. let mut buf = bun_paths::PathBuffer::uninit(); let dir = bun_sys::get_fd_path(handle, &mut buf).map_err(bun_core::Error::from)?; @@ -2022,9 +1999,7 @@ impl Drop for BundleOptions<'_> { fn drop(&mut self) { // self.define dropped automatically (Box). // - // bundler_feature_flags: Zig compared the pointer to - // `&Runtime.Features.empty_bundler_feature_flags` and freed iff distinct. - // In Rust the field is `Option>`; `None` ≡ the static + // bundler_feature_flags is `Option>`; `None` ≡ the static // empty set (nothing to free), `Some` drops the Box here automatically. } } @@ -2064,12 +2039,11 @@ pub mod bundle_options_defaults { } pub fn open_output_dir(output_dir: &[u8]) -> Result { - // Zig used `std.fs.cwd().openDir/.makeDir`; routed through - // `bun_sys` per CLAUDE.md (never `std::fs`). + // Routed through `bun_sys` per CLAUDE.md (never `std::fs`). match bun_sys::open_dir_at(bun_sys::Fd::cwd(), output_dir) { Ok(d) => Ok(d), Err(_) => { - // Zig: `std.fs.cwd().makeDir(output_dir)` — single-level mkdir + // Single-level mkdir // (fails ENOENT if parent missing). Do NOT use `make_path` (the // recursive `mkdir -p` variant) here. let mut buf = bun_paths::PathBuffer::uninit(); @@ -2103,7 +2077,7 @@ pub fn open_output_dir(output_dir: &[u8]) -> Result { } } -/// Port of `fs.zig` `Fs.File` (path + contents pair). `bun_resolver::fs` +/// Path + contents pair. `bun_resolver::fs` /// does not surface this type; local mirror keeps /// `TransformOptions.entry_point` self-contained. pub struct EntryPointFile { @@ -2143,7 +2117,6 @@ impl TransformOptions { }; let mut _cwd: Box<[u8]> = Box::from(b"/".as_slice()); - // Zig gated on `Environment.isWasi or Environment.isWindows`. #[cfg(any(target_os = "wasi", windows))] { // `getcwd_alloc` returns a NUL-terminated `ZBox`; strip the NUL @@ -2155,7 +2128,6 @@ impl TransformOptions { let mut define = StringHashMap::>::default(); define.reserve(1); - // PERF(port): was assume_capacity define.put_assume_capacity(b"process.env.NODE_ENV", b"development".as_slice().into()); let entry_point_name = entry_point.path.name(); @@ -2172,7 +2144,7 @@ impl TransformOptions { loader, resolve_dir: Box::from(entry_point_name.dir), entry_point, - // resolve_dir borrows from entry_point in Zig; cloned here so + // resolve_dir is cloned so // `TransformOptions` stays lifetime-free. main_fields: Target::default_main_fields_map()[Target::Browser], jsx: if loader.is_jsx() { @@ -2293,7 +2265,6 @@ impl Env { self.defaults.ensure_total_capacity(defaults.keys.len())?; for (i, key) in defaults.keys.iter().enumerate() { - // PERF(port): was assume_capacity self.defaults.append(EnvEntry { key: key.clone(), value: defaults.values[i].clone(), @@ -2508,9 +2479,7 @@ pub(crate) fn path_template_needs(data: &[u8], field: PlaceholderField) -> bool } // Shared body for PathTemplate::print / PathTemplateConst::print (D064). -// Zig `format(self, comptime _, _, writer: anytype)` writes raw path bytes via -// writer.writeAll; mapped to a byte-writer free fn (not `core::fmt::Display`) per -// PORTING.md "(comptime X: type, arg: X) writer → &mut impl bun_io::Write (bytes)". +// Writes raw path bytes via a byte-writer free fn (not `core::fmt::Display`). pub(crate) fn path_template_print( writer: &mut W, data: &[u8], @@ -2719,8 +2688,8 @@ impl PlaceholderConst { } impl PathTemplateConst { - /// Byte-writer form mirroring [`PathTemplate::print`] (Zig - /// `PathTemplate.format`). Kept as an inherent method so callers writing + /// Byte-writer form mirroring [`PathTemplate::print`]. + /// Kept as an inherent method so callers writing /// to `Vec` via `write!(.., "{}", template)` resolve through the /// blanket [`core::fmt::Display`] impl below. pub fn print(&self, writer: &mut W) -> bun_io::Result<()> { @@ -2771,4 +2740,3 @@ impl From for PathTemplate { } } -// ported from: src/bundler/options.zig diff --git a/src/bundler/transpiler.rs b/src/bundler/transpiler.rs index e09cc7052c8d..8b9ed7db971a 100644 --- a/src/bundler/transpiler.rs +++ b/src/bundler/transpiler.rs @@ -16,15 +16,14 @@ use bun_router::Router; use crate::options; -/// Port of `transpiler.zig:ResolveResults` — keyed by source path hash. +/// Keyed by source path hash. pub(crate) type ResolveResults = HashMap; -/// Port of `transpiler.zig:ResolveQueue` — `std.fifo.LinearFifo(resolver.Result, .Dynamic)`. // `bun_collections::LinearFifo>` would be exact, // but `DynamicBuffer` isn't re-exported from `bun_collections` yet. `VecDeque` // is structurally equivalent (growable ring buffer); swap once the re-export lands. pub(crate) type ResolveQueue = std::collections::VecDeque; -/// Spec `JSGlobalObject.BunPluginTarget` (JSGlobalObject.zig:265). Defined at +/// Defined at /// this tier (lowest crate that needs to name it) and re-exported from /// `bun_jsc::BunPluginTarget` so there is exactly one enum (no bridge between /// mirror types). @@ -39,11 +38,11 @@ pub enum BunPluginTarget { // Crosses FFI by-value to `JSBundlerPlugin__create` / `Bun__runOn*Plugins` // (C++: `typedef uint8_t BunPluginTarget`, `headers-handwritten.h`). NB: the // C++ header's *named* constants (`BunPluginTargetBrowser = 1`, `Node = 2`) -// disagree with Zig `JSGlobalObject.zig:265` (`node = 1`, `browser = 2`); Rust -// matches the Zig spec. The width (`u8`) is what matters at the ABI. +// disagree with the Rust enum (`Node = 1`, `Browser = 2`). The width (`u8`) +// is what matters at the ABI. bun_core::assert_ffi_discr!(BunPluginTarget, u8; Bun = 0, Node = 1, Browser = 2); -/// Spec PluginRunner.zig:34 `onResolve` — the JSC-aware resolve hook. +/// The JSC-aware resolve hook. /// /// The body calls `JSGlobalObject.runOnResolvePlugins`, so it cannot be /// defined at this tier (`bun_jsc` depends on this crate). `bun_jsc` provides @@ -62,14 +61,15 @@ pub trait PluginResolver { ) -> Result>, bun_core::Error>; } -/// Spec PluginRunner.zig — namespace for the static byte-level helpers +/// Namespace for the static byte-level helpers /// (`extractNamespace` / `couldBePlugin`). The stateful struct (with /// `global_object`) lives in `bun_jsc::PluginRunner` where `JSGlobalObject` is /// nameable; only the JSC-free helpers stay at this tier. pub struct PluginRunner; impl PluginRunner { - /// Spec PluginRunner.zig:14 `extractNamespace`. + /// Returns the `namespace:` prefix of `specifier`, or `b""` if it has none + /// (Windows drive-letter prefixes are not namespaces). pub fn extract_namespace(specifier: &[u8]) -> &[u8] { let Some(colon) = bun_core::index_of_char(specifier, b':') else { return b""; @@ -87,7 +87,7 @@ impl PluginRunner { &specifier[..colon] } - /// Spec PluginRunner.zig:22 `couldBePlugin` — cheap pre-filter that rules + /// Cheap pre-filter that rules /// out `./` / `../` / absolute paths before hitting the resolve hook. pub fn could_be_plugin(specifier: &[u8]) -> bool { if let Some(last_dot) = bun_core::last_index_of_char(specifier, b'.') { @@ -105,11 +105,10 @@ impl PluginRunner { } } -/// Spec `transpiler.zig:5` — `pub const MacroJSCtx = @import("../bundler_jsc/PluginRunner.zig").MacroJSCtx`. /// The canonical newtype lives in `bun_ast::Macro` (the lowest tier that -/// stores it, in `MacroContext.javascript_object`); re-exported here per spec. +/// stores it, in `MacroContext.javascript_object`); re-exported here. pub use js_ast::Macro::MacroJSCtx; -/// Spec `transpiler.zig:1433 default_macro_js_value` (= `JSValue.zero`). +/// The default (zero) macro JS context value. #[inline] pub const fn default_macro_js_value() -> MacroJSCtx { MacroJSCtx::ZERO @@ -123,7 +122,7 @@ pub const fn default_macro_js_value() -> MacroJSCtx { /// on every VM so that the options can be used for transpilation. pub struct Transpiler<'a> { pub options: options::BundleOptions<'a>, - // Raw ptr — Zig aliased the same `*Log` into `linker.log` and + // Raw ptr — the same `*Log` is aliased into `linker.log` and // `resolver.log` (see `set_log`). `&'a mut` would forbid that aliasing. pub log: *mut bun_ast::Log, // Bundler is an AST crate per PORTING.md, so an arena is threaded even @@ -131,8 +130,8 @@ pub struct Transpiler<'a> { pub arena: &'a Arena, pub result: options::TransformResult, pub resolver: Resolver<'a>, - // Raw ptr — Zig used the global `Fs.FileSystem.instance` singleton - // (`&'static mut`); a stored `&'a mut` would assert uniqueness it cannot have. + // Raw ptr — points at the shared global `FileSystem` singleton; a stored + // `&'a mut` would assert uniqueness it cannot have. pub fs: *mut Fs::FileSystem, pub output_files: Vec, pub resolve_results: Box, @@ -147,7 +146,7 @@ pub struct Transpiler<'a> { // by `configure_linker` below; `set_log` keeps `linker.log` in sync. pub linker: crate::linker::Linker, pub timer: SystemTimer, - // Raw ptr — Zig stored `&DotEnv.Loader` (the global singleton). + // Raw ptr — the global `DotEnv::Loader` singleton. pub env: *mut dot_env::Loader<'a>, pub macro_context: Option, @@ -156,21 +155,18 @@ pub struct Transpiler<'a> { impl<'a> Transpiler<'a> { pub const IS_CACHE_ENABLED: bool = false; - /// Port of `transpiler.zig:95 setLog`. - /// - /// takes `*mut Log` (not `&'a mut`) because Zig aliased the same - /// `*Log` into `linker.log` / `resolver.log`; the un-gated struct field is + /// Takes `*mut Log` (not `&'a mut`) because the same + /// `*Log` is aliased into `linker.log` / `resolver.log`; the un-gated struct field is /// already a raw pointer for that reason. pub fn set_log(&mut self, log: *mut bun_ast::Log) { self.log = log; self.linker.log = log; // SAFETY: caller (`ThreadPool::Worker::create`) passes the per-worker - // arena-allocated `Log`, which outlives this `Transpiler<'a>`. Zig - // aliased the same `*Log` into `resolver.log`. + // arena-allocated `Log`, which outlives this `Transpiler<'a>`. self.resolver.log = core::ptr::NonNull::new(log).expect("set_log: log is non-null"); } - /// Port of `transpiler.zig:102 setAllocator`. + /// Replace the transpiler's arena and re-init the macro context against it. // TODO: remove this method. it does not make sense pub fn set_arena(&mut self, arena: &'a Arena) { self.arena = arena; @@ -194,8 +190,8 @@ impl<'a> Transpiler<'a> { /// `&'static mut`. Owned `Transpiler`s from [`Self::for_worker`] must use /// normal `Drop` instead. pub unsafe fn deinit(&mut self) { - // The lazily-created `Box` was - // intentionally process-lifetime under Zig (`default_allocator`), but + // The lazily-created `Box` is + // process-lifetime by default, but // worker VMs run `destroy()` on thread exit and would otherwise strand // one box per worker. The box only owns a `MacroMap` and an optional // `bun_alloc::Arena` — no JSC handles — so freeing it from either @@ -287,7 +283,7 @@ impl<'a> Transpiler<'a> { /// Reborrow the `DotEnv::Loader`. Returned lifetime is decoupled from /// `&self` so call sites in `configure_defines` / `run_env_loader` can /// hold it across disjoint `&mut self.options` / `&mut self.resolver` - /// borrows (matching Zig's free `this.env.*` access). + /// borrows. #[inline] #[allow(clippy::mut_from_ref)] pub fn env_mut(&self) -> &'a mut dot_env::Loader<'a> { @@ -299,11 +295,9 @@ impl<'a> Transpiler<'a> { unsafe { &mut *self.env } } - /// Per-worker / client-transpiler constructor — port of Zig's - /// `transpiler.* = from.*` (ThreadPool.zig:308, bundle_v2.zig:204). + /// Per-worker / client-transpiler constructor. /// - /// Zig structs have no destructors, so a bitwise struct copy that aliases - /// heap-owning fields is sound there. In Rust the prior bitwise + /// The prior bitwise /// `ptr::copy_nonoverlapping` aliased every `Box`/`Vec` between parent and /// worker; reassigning any of them on the worker (e.g. /// `resolver.caches = ...`) ran `Drop` on the parent's allocation. This @@ -351,8 +345,7 @@ impl<'a> Transpiler<'a> { options, log, arena, - // Per-worker scratch — Zig's bitwise copy carried these too, but - // workers never read the parent's accumulated state. + // Per-worker scratch — workers never read the parent's accumulated state. result: options::TransformResult::default(), resolver, fs: from.fs, @@ -379,7 +372,7 @@ impl<'a> Transpiler<'a> { // SAFETY: lifetime-widen the `Loader<'from>` raw pointer to `'a` // (process-lifetime singleton; see fn doc). env: from.env.cast(), - // Spec ThreadPool.zig:311 `MacroContext.init(transpiler)` takes the + // `MacroContext::init(transpiler)` takes the // transpiler's *address*; deferred to `wire_after_move`. macro_context: None, } @@ -387,22 +380,19 @@ impl<'a> Transpiler<'a> { /// Wire the self-referential `linker` back-pointers and `macro_context` /// after this `Transpiler` has reached its final address (post-move into - /// `WorkerData` / arena slot). Port of the post-copy fixups in - /// ThreadPool.zig:309-313 / bundle_v2.zig:228-232. + /// `WorkerData` / arena slot). pub fn wire_after_move(&mut self) { - // Spec: `transpiler.setLog(log)` already ran inside `for_worker` via - // direct field init; re-thread into `options.log` / `resolver.log` / + // `self.log` was already set inside `for_worker` via direct field + // init; re-thread into `options.log` / `resolver.log` / // `linker.log` here so all four aliases agree. let log = self.log; self.options.log = log; self.resolver.log = core::ptr::NonNull::new(log).expect("wire_after_move: log is non-null"); self.resolver.fs = self.fs; - // Spec ThreadPool.zig:310 `transpiler.linker.resolver = &transpiler.resolver`. // Only reseat the back-pointers — do NOT `Linker::init` here: that // would clobber `import_counter` / `plugin_runner` / - // `tagged_resolutions` / `any_needs_runtime`, which the spec - // preserves across the move (bundle_v2.zig:230 only assigns - // `linker.resolver`). + // `tagged_resolutions` / `any_needs_runtime`, which must be + // preserved across the move. self.linker.reseat_self_refs( log, core::ptr::addr_of_mut!(self.resolve_queue), @@ -411,17 +401,17 @@ impl<'a> Transpiler<'a> { core::ptr::addr_of_mut!(*self.resolve_results), self.fs, ); - // Spec ThreadPool.zig:311 `transpiler.macro_context = MacroContext.init(transpiler)`. self.macro_context = Some(js_ast::Macro::MacroContext::init(self)); } - /// Port of `transpiler.zig:91 getPackageManager`. + /// Returns the resolver's auto-install package-manager handle. #[inline] pub fn get_package_manager(&mut self) -> *mut dyn bun_resolver::install_types::AutoInstaller { self.resolver.get_package_manager() } - /// Port of `transpiler.zig:358 resetStore`. + /// Reset the thread-local AST block stores (`Expr`/`Stmt`) and the side + /// `AstAlloc` arena. pub fn reset_store(&self) { bun_ast::Expr::data_store_reset(); bun_ast::Stmt::data_store_reset(); @@ -447,7 +437,6 @@ impl<'a> Transpiler<'a> { } } - /// Port of `transpiler.zig:108 _resolveEntryPoint`. fn _resolve_entry_point( &mut self, entry_point: &[u8], @@ -465,12 +454,10 @@ impl<'a> Transpiler<'a> { if !bun_paths::is_absolute(entry_point) && !(entry_point.starts_with(b"./") || entry_point.starts_with(b".\\")) { - // Spec: `strings.append(arena, "./", entry_point)`. let mut prefixed = Vec::with_capacity(2 + entry_point.len()); prefixed.extend_from_slice(b"./"); prefixed.extend_from_slice(entry_point); - // spec leaks the prefixed slice (arena-freed in - // Zig). `Resolver::resolve` interns the path internally, + // `Resolver::resolve` interns the path internally, // so the heap buffer can drop after the call. if let Ok(r) = self.resolver.resolve( top_level_dir, @@ -486,7 +473,8 @@ impl<'a> Transpiler<'a> { } } - /// Port of `transpiler.zig:130 resolveEntryPoint`. + /// Resolve an entry-point specifier, busting the directory cache and + /// retrying once on failure before reporting the error to the log. pub fn resolve_entry_point( &mut self, entry_point: &[u8], @@ -497,8 +485,8 @@ impl<'a> Transpiler<'a> { let mut cache_bust_buf = bun_paths::PathBuffer::uninit(); // Bust directory cache and try again - // reshaped for borrowck — Zig's labelled-block - // returned a slice that aliases either `entry_point` (via + // reshaped for borrowck — a single labelled block would + // return a slice that aliases either `entry_point` (via // `dirname`) or `cache_bust_buf`. Rust can't unify the two // disjoint mutable borrows of `cache_bust_buf` across `break`, // so compute `busted` directly instead. @@ -522,7 +510,7 @@ impl<'a> Transpiler<'a> { } } - // Spec: `bun.pathLiteral("..")` — `".."` is sep-agnostic. + // `".."` needs no platform separator rewrite. let parts: [&[u8]; 2] = [entry_point, b".."]; let top_level_dir = self.fs().top_level_dir; @@ -560,7 +548,8 @@ impl<'a> Transpiler<'a> { } } - /// Port of `transpiler.zig:314 configureDefines`. + /// Load env files and build `options.define`. Idempotent — a no-op once + /// `options.defines_loaded` is set. pub fn configure_defines(&mut self) -> Result<(), bun_core::Error> { if self.options.defines_loaded { return Ok(()); @@ -576,10 +565,8 @@ impl<'a> Transpiler<'a> { let env_loader = self.env_mut(); let mut is_production = env_loader.is_production(); - // spec (`transpiler.zig:314`) eagerly did - // `Expr.Data.Store.create()` / `Stmt.Data.Store.create()` plus a - // `defer Store.reset()` here, purely so `defines.zig`'s `parse_env_json` - // had a thread-local AST store to build `E::String` nodes in. That work + // `parse_env_json` needs a thread-local AST store to build + // `E::String` nodes in. That work // is now done lazily inside `DefineData::parse`, only on the JSON-parse // slow path — the common case (`bun run` with no user `--define`) // resolves every define through the literal fast path and never @@ -617,9 +604,7 @@ impl<'a> Transpiler<'a> { Ok(()) } - /// Port of the spec idiom `out.resolver.opts = out.options` (transpiler.zig - /// passes the same `BundleOptions` value to both struct fields; bake.zig:788 - /// re-assigns after mutating `out.options`). In the Rust port the resolver + /// The resolver /// crate carries a FORWARD_DECL subset of `BundleOptions`, so re-project /// rather than `Clone`. Called after `init_transpiler_with_options` mutates /// `self.options` so the resolver sees the same conditions/target/public_path. @@ -627,17 +612,17 @@ impl<'a> Transpiler<'a> { self.resolver.opts = resolver_bundle_options_subset(&self.options); } - /// Port of `transpiler.zig:363 dumpEnvironmentVariables`. + /// Print the loaded environment variables to stdout as 2-space-indented + /// JSON. #[cold] #[inline(never)] pub fn dump_environment_variables(&self) { use bun_js_printer::{Encoding, write_json_string}; - // spec uses `std.json.Stringify` (`.whitespace = .indent_2`) - // to dump `env.map.*`. The Rust `bun_dotenv::Map` doesn't impl - // `serde::Serialize`, so iterate and emit the object by hand. Keys and - // values go through `write_json_string` (the same escaper the printer - // uses for metafile/HTML-manifest JSON) so `"` / `\` / control bytes - // are escaped exactly as `std.json.Stringify` does. + // Dump `env.map.*` as 2-space-indented JSON. `bun_dotenv::Map` doesn't + // impl `serde::Serialize`, so iterate and emit the object by hand. + // Keys and values go through `write_json_string` (the same escaper the + // printer uses for metafile/HTML-manifest JSON) so `"` / `\` / control + // bytes are escaped as standard JSON requires. bun_core::Output::flush(); let env = self.env_mut(); let w = bun_core::Output::writer(); @@ -686,11 +671,12 @@ fn merge_tsconfig_jsx_into(tsconfig: &TSConfigJSON, out: &mut crate::options_imp } impl<'a> Transpiler<'a> { - /// Port of `transpiler.zig:233 configureLinkerWithAutoJSX`. + /// Initialize `self.linker` with back-pointers into this `Transpiler`, + /// optionally auto-configuring JSX from the nearest `tsconfig.json`. pub fn configure_linker_with_auto_jsx(&mut self, auto_jsx: bool) { // `Linker::init` dropped its `arena` arg (linker.rs:172 - // — global mimalloc). Zig stored borrowed `*T` into the linker; the - // un-gated `crate::linker::Linker` mirrors that with raw pointers so + // — global mimalloc). The + // un-gated `crate::linker::Linker` stores raw pointers so // `&mut self.options` etc. coerce directly. Self-reference is // load-bearing — `linker.link()` reads back through these into the // owning `Transpiler` — hence raw `*mut`, not `&'a mut` (would alias @@ -698,7 +684,7 @@ impl<'a> Transpiler<'a> { // `.cast()` on the `options`/`resolver` pointers erases the // `<'a>` lifetime parameter — `Linker` stores them as // `*mut BundleOptions` / `*mut Resolver` with an (implicit) distinct - // lifetime. Raw-pointer storage is the Zig contract; the linker never + // lifetime. The linker never // outlives its owning `Transpiler<'a>`. self.linker = crate::linker::Linker::init( self.log, @@ -725,17 +711,18 @@ impl<'a> Transpiler<'a> { } } - /// Port of `transpiler.zig:259 configureLinker`. + /// [`Self::configure_linker_with_auto_jsx`] with `auto_jsx = true`. #[inline] pub fn configure_linker(&mut self) { self.configure_linker_with_auto_jsx(true); } - /// Port of `transpiler.zig:263 runEnvLoader`. + /// Load `.env` files into the env loader according to + /// `options.env.behavior`. pub fn run_env_loader(&mut self, skip_default_env: bool) -> Result<(), bun_core::Error> { use bun_options_types::schema::api::DotEnvBehavior; // Derived once up front; no other live `&mut` to this `Loader` exists - // for the duration of this call (Zig accessed `this.env.*` freely). + // for the duration of this call. let env: &mut dot_env::Loader<'_> = self.env_mut(); match self.options.env.behavior { @@ -751,7 +738,6 @@ impl<'a> Transpiler<'a> { let has_production_env = env.is_production(); if !was_production && has_production_env { self.options.set_production(true); - // Spec transpiler.zig:275 `this.resolver.opts.setProduction(true)`. // The resolver's FORWARD_DECL `BundleOptions` now exposes // `set_production` (flips `production` + `jsx.development` // and self-guards on `force_node_env`; resolver/lib.rs). @@ -789,7 +775,6 @@ impl<'a> Transpiler<'a> { // `Env.files: Box<[Box<[u8]>]>` but `Loader::load` // wants `&[&[u8]]`. Re-borrow into a small Vec; the explicit // `--env-file` list is bounded (CLI args), not hot-path. - // PERF(port): one tiny alloc — Zig passed the slice directly. let env_files: Vec<&[u8]> = self.options.env.files.iter().map(|f| &**f).collect(); let suffix = if self.options.is_test() || env.is_test() { @@ -805,7 +790,7 @@ impl<'a> Transpiler<'a> { env.load_process()?; if env.is_production() { self.options.set_production(true); - // Spec transpiler.zig:302 — see note in the `.prefix` arm. + // See note in the `.prefix` arm. self.resolver.opts.set_production(true); } } @@ -835,7 +820,8 @@ use bun_core::strings; use bun_resolver::package_json::MacroMap as MacroRemap; use bun_sys::Fd as FD; -/// Port of `transpiler.zig:ParseResult.AlreadyBundled` (tagged union). +/// How a parsed source was found to be pre-bundled (plain source vs. cached +/// bytecode, ESM vs. CJS). #[derive(Default)] pub enum AlreadyBundled { #[default] @@ -869,9 +855,10 @@ impl AlreadyBundled { } } -/// Port of `transpiler.zig:ParseResult`. -// lifetime-free — `runtime_transpiler_cache` is a raw pointer (Zig -// `?*RuntimeTranspilerCache`) so `AsyncModule.parse_result` / `JSTranspiler` +/// Output of the transpiler's parse step: the parsed AST plus its source and +/// loader metadata. +// lifetime-free — `runtime_transpiler_cache` is a raw pointer +// so `AsyncModule.parse_result` / `JSTranspiler` // can store this by value without threading a borrow lifetime. pub struct ParseResult<'a> { pub source: bun_ast::Source, @@ -880,8 +867,7 @@ pub struct ParseResult<'a> { pub already_bundled: AlreadyBundled, pub input_fd: Option, pub empty: bool, - // Zig `_resolver.PendingResolution.List` is - // `MultiArrayList(PendingResolution)`. `PendingResolution` does not yet + // `PendingResolution` does not yet // derive `MultiArrayElement` (lives in `bun_resolver`, derive macro is in // `bun_collections_macros` — orphan rules forbid impl-ing it here), so the // SoA `len()`/column accessors aren't reachable. Use AoS `Vec` for now; @@ -891,7 +877,7 @@ pub struct ParseResult<'a> { // this can switch back to `MultiArrayList`. pub pending_imports: Vec, - /// Zig: `?*bun.RuntimeTranspilerCache`. SAFETY: erased — bundler stores it + /// SAFETY: erased — bundler stores it /// and hands it back to the runtime side; never dereferenced here. pub runtime_transpiler_cache: Option>, @@ -907,15 +893,15 @@ pub struct ParseResult<'a> { } impl<'a> ParseResult<'a> { - /// Spec transpiler.zig — `ParseResult` is value-copied (e.g. + /// `ParseResult` is value-copied (e.g. /// `AsyncModule.resumeLoadingModule` reads/writes `this.parse_result` by /// value). `Default` lets the Rust port `mem::take` it across that /// boundary; see `AsyncModule::resume_loading_module`. pub fn empty(arena: &'a bun_alloc::Arena) -> Self { ParseResult { source: Default::default(), - // `options::Loader` has no `Default`; Zig field had no - // initializer either. `File` is the resolver's neutral fallback + // `options::Loader` has no `Default`. + // `File` is the resolver's neutral fallback // (BundleEnums.rs:353), and `Default` here exists only for // `mem::take` in `AsyncModule::resume_loading_module`. loader: options::Loader::File, @@ -953,7 +939,6 @@ impl<'a> ParseResult<'a> { } pub fn is_pending_import(&self, id: u32) -> bool { - // Spec transpiler.zig:43-47: scan `pending_imports.items(.import_record_id)` for `id`. // AoS scan (see field comment); SoA column iteration restored // when `PendingResolution: MultiArrayElement` lands. self.pending_imports @@ -962,7 +947,7 @@ impl<'a> ParseResult<'a> { } } -/// Port of `transpiler.zig:Transpiler.ParseOptions`. +/// Per-file inputs to the transpiler's parse step. pub struct ParseOptions<'a, 'b> { pub arena: &'a Arena, pub dirname_fd: FD, @@ -980,7 +965,6 @@ pub struct ParseOptions<'a, 'b> { pub macro_remappings: MacroRemap, pub macro_js_ctx: MacroJSCtx, pub virtual_source: Option<&'b bun_ast::Source>, - /// Zig: `runtime.Runtime.Features.ReplaceableExport.Map`. pub replace_exports: bun_collections::StringArrayHashMap, pub inject_jest_globals: bool, pub set_breakpoint_on_first_line: bool, @@ -1017,7 +1001,7 @@ use bun_options_types::schema::api; // `bun_options_types::jsx::Pragma`). Only the `_None → Automatic` fold is // applied so parser-side `== Automatic` checks in visitExpr/parseJSXElement // keep their pre-unification semantics (parser only ever sees a resolved -// runtime; options.zig:1199 default). +// runtime). #[inline] pub(crate) fn to_parser_jsx_pragma( mut p: crate::options_impl::jsx::Pragma, @@ -1039,20 +1023,17 @@ fn to_parser_module_type( m } -/// Spec: `fs.zig:FileSystem.init`. -/// -/// the inline `bun_resolver::fs` module exposes the `FileSystem` +/// The inline `bun_resolver::fs` module exposes the `FileSystem` /// struct + `INSTANCE`/`INSTANCE_LOADED` statics (resolver/lib.rs:120,129) but /// not the `init` constructor (that lives in the still-gated file-backed /// `resolver/fs.rs`). All fields are `pub` and `EntriesMap`/`Mutex` have -/// public constructors, so reproduce the singleton-init here. Matches Zig -/// semantics: first call sets `top_level_dir` (defaulting to getcwd), +/// public constructors, so reproduce the singleton-init here: +/// first call sets `top_level_dir` (defaulting to getcwd), /// subsequent calls return the existing instance untouched. fn init_file_system( top_level_dir: Option<&'static [u8]>, ) -> Result<*mut Fs::FileSystem, bun_core::Error> { - // Spec fs.zig:90-108 — delegate to `FileSystem.init`, which routes through - // `Implementation.init` (fs.zig:823-837): that path calls `adjustUlimit()` + // `FileSystem` initialization calls `adjustUlimit()` // to raise RLIMIT_NOFILE and stores the returned limit in // `file_limit`/`file_quota`, and touches the `DirEntry.EntryStore` // singleton. The previous hand-built `Implementation { file_limit: 0, .. }` @@ -1068,8 +1049,7 @@ fn init_file_system( /// The two are nominally distinct until MOVE_DOWN to `bun_options_types` /// unifies them (resolver/lib.rs `mod options` note). /// -/// Spec transpiler.zig:214 passes the SAME `bundle_options` value to -/// `Resolver.init1`, so `resolver.opts` must carry user-configured +/// `resolver.opts` must carry the same user-configured /// `--external`, `--conditions`, `--main-fields`, and the extension order. /// Every field the resolver reads is now projected (clone of owned data, no /// `Box::leak`); the resolver-side FORWARD_DECL types were widened to owned @@ -1078,8 +1058,7 @@ fn init_file_system( /// /// This projection can be dropped once `bun_options_types::BundleOptions` exists and both /// crates re-export it — `Resolver::init1` will then take the canonical type -/// directly and Zig's `bundle_options` value can flow through unchanged -/// (transpiler.zig:209 passes the same `options` to both struct fields). +/// directly. /// /// `#[cold]`/`#[inline(never)]`: this is a ~100-line struct-construction blob /// run exactly once per `Transpiler::init` (i.e. once per VM bring-up). Keeping @@ -1146,15 +1125,13 @@ pub(crate) fn resolver_bundle_options_subset( } }), global_cache: src.global_cache, - // Spec `options.zig:1753`: `?*const Api.BunInstall` — both sides store + // Both sides store // `Option>`, so this is a straight copy. install: src.install, load_package_json: src.load_package_json, load_tsconfig_json: src.load_tsconfig_json, main_field_extension_order: ropts::owned_string_list(src.main_field_extension_order), - // Spec resolver.zig `auto_main` compares the pointer of - // `opts.main_fields` against the per-target default; with owned - // storage that pointer test can't hold, so project the predicate as a + // `auto_main` is projected as a // bool: it's "default" iff the user did not pass `--main-fields` // (`from_api` overwrites `main_fields` only when // `transform.main_fields` is non-empty — options.rs:2231). @@ -1169,9 +1146,8 @@ pub(crate) fn resolver_bundle_options_subset( production: src.production, force_node_env: src.force_node_env, // FORWARD_DECL: bundler-only fields read via `c.resolver.opts` in - // `linker_context/*` (Zig stores the full `BundleOptions` on the - // resolver). Project them so the linker sees the same values it would - // have read off the spec's shared struct. + // `linker_context/*`. Project them so the linker sees the same values + // the bundler configured. output_dir: src.output_dir.clone(), root_dir: src.root_dir.clone(), public_path: src.public_path.clone(), @@ -1183,15 +1159,13 @@ pub(crate) fn resolver_bundle_options_subset( } impl<'a> Transpiler<'a> { - /// Port of `transpiler.zig:Transpiler.init`. - /// - /// Called by [`init_runtime_state`](../runtime/jsc_hooks.rs) (spec - /// `VirtualMachine.zig:1241`) to write `vm.transpiler`. Builds on: + /// Called by [`init_runtime_state`](../runtime/jsc_hooks.rs) + /// to write `vm.transpiler`. Builds on: /// * [`options::BundleOptions::from_api`] — `bun_bundler::options` /// * [`Resolver::init1`] — `bun_resolver` /// /// `log` / `env_loader_` are raw pointers (not `&'a mut`) to - /// match the un-gated struct field types — Zig aliased the same `*Log` + /// match the un-gated struct field types — the same `*Log` is aliased /// into `linker.log` / `resolver.log` (see `set_log`). pub fn init( arena: &'a Arena, @@ -1249,8 +1223,7 @@ impl<'a> Transpiler<'a> { // `reset()` branches to `enter()` on null ARENA), so per-file ASTs // *do* get the side arena from the first parsed file onward. - // `FileSystem::init` wants `&'static [u8]`; Zig passed a - // borrowed slice (transpiler.zig:179). Intern via `DirnameStore` + // `FileSystem::init` wants `&'static [u8]`. Intern via `DirnameStore` // (the same path `FileSystem::init` already uses for the // `None`/getcwd case — fs.rs:222) so the cwd lives in the // process-lifetime BSS string store without `Box::leak`. PORTING.md @@ -1305,7 +1278,7 @@ impl<'a> Transpiler<'a> { // `log` stays raw — `from_api` stores it in `BundleOptions.log: *mut` // and the same pointer is aliased into `Resolver::init1` / `Linker` - // / the struct field below (Zig aliased `*Log` everywhere). No `&'a + // / the struct field below. No `&'a // mut Log` is materialized here, so the sibling raw pointers don't // invalidate a long-lived unique borrow under stacked borrows. // SAFETY: `fs` is the process-lifetime `Fs::FileSystem` singleton from @@ -1326,7 +1299,7 @@ impl<'a> Transpiler<'a> { // Construct directly into the caller-owned storage instead of building a // stack temporary and returning it. All fallible work is done; every // field below is written exactly once. `Linker::init` gets null - // back-pointers (Zig used `undefined`) — `core::mem::zeroed()` is NOT a + // back-pointers — `core::mem::zeroed()` is NOT a // valid analogue (`Linker.hashed_filenames: HashMap` carries a `NonNull` // niche, so all-zeroes is instant UB); the value fields get their proper // defaults and `configure_linker_with_auto_jsx` overwrites the @@ -1400,9 +1373,8 @@ impl<'a> Transpiler<'a> { >( &mut self, mut this_parse: ParseOptions<'a, '_>, - // Zig took `anytype` + `@hasField(.., "source")`; the only caller - // type is `?*EntryPoints.ClientEntryPoint`, so the param is typed - // concretely. + // The only caller passes `EntryPoints::ClientEntryPoint`, so the + // param is typed concretely. client_entry_point_: Option<&mut EntryPoints::ClientEntryPoint>, ) -> Option> { let arena = this_parse.arena; @@ -1426,15 +1398,12 @@ impl<'a> Transpiler<'a> { // (`Drop` is a no-op). let mut source_backing: resolver::cache::Contents = resolver::cache::Contents::Empty; - // Zig `&brk: { ... }` took the address of a temporary; Rust - // owns the value and borrows it after the block. let source: &'a bun_ast::Source = arena.alloc('brk: { if let Some(virtual_source) = this_parse.virtual_source { break 'brk virtual_source.clone(); } if let Some(client_entry_point) = client_entry_point_ { - // Zig: if (@hasField(Child, "source")) — ClientEntryPoint always has it. break 'brk client_entry_point.source.clone(); } @@ -1446,7 +1415,7 @@ impl<'a> Transpiler<'a> { break 'brk bun_ast::Source::init_path_string(path.text, b""); } - // Spec transpiler.zig:826-835. The decoded body is owned in + // The decoded body is owned in // `source_backing` (below) so `source.contents` re-borrows it // without leaking; never falls through to `read_file_with_allocator` // (which would try to open `data:...` as a filesystem path). @@ -1493,8 +1462,7 @@ impl<'a> Transpiler<'a> { break 'brk bun_ast::Source::init_path_string(path.text, contents); } - // Zig (`transpiler.zig:838-839`): `if (use_shared_buffer) - // bun.default_allocator else this_parse.allocator`. Thread + // Thread // `this_parse.arena` (the per-call `MimallocArena` from // `RuntimeTranspilerStore`) so the source bytes land in the // job-scoped heap that `TranspilerJob::run` `mi_heap_destroy`s on @@ -1533,9 +1501,7 @@ impl<'a> Transpiler<'a> { // recycled). Thread the // provenance-tagged backing alongside the `ParseResult` so it // drops when the result is recycled — no `mem::forget` - // (PORTING.md §Forbidden patterns). Spec transpiler.zig:853 hands - // `entry.contents` to `Source.initRecycledFile` by slice; Zig has - // no implicit drop, so ownership was already with the caller. + // (PORTING.md §Forbidden patterns). source_backing = core::mem::take(&mut entry.contents); // SAFETY: `source_backing` outlives every read through // `source.contents` (it is moved into the returned `ParseResult`, @@ -1650,8 +1616,7 @@ impl<'a> Transpiler<'a> { opts.features.no_macros = self.options.no_macros; // `bun_ast::RuntimeTranspilerCache` is the single nominal // type on both sides; thread the pointer directly. - // Spec transpiler.zig:899/957 copies the same - // `?*RuntimeTranspilerCache` raw pointer to BOTH + // The same `RuntimeTranspilerCache` raw pointer goes to BOTH // `opts.features` and the returned `ParseResult`. Derive both // from a single reborrow so they share one provenance tag — // re-touching the parent `&mut` after the `*mut` cast would @@ -1678,8 +1643,6 @@ impl<'a> Transpiler<'a> { opts.features.minify_identifiers = self.options.minify_identifiers; opts.features.dead_code_elimination = self.options.dead_code_elimination; opts.features.remove_cjs_module_wrapper = this_parse.remove_cjs_module_wrapper; - // Spec transpiler.zig:925 forwards `transpiler.options - // .bundler_feature_flags`. Zig aliased a `*const StringSet`; // `Features.bundler_feature_flags` is currently owned // (`Option>`), so clone by value. // (Changing the parser-side field to `Option<&'a StringSet>` @@ -1697,8 +1660,6 @@ impl<'a> Transpiler<'a> { opts.features.top_level_await = true; opts.features.is_macro_runtime = target == crate::options_impl::Target::BunMacro; - // Spec transpiler.zig:943: `opts.features.replace_exports = - // this_parse.replace_exports`. // `bun_ast::runtime::ReplaceableExport` IS // `js_ast::Runtime::ReplaceableExport`, so the inner // `StringArrayHashMap` moves directly into the newtype. @@ -1710,7 +1671,7 @@ impl<'a> Transpiler<'a> { let ctx = js_ast::Macro::MacroContext::init(self); self.macro_context = Some(ctx); } - // Spec transpiler.zig:938-940: thread the caller-supplied JS + // Thread the caller-supplied JS // context into the macro runtime so macros invoked during // runtime transpilation see it (instead of null). Written on // `self.macro_context` before reborrowing into `opts` so the @@ -1728,8 +1689,7 @@ impl<'a> Transpiler<'a> { // owned by the long-lived `Transpiler`; the parser borrows // them for `'a` (arena lifetime). Erase to `'a` so the // returned `Ast<'a>` is not pinned to the `&mut self` borrow - // — neither field is dropped while a parse is in flight - // (Zig held `*const Define` / `*MacroContext`). + // — neither field is dropped while a parse is in flight. unsafe { let define_ptr: *const js_ast::defines::Define = &raw const *self.options.define; @@ -1782,7 +1742,7 @@ impl<'a> Transpiler<'a> { js_ast::AlreadyBundled::BunCjs => AlreadyBundled::SourceCodeCjs, js_ast::AlreadyBundled::BytecodeCjs | js_ast::AlreadyBundled::Bytecode => 'brk: { - // Spec transpiler.zig:971-984: when the parser + // When the parser // saw `// @bun @bytecode`, attempt to load the // sidecar `.jsc` cached bytecode. Only // fall back to re-parsing source on read @@ -1797,8 +1757,7 @@ impl<'a> Transpiler<'a> { if this_parse.virtual_source.is_none() && this_parse.allow_bytecode_cache { - // `bun.bytecode_extension` - // (bun.zig:3502) — no Rust const re-export + // No shared const for the bytecode extension // in `bun_core` yet, so inline the literal. const BYTECODE_EXT: &[u8] = b".jsc"; let mut path_buf2 = bun_paths::PathBuffer::uninit(); @@ -1914,10 +1873,6 @@ fn parse_data_loader<'a>( log: &mut bun_ast::Log, keep_json_and_toml_as_one_statement: bool, ) -> Option> { - // PERF(port): was `inline .toml, .yaml, .json, .jsonc, .json5 - // => |kind|` — comptime monomorphization per loader; profile if it - // shows up on a hot path. - // // `bun_parsers::*` parse into the T2 value AST // (`bun_ast::Expr`); lift into the full T4 // `bun_ast::Expr` via the deep-convert `From` bridge @@ -1956,8 +1911,7 @@ fn parse_data_loader<'a>( let mut symbols: Vec = Vec::new(); - // reshaped — Zig `arena.alloc(Part, 1)` returned - // an arena slice, but `Ast::from_parts` takes `Box<[Part]>` + // `Ast::from_parts` takes `Box<[Part]>` // (Vec owns its buffer). The single-part array is built on // the global heap; `stmts` stays arena-backed (`*mut [Stmt]`). let parts: Box<[bun_ast::Part]> = 'parts: { @@ -1970,7 +1924,6 @@ fn parse_data_loader<'a>( }, bun_ast::Loc { start: 0 }, ); - // PERF(port): was `arena.alloc(Stmt, 1) catch unreachable`. let stmts = bun_ast::StoreSlice::new_mut(arena.alloc_slice_copy(&[stmt])); break 'parts Box::new([bun_ast::Part { stmts, @@ -1982,15 +1935,13 @@ fn parse_data_loader<'a>( let properties: &mut [bun_ast::G::Property] = obj.properties.slice_mut(); if !properties.is_empty() { let n = properties.len(); - // Zig `expandToCapacity()` / `arena.alloc(Symbol, n)` - // leave slots uninitialized, which is inert in Zig. // The loop below writes sparsely at index `i` and // `continue`s on `"default"` / duplicate keys, so // some slots are never assigned. In Rust an uninit // live `Vec` element is UB the moment it is // observed (truncate/into_boxed_slice/index-assign), // so pre-fill every slot with `Default` instead of - // `set_len`. PERF(port): was `expandToCapacity()`. + // `set_len`. let mut decls: Vec = vec![bun_ast::G::Decl::default(); n]; symbols.resize_with(n, Default::default); @@ -2008,7 +1959,7 @@ fn parse_data_loader<'a>( for i in 0..n { let prop = &mut properties[i]; // SAFETY: data-format parsers always emit - // `e_string` keys (Zig `.?.data.e_string`). + // `e_string` keys. let key = prop.key.as_mut().unwrap(); let key_loc = key.loc; let name: &[u8] = key @@ -2030,10 +1981,7 @@ fn parse_data_loader<'a>( Some(prop.value.expect("infallible: prop has value")); continue; } - // spec transpiler.zig:1030-1071 - // writes at `i` and shrinks to `count`, leaving - // holes when `"default"` / duplicates `continue` - // — a latent spec bug. Write densely at `count` + // Write densely at `count` // (and store `count` in the checker) so // `truncate(count)` / `[..count]` keep the // actually-populated entries. @@ -2042,9 +1990,7 @@ fn parse_data_loader<'a>( symbols[count] = bun_ast::Symbol { original_name: match bun_core::MutableString::ensure_valid_identifier(name) { - // Spec transpiler.zig:1049 calls - // `MutableString.ensureValidIdentifier(name, arena)` - // — the identifier lives in the + // The identifier lives in the // per-parse arena. Arena-copy the // owned `Box<[u8]>` so it is freed // with the arena instead of leaking @@ -2175,7 +2121,6 @@ fn parse_text_loader<'a>( }, bun_ast::Loc { start: 0 }, ); - // PERF(port): was `arena.alloc(Stmt, 1) catch unreachable`. let stmts = bun_ast::StoreSlice::new_mut(arena.alloc_slice_copy(&[stmt])); let parts: Box<[bun_ast::Part]> = Box::new([bun_ast::Part { stmts, @@ -2206,7 +2151,7 @@ fn parse_md_loader<'a>( log: &mut bun_ast::Log, ) -> Option> { let html: &'static [u8] = match bun_md::root::render_to_html(&source.contents) { - // Spec transpiler.zig:1162 allocates the rendered HTML via + // The rendered HTML is allocated via // `arena` (the per-parse arena), so it is freed with the // arena. Arena-copy the heap `Box<[u8]>` and let it drop; // PORTING.md §Forbidden patterns bars `Box::leak` here. @@ -2296,7 +2241,7 @@ fn parse_wasm_loader<'a>( #[cold] #[inline(never)] fn parse_unsupported_loader(loader: options::Loader, path: &bun_paths::fs::Path<'static>) -> ! { - // Spec transpiler.zig:1216 — programmer-error hard crash, NOT a + // Programmer-error hard crash, NOT a // silent `None` (PORTING.md §Forbidden: silent no-op). bun_core::Output::panic(format_args!( "Unsupported loader {:?} for path: {}", @@ -2310,12 +2255,12 @@ fn parse_unsupported_loader(loader: options::Loader, path: &bun_paths::fs::Path< // `ModuleLoader::transpile_source_code` (jsc_hooks.rs spec :525-539); the // dispatch shim that `RuntimeTranspilerStore` / `AsyncModule` link against. // -// `comptime format: js_printer.Format` demoted to a runtime arg — +// `format` is a runtime arg rather than a const generic — // `bun_js_printer::Format` doesn't derive `ConstParamTy` (and can't be added // from this crate). All callers pass a literal anyway; the inner -// `print_ast::<_, ASCII_ONLY, ENABLE_SOURCE_MAP>` keeps both real comptime +// `print_ast::<_, ASCII_ONLY, ENABLE_SOURCE_MAP>` keeps both const-generic // bools, so codegen monomorphizes the printer body identically. -// PERF(port): outer `match format` is one extra branch — profile if hot. +// PERF: outer `match format` is one extra branch — profile if hot. // ══════════════════════════════════════════════════════════════════════════ use bun_js_printer as js_printer; @@ -2323,7 +2268,7 @@ use bun_js_printer as js_printer; // (the producer), not `crate::analyze_transpiled_module::ModuleInfo` (the // richer consumer-side mirror). The print // path only ever fills the printer-owned one and hands its serialized bytes to -// T6, so unify on the printer type here. Spec: transpiler.zig:663. +// T6, so unify on the printer type here. use js_printer::analyze_transpiled_module; /// Map the bundler-local `Target` (options.rs:489) to the lower-tier @@ -2352,8 +2297,8 @@ pub use js_printer::Format as PrintFormat; // `&mut BufferPrinter`. Leaving the public entry points generic forced each // downstream crate (bun_runtime / bun_jsc / bun_install / bun_bundler) to stamp // out its own copy of the 109-fn `Printer` recursion tree — -// `llvm-nm --print-size` showed `bun_js_printer` .text at 1,367 KB vs 594 KB on -// the Zig build, with both the `_11bun_runtime` and `_7bun_jsc` copies of +// `llvm-nm --print-size` showed `bun_js_printer` .text at 1,367 KB, +// with both the `_11bun_runtime` and `_7bun_jsc` copies of // `print_expr<…>` live in `perf` and thrashing icache against each other // (L1-icache-misses +5.1%, iTLB-misses +13.2%, IPC 1.40 vs 1.50). Pinning `W` // to the one concrete type and marking the public entry points @@ -2370,8 +2315,7 @@ impl<'a> Transpiler<'a> { runtime_transpiler_cache: Option>, module_info: Option<*mut analyze_transpiled_module::ModuleInfo>, ) -> Result { - // Zig: `bun.perf.trace("JSPrinter.printWithSourceMap")` / - // `("JSPrinter.print")`. Routed through the T0 ftrace subset like the + // Routed through the T0 ftrace subset like the // other bundler spans (`Bundler.computeChunks` etc.) — // `bun_perf::PerfEvent` has no JSPrinter variants yet. let _trace = bun_core::perf::trace(if ENABLE_SOURCE_MAP { @@ -2380,9 +2324,7 @@ impl<'a> Transpiler<'a> { "JSPrinter.print" }); - // Zig built `Symbol.NestedList.fromBorrowedSliceDangerous( - // &.{ast.symbols})` — aliased the stack-one-slice into the map. Rust - // can't borrow `ast.symbols` while moving `ast` into `print_ast`, so + // We can't borrow `ast.symbols` while moving `ast` into `print_ast`, so // take the column out (the printer never reads `tree.symbols`; it // walks `symbols` exclusively — `rg tree.symbols js_printer/lib.rs` is // empty). `init_with_one_list` boxes the single inner list. @@ -2396,16 +2338,16 @@ impl<'a> Transpiler<'a> { // `runtime_imports` is now forwarded — after Round-G `Ast.runtime_imports` // is the real `parser::Runtime::Imports`, the same type // `js_printer::Options.runtime_imports` takes (via `js_ast::runtime`), - // so the seam is gone. Spec: zig:593/619/645. + // so the seam is gone. // `target` is now forwarded via `to_bundle_enums_target` below — it // *does* affect the EsmAscii/bun-runtime path (js_printer/lib.rs:6872 // gates the `var {require}=import.meta;` hoist on `target == Bun`; // regression of oven-sh/bun#15738 if left at the `Browser` default). // `runtime_transpiler_cache` is now forwarded — js_printer holds the - // `NonNull` directly. Spec: zig:601/627/662. + // `NonNull` directly. // `module_info` is now forwarded — this fn's parameter is the // printer-crate `analyze_transpiled_module::ModuleInfo` (see the `use` - // above), so the seam is gone. Spec: zig:663 — EsmAscii arm only. + // above), so the seam is gone (EsmAscii arm only). let exports_kind = ast.exports_kind; @@ -2446,10 +2388,9 @@ impl<'a> Transpiler<'a> { ), js_printer::Format::EsmAscii => { - // `switch (target.isBun()) { inline else => |is_bun| ... }` - // — runtime bool → comptime dispatch. Hoisted into the - // `print_ast_esm_ascii` helper so the const-generic IS_BUN can - // also drive `module_type`. + // Runtime `target.is_bun()` bool → const-generic dispatch, + // hoisted into the `print_ast_esm_ascii` helper so the + // const-generic IS_BUN can also drive `module_type`. if self.options.target.is_bun() { self.print_ast_esm_ascii::( print_arena, @@ -2477,7 +2418,6 @@ impl<'a> Transpiler<'a> { } } - // Spec transpiler.zig:672 `else => unreachable`. js_printer::Format::CjsAscii => unreachable!(), } } @@ -2603,9 +2543,8 @@ impl<'a> Transpiler<'a> { ) } - // hoisted from `inline else => |is_bun|` arm of - // print_with_source_map_maybe to express the comptime bool dispatch as a - // const generic. + // hoisted out of print_with_source_map_maybe to express the + // is-bun bool dispatch as a const generic. #[allow(clippy::too_many_arguments)] fn print_ast_esm_ascii( &mut self, @@ -2619,7 +2558,7 @@ impl<'a> Transpiler<'a> { runtime_transpiler_cache: Option, module_info: Option<*mut analyze_transpiled_module::ModuleInfo>, ) -> Result { - // Spec transpiler.zig:662-663 — both set on this (EsmAscii) arm only. + // Both set on this (EsmAscii) arm only. // SAFETY: `module_info` is `ModuleInfo::create`'s `heap::alloc` (or // null); it is exclusively owned by this print call until T6 reclaims // it after `print_with_source_map` returns. @@ -2650,7 +2589,7 @@ impl<'a> Transpiler<'a> { module_info, hmr_ref: ast.wrapper_ref, mangled_props: None, - // Spec transpiler.zig:664. The printer reads `opts.target` at + // The printer reads `opts.target` at // js_printer/lib.rs:6872 to gate the `var {require}=import.meta;` // hoist on `Target::Bun` — defaulting to `Browser` here regressed // oven-sh/bun#15738. @@ -2714,7 +2653,7 @@ impl<'a> Transpiler<'a> { module_info: Option<*mut analyze_transpiled_module::ModuleInfo>, ) -> Result { // env_var feature_flag getters return `Option` - // (Some(default) when unset); Zig's `.get()` is plain `bool`. + // (Some(default) when unset). if bun_core::env_var::feature_flag::BUN_FEATURE_FLAG_DISABLE_SOURCE_MAPS .get() .unwrap_or(false) @@ -2774,14 +2713,12 @@ impl<'a> Transpiler<'a> { ) } - /// Port of `transpiler.zig:1225 normalizeEntryPointPath`. fn normalize_entry_point_path(&self, _entry: &[u8]) -> &'static [u8] { let fs = self.fs(); let entry = fs.abs(&[_entry]); - // Spec: `std.fs.accessAbsolute(entry, .{}) catch return _entry` — if the - // absolutized path does not exist on disk, return the original input - // unchanged so bare specifiers (`react`) and URLs are left alone. + // If the absolutized path does not exist on disk, return the original + // input unchanged so bare specifiers (`react`) and URLs are left alone. if !bun_sys::exists(entry) { return crate::linker::dupe(_entry); } @@ -2809,11 +2746,7 @@ impl<'a> Transpiler<'a> { crate::linker::dupe(entry) } - /// Port of `transpiler.zig:1254 enqueueEntryPoints`. - /// - /// the Zig version writes the resolved entry results into a - /// caller-provided `[]Result` slice; the only caller (`transform`) discards - /// that slice immediately, so the Rust port returns only the count and lets + /// Returns only the count and lets /// `linker.enqueue_resolve_result` push directly onto `resolve_queue`. fn enqueue_entry_points(&mut self) -> usize { let mut entry_point_i: usize = 0; @@ -2868,7 +2801,6 @@ impl<'a> Transpiler<'a> { entry_point_i } - /// Port of `transpiler.zig:1286 transform`. pub fn transform( &mut self, log: *mut bun_ast::Log, @@ -2942,8 +2874,8 @@ impl<'a> Transpiler<'a> { Ok(final_result) } - /// Port of `transpiler.zig:1344 processResolveQueue` (with - /// `wrap_entry_point = false`, the only value passed by the in-tree caller). + /// `wrap_entry_point` is fixed to `false`, the only value passed by the + /// in-tree caller. fn process_resolve_queue( &mut self, import_path_format: options::ImportPathFormat, @@ -2968,7 +2900,6 @@ impl<'a> Transpiler<'a> { Ok(()) } - /// Port of `transpiler.zig:380 buildWithResolveResultEager`. fn build_with_resolve_result_eager( &mut self, resolve_result: &resolver::Result, @@ -2991,9 +2922,9 @@ impl<'a> Transpiler<'a> { let file_path_ext: &'static [u8] = crate::linker::dupe(file_path_ref.name().ext); // Step 1. Parse & scan - // Spec (transpiler.zig:397) keys the loader on the ORIGINAL resolve - // result's extension *before* the `client_entry_point` path override - // (line 400). Compute it here, then apply the override. + // Key the loader on the ORIGINAL resolve + // result's extension *before* the `client_entry_point` path override. + // Compute it here, then apply the override. let loader = self.options.loader(file_path_ext); // `client_entry_point_` is always `None` from the only in-tree caller; @@ -3040,8 +2971,8 @@ impl<'a> Transpiler<'a> { let emit_decorator_metadata = resolve_result.flags.emit_decorator_metadata(); let experimental_decorators = resolve_result.flags.experimental_decorators(); // `MacroRemap` (StringArrayHashMap of StringArrayHashMap) has - // no nested `Clone` impl (the inner clone is fallible); Zig - // copied it by value. Rebuild the outer map, deep-cloning + // no nested `Clone` impl (the inner clone is fallible). + // Rebuild the outer map, deep-cloning // each inner map (fallible), matching the build-command // conversion. let macro_remappings = { @@ -3298,14 +3229,11 @@ impl<'a> Transpiler<'a> { } } -/// Port of the `comptime Outstream: type` parameter to -/// `processResolveQueue` / `buildWithResolveResultEager` — Zig switched on -/// `bun.sys.File` vs `std.fs.Dir` at the type level; collapse to a runtime -/// enum since the only behavioural difference is unused (`_ = outstream`). +/// Outstream selector for `process_resolve_queue` / +/// `build_with_resolve_result_eager` — a runtime enum since the only +/// behavioural difference is unused (`_ = outstream`). #[derive(Clone, Copy)] enum TransformOutstream { Stdout, Dir(#[expect(dead_code)] bun_sys::Fd), } - -// ported from: src/bundler/transpiler.zig diff --git a/src/bundler_jsc/PluginRunner.rs b/src/bundler_jsc/PluginRunner.rs index fe427df7100e..4395d2d1c0b0 100644 --- a/src/bundler_jsc/PluginRunner.rs +++ b/src/bundler_jsc/PluginRunner.rs @@ -1,41 +1,38 @@ -//! Runtime plugin host (JS-side `Bun.plugin()` resolve hooks). Moved from -//! `bundler/transpiler.zig` so `bundler/` is free of `JSValue`/`JSGlobalObject`. +//! Runtime plugin host (JS-side `Bun.plugin()` resolve hooks). Lives here so +//! `bundler/` is free of `JSValue`/`JSGlobalObject`. pub use bun_resolver::fs::Path as FsPath; -/// Spec `PluginRunner.zig:MacroJSCtx` — re-export of the canonical newtype +/// Re-export of the canonical newtype /// (defined at the lowest tier that stores it, `bun_ast::Macro`). pub use bun_bundler::transpiler::MacroJSCtx as MacroJsCtx; -/// Spec `PluginRunner.zig:PluginRunner` — re-export of the concrete struct. +/// Re-export of the concrete struct. /// `extract_namespace` / `could_be_plugin` (pure byte parsing) live in /// `bun_bundler`; the stateful struct + `on_resolve` body live in /// `bun_jsc::plugin_runner`. `on_resolve_jsc` (below) is a free fn because it /// only reads the global, not the runner record. pub use bun_jsc::plugin_runner::PluginRunner; -/// Spec PluginRunner.zig:14 — re-export for callers that named this module. +/// Re-export for callers that named this module. #[inline] pub fn extract_namespace(specifier: &[u8]) -> &[u8] { PluginRunner::extract_namespace(specifier) } -/// Spec PluginRunner.zig:22 — re-export for callers that named this module. +/// Re-export for callers that named this module. #[inline] pub fn could_be_plugin(specifier: &[u8]) -> bool { PluginRunner::could_be_plugin(specifier) } -// `on_resolve` (the `Log`-reporting variant, PluginRunner.zig:34) lives at +// `on_resolve` (the `Log`-reporting variant) lives at // `bun_jsc::plugin_runner::PluginRunner` as the `PluginResolver` impl — // `bun_jsc` is the lowest tier that can name `JSGlobalObject` AND is reachable // from `Bun__onDidAppendPlugin`. -/// Spec PluginRunner.zig:121 `onResolveJSC`. /// LAYERING: body moved DOWN into `bun_jsc::virtual_machine` so the /// VM's `resolve_maybe_needs_trailing_slash` can consult it without a /// `bun_jsc → bun_bundler_jsc` cycle. Re-exported here for callers that /// still name this module. pub use bun_jsc::virtual_machine::plugin_runner_on_resolve_jsc as on_resolve_jsc; - -// ported from: src/bundler_jsc/PluginRunner.zig diff --git a/src/bundler_jsc/analyze_jsc.rs b/src/bundler_jsc/analyze_jsc.rs index 82232921c7f0..7648b1b3763c 100644 --- a/src/bundler_jsc/analyze_jsc.rs +++ b/src/bundler_jsc/analyze_jsc.rs @@ -2,7 +2,7 @@ //! `ModuleInfoDeserialized` into a `JSC::JSModuleRecord`. Aliased back so the //! `extern "C"` symbol names are still discoverable from C++. //! -//! Note: the `zig__renderDiff` export from `analyze_jsc.zig` lives in +//! Note: the `zig__renderDiff` export lives in //! `bun_runtime::test_runner::diff_format` instead — `DiffFormatter` is a //! higher-tier type this crate cannot depend on, and the C++ caller only needs //! the symbol at link time, not a particular crate. @@ -55,8 +55,8 @@ pub(crate) extern "C" fn zig__ModuleInfoDeserialized__toJSModuleRecord( } let identifiers = IdentifierArray::create(strings_lens.len()); - // SAFETY: `identifiers` is non-null (returned by `create`); destroyed exactly once at scope exit, - // mirroring Zig's `defer identifiers.destroy()` (runs on both success and early-return paths). + // SAFETY: `identifiers` is non-null (returned by `create`); the scopeguard destroys it + // exactly once at scope exit (on both success and early-return paths). let _identifiers_guard = scopeguard::guard(identifiers, |p| unsafe { IdentifierArray::destroy(p); }); @@ -141,7 +141,6 @@ pub(crate) extern "C" fn zig__ModuleInfoDeserialized__toJSModuleRecord( RequestedModuleValue::Json => { module_record.add_requested_module_json(identifiers, reqk, phase_defer) } - // Zig open-enum tail: `else => |uv| @enumFromInt(@intFromEnum(uv))` — // FetchParameters and StringID are both `#[repr(transparent)] u32`, so this // is a bitcast of the raw discriminant back into the interned-string index. uv => module_record.add_requested_module_host_defined( @@ -409,8 +408,7 @@ impl JSModuleRecord { } // Thin method shims over the raw `*mut JSModuleRecord` returned by `create`. -// These take `*mut Self` because the Zig side calls them as `module_record.addX(...)` -// on a raw pointer; we keep raw-ptr receivers to avoid materializing `&mut` aliases. +// These take `*mut Self` raw-ptr receivers to avoid materializing `&mut` aliases. trait JSModuleRecordExt { fn add_indirect_export( self, @@ -674,5 +672,3 @@ impl JSModuleRecordExt for *mut JSModuleRecord { } } } - -// ported from: src/bundler_jsc/analyze_jsc.zig diff --git a/src/bundler_jsc/lib.rs b/src/bundler_jsc/lib.rs index 1afeea213736..92966512bd12 100644 --- a/src/bundler_jsc/lib.rs +++ b/src/bundler_jsc/lib.rs @@ -16,7 +16,7 @@ pub mod options_jsc; #[path = "PluginRunner.rs"] pub mod PluginRunner; -// LAYERING: `output_file_jsc` (port of `src/bundler_jsc/output_file_jsc.zig`) +// LAYERING: `output_file_jsc` // constructs `webcore::Blob`/`Store`, `api::BuildArtifact`, and // `node::PathOrFileDescriptor`. Those types live in `bun_runtime`, which is // not a dependency of this crate. The module has been moved to diff --git a/src/bundler_jsc/options_jsc.rs b/src/bundler_jsc/options_jsc.rs index 7fcd92a0b3ea..c83418c1c716 100644 --- a/src/bundler_jsc/options_jsc.rs +++ b/src/bundler_jsc/options_jsc.rs @@ -80,5 +80,3 @@ pub fn compile_target_from_slice( Ok(target_parsed) } - -// ported from: src/bundler_jsc/options_jsc.zig diff --git a/src/bundler_jsc/source_map_mode_jsc.rs b/src/bundler_jsc/source_map_mode_jsc.rs index f3841db6ddd5..77f025b5b221 100644 --- a/src/bundler_jsc/source_map_mode_jsc.rs +++ b/src/bundler_jsc/source_map_mode_jsc.rs @@ -1,6 +1,5 @@ -//! `from_js` for `bun.schema.api.SourceMapMode` — kept out of -//! `options_types/schema.zig` so that file has no `JSGlobalObject`/`JSValue` -//! references. +//! `from_js` for `bun.schema.api.SourceMapMode` — kept here so the schema +//! module has no `JSGlobalObject`/`JSValue` references. use crate::{JSGlobalObject, JSValue, JsResult}; use bun_options_types::schema::api::SourceMapMode; @@ -27,5 +26,3 @@ pub fn source_map_mode_from_js( } Ok(None) } - -// ported from: src/bundler_jsc/source_map_mode_jsc.zig diff --git a/src/bunfig/arguments.rs b/src/bunfig/arguments.rs index ff128d009e57..a25da143c6dd 100644 --- a/src/bunfig/arguments.rs +++ b/src/bunfig/arguments.rs @@ -1,5 +1,4 @@ -//! Port of `src/runtime/cli/Arguments.zig` — bunfig-loading subset. -//! +//! Bunfig-loading subset of CLI argument handling: these functions //! and their private helpers were lifted out of `bun_runtime::cli::Arguments` //! so that mid-tier crates (`bun_install`) can call them directly. The //! `bun_runtime` crate re-exports these for its own callers. @@ -62,8 +61,8 @@ fn load_bunfig( bun_ast::expr::data::Store::create(); let _store_reset = bun_ast::StoreResetGuard::new(); - // Reshaped for borrowck: Zig's `defer { ctx.log.level = original }` - // would capture `&mut *ctx.log` past the `Bunfig::parse(.., ctx)` reborrow. + // A drop-guard borrowing `&mut *ctx.log` would conflict with the + // `Bunfig::parse(.., ctx)` reborrow. // Route through the raw `*mut Log` (process-lifetime, set in // `create_context_data()`); the guard restores `level` on unwind/return. let log_ptr: *mut bun_ast::Log = ctx.log; @@ -100,7 +99,7 @@ pub fn load_config_path( config_path: &ZStr, ctx: Context<'_>, ) -> Result<(), bun_core::Error> { - // Zig's `comptime cmd.readGlobalConfig()` is demoted to runtime here (see + // `cmd.read_global_config()` is evaluated at runtime (see // the note on `Parser::parse` in src/bunfig/bunfig.rs); // `Tag::read_global_config` is a const-ish // lookup so the dead arm is still a single branch. diff --git a/src/bunfig/bunfig.rs b/src/bunfig/bunfig.rs index f245183fd785..eca200865bcc 100644 --- a/src/bunfig/bunfig.rs +++ b/src/bunfig/bunfig.rs @@ -1,4 +1,4 @@ -//! Port of `src/runtime/cli/bunfig.zig`. +//! Bunfig (configuration file) loading. //! //! `Bunfig::parse` and the inner `Parser` route through the real //! `bun_parsers::{toml,json}` parsers (which produce the value-shaped @@ -28,7 +28,6 @@ use bun_options_types::schema::api; use bun_options_types::command_tag::Tag as CommandTag; use bun_options_types::context::ContextData; -// Re-exports (Zig: `pub const OfflineMode = @import("../options_types/OfflineMode.zig").OfflineMode;`) pub use bun_options_types::offline_mode::OfflineMode; // TODO: replace api.TransformOptions with Bunfig @@ -40,9 +39,8 @@ fn estring_to_owned(s: &E::EString, bump: &Bump) -> Box<[u8]> { Box::<[u8]>::from(s.string(bump).expect("OOM")) } -/// Port of `resolver/package_json.zig` `PackageJSON.parseMacrosJSON`. -/// -/// Re-ported here against the value-shaped `bun_ast::Expr` (the +/// Macro-remap parsing (the `PackageJSON.parseMacrosJSON` shape), +/// implemented here against the value-shaped `bun_ast::Expr` (the /// tree produced by the TOML/JSON parsers) and returning the /// `bun_options_types::context::MacroMap` shape so the result slots directly /// into `ctx.debug.macros` without crossing the `bun_ast::Expr` / @@ -136,7 +134,7 @@ fn parse_macros_json( #[inline] fn num_to_u32(n: f64) -> u32 { - // Note: Rust `as` saturates on overflow/NaN where Zig @intFromFloat is UB. + // Note: Rust `as` saturates on overflow/NaN. n as u32 } @@ -148,9 +146,9 @@ pub(crate) struct Parser<'a> { json: Expr, source: &'a bun_ast::Source, log: &'a mut bun_ast::Log, - // Zig held both `bunfig: *api.TransformOptions` (= `&ctx.args`) - // and `ctx: *Command.Context` simultaneously. Rust forbids the overlapping - // borrow, so `bunfig` writes route through `self.ctx.args` directly. + // Holding both a `TransformOptions` pointer (= `&ctx.args`) and `ctx` + // would be an overlapping borrow, so `bunfig` writes route through + // `self.ctx.args` directly. ctx: &'a mut ContextData, /// Arena backing `EString::string()` UTF-16→UTF-8 transcodes; lifetime /// matches the `Expr` tree (same bump used for the TOML/JSON parse). @@ -232,7 +230,6 @@ impl<'a> Parser<'a> { fn load_log_level(&mut self, expr: &Expr) -> Result<(), bun_core::Error> { self.expect_string(expr)?; - // PERF(port): Zig used strings.ExactSizeMatcher(8). let level = match expr.as_string(self.bump).unwrap_or(b"") { b"debug" => api::MessageLevel::Debug, b"error" => api::MessageLevel::Err, @@ -258,7 +255,6 @@ impl<'a> Parser<'a> { self.expect_string(item)?; if let ExprData::EString(s) = &item.data { if s.len() > 0 { - // PERF(port): was appendAssumeCapacity preloads.push(estring_to_owned(s, self.bump)); } } @@ -348,10 +344,10 @@ impl<'a> Parser<'a> { Ok(api::StringMap { keys, values }) } - // Zig's `comptime cmd: Command.Tag` is demoted to a runtime arg — + // `cmd` is a runtime arg rather than a const generic — // `bun_options_types::command_tag::Tag` does not derive `ConstParamTy` (it - // already derives `enum_map::Enum`, which conflicts). The Zig original - // monomorphised over `cmd` purely to dead-code-eliminate untaken arms; the + // already derives `enum_map::Enum`, which conflicts). Monomorphising over + // `cmd` would only dead-code-eliminate untaken arms; the // runtime branches below are equivalent and the few hot fields are tiny. pub(crate) fn parse(&mut self, cmd: CommandTag) -> Result<(), bun_core::Error> { bun_analytics::features::bunfig.fetch_add(1, Ordering::Relaxed); @@ -788,7 +784,7 @@ impl<'a> Parser<'a> { if let Some(elide_lines) = run_expr.get(b"elide-lines") { if let Some(n) = elide_lines.as_number() { - // Note: Rust `as` saturates on overflow/NaN where Zig @intFromFloat is UB + // Note: Rust `as` saturates on overflow/NaN self.ctx.bundler_options.elide_lines = Some(n as usize); } else { self.add_error(elide_lines.loc, b"Expected number")?; @@ -937,7 +933,6 @@ impl<'a> Parser<'a> { self.add_error(key_expr.loc, b"Expected package name")?; } - // PERF(port): was putAssumeCapacity self.ctx.debug.package_bundle_map.insert( path, if b.value { @@ -1120,16 +1115,14 @@ impl Bunfig { let log: &mut bun_ast::Log = unsafe { &mut *log_ptr }; let log_count = log.errors + log.warnings; - // Zig passes `bun.default_allocator` here — no side `mi_heap`. The Rust - // port previously called `Arena::new()` (= `mi_heap_new` + + // This previously called `Arena::new()` (= `mi_heap_new` + // `mi_heap_destroy` on drop), which perf attributed ~1.6% of // `bun -e ''` startup to. Borrow the process default heap instead so - // TOML/JSON parse allocations route through plain `mi_malloc`, matching - // Zig. Parsed config lives for the process lifetime either way. + // TOML/JSON parse allocations route through plain `mi_malloc`. + // Parsed config lives for the process lifetime either way. let bump = Bump::borrowing_default(); let ext = source.path.name().ext; - // Zig: `if (strings.eqlComptime(source.path.name.ext[1..], "toml"))` let is_toml = ext.len() > 1 && &ext[1..] == b"toml"; let expr = if is_toml { @@ -1168,9 +1161,9 @@ impl Bunfig { } }; - // Reshaped for borrowck: Zig stored both `&mut ctx` and - // `&mut ctx.args` simultaneously inside Parser. In Rust we route bunfig - // writes through `self.ctx.args` directly. `log` is derived from the + // Parser cannot hold both `&mut ctx` and `&mut ctx.args` + // simultaneously, so bunfig + // writes route through `self.ctx.args` directly. `log` is derived from the // copied raw pointer above so it does not overlap the `&mut ctx` borrow. // SAFETY: Parser never reaches `ctx.log` (only `self.log`), so no two // live `&mut` to the same `Log` coexist. @@ -1259,7 +1252,6 @@ impl<'a> Parser<'a> { } fn parse_install(&mut self, install_obj: &Expr) -> Result<(), bun_core::Error> { - // Zig held `*BunInstall` and `*Parser` simultaneously. // The helper methods (`expect*`, `add_error`, `parse_registry`) take // `&mut self`, which under Stacked Borrows would invalidate any // long-lived `&mut` derived from `self.ctx.install`. Move the box @@ -1540,7 +1532,7 @@ impl<'a> Parser<'a> { } } - // bunfig.zig:824-839 — remap PnpmMatcher errors so callers (and the + // Remap PnpmMatcher errors so callers (and the // crash handler's `"Invalid Bunfig"` match) see the canonical // bunfig error; only OOM passes through unchanged. let remap = |e: FromExprError| -> bun_core::Error { @@ -1680,5 +1672,3 @@ impl<'a> Parser<'a> { Ok(()) } } - -// ported from: src/runtime/cli/bunfig.zig diff --git a/src/bunfig/lib.rs b/src/bunfig/lib.rs index 081e628aabf2..12e96c1be162 100644 --- a/src/bunfig/lib.rs +++ b/src/bunfig/lib.rs @@ -1,8 +1,8 @@ //! `bun_bunfig` — bunfig.toml parser and `Arguments::loadConfig` entrypoints. //! -//! that `bun_install::PackageManager::init` can call -//! `bun.cli.Arguments.loadConfig(_, cli.config, ctx, .InstallCommand)` -//! (PackageManager.zig:801) without a tier-6 dependency or fn-pointer hook. +//! Split out so that `bun_install::PackageManager::init` can call +//! `Arguments::loadConfig` +//! without a tier-6 dependency or fn-pointer hook. //! Every dependency of this crate was already a transitive dependency of //! `bun_install` (via `bun_transpiler` → `bun_bundler`), so no cycle is //! introduced; this only makes the existing edge direct. diff --git a/src/c-headers-for-zig.h b/src/c-headers-for-zig.h index d08b86b70511..29d0b1fb6c20 100644 --- a/src/c-headers-for-zig.h +++ b/src/c-headers-for-zig.h @@ -1,14 +1,10 @@ -// This file is run through translate-c and exposed to Zig code -// under the namespace bun.c (lowercase c). Prefer adding includes -// to this file instead of manually porting struct definitions -// into Zig code. By using automatic translation, differences +// Aggregated C includes for automatic translation into bindings. +// Prefer adding includes +// to this file instead of manually porting struct definitions. +// By using automatic translation, differences // between platforms and subtle mistakes can be avoided. // -// One way to locate a definition for a given symbol is to open -// Zig's `lib` directory and run ripgrep on it. For example, -// `sockaddr_dl` is in `libc/include/any-macos-any/net/if_dl.h` -// -// When Zig is translating this file, it will define these macros: +// When this file is translated, these macros are defined: // - WINDOWS // - DARWIN // - LINUX diff --git a/src/cares_sys/c_ares.rs b/src/cares_sys/c_ares.rs index 9a80abaa3bf6..efdc8ddd3158 100644 --- a/src/cares_sys/c_ares.rs +++ b/src/cares_sys/c_ares.rs @@ -32,7 +32,7 @@ bun_opaque::opaque_ffi! { pub struct struct_apattern; } -/// Mirror of `std.posix.AF` in Zig — only the address families c-ares +/// Only the address families c-ares /// actually uses. Kept local so this `*_sys` crate stays leaf-level /// (no dependency on `bun_sys`). Canonical: `bun_sys::posix::AF`. pub mod AF { @@ -50,7 +50,7 @@ pub mod AF { pub const INET6: c_int = libc::AF_INET6; } -/// Mirror of `std.posix.system.EAI` in Zig. The `libc` crate is missing +/// `EAI_*` getaddrinfo error codes. The `libc` crate is missing /// `EAI_ADDRFAMILY` and the glibc-only async-getaddrinfo extensions, so we /// hardcode those raw values from ``. #[cfg(not(windows))] @@ -118,7 +118,7 @@ pub enum NSClass { ns_c_max = 65536, } -// Zig: `enum(c_int) { ..., _ }` (non-exhaustive). Values are only ever +// Values are only ever // constructed in Rust and passed *to* C, so a plain repr(i32) enum is sound. // If c-ares ever returns an NSType, this must become a transparent newtype. #[repr(i32)] @@ -305,13 +305,9 @@ pub struct struct_hostent { pub h_addr_list: *mut *mut c_char, // NUL-terminated array } -// ─── callback-wrapper reshaping note ────────────────────────────────────── -// Zig: each reply type defines -// pub fn Callback(comptime Type) type = fn(*Type, ?Error, i32, ?*Reply) void -// pub fn callbackWrapper(comptime lookup_name, comptime Type, comptime fn) ares_callback -// which monomorphizes a unique `extern "C"` thunk per (Type, fn) pair via the -// anonymous-struct trick. Rust cannot take a fn pointer as a const generic on -// stable, so the wrappers below are reshaped to a trait: the implementing +// ─── callback-wrapper note ───────────────────────────────────────────────── +// Rust cannot take a fn pointer as a const generic on +// stable, so the wrappers below are expressed as a trait: the implementing // type provides the callback as a trait method, and the `extern "C"` thunk is // monomorphized per `T: Trait`. // ────────────────────────────────────────────────────────────────────────── @@ -338,7 +334,7 @@ impl struct_hostent { this.on_hostent(None, timeouts, hostent); } - // Zig branched on `comptime lookup_name`; split into one thunk per name. + // One `extern "C"` thunk per lookup name. pub unsafe extern "C" fn callback_wrapper_cname( ctx: *mut c_void, status: c_int, @@ -450,8 +446,7 @@ impl Default for hostent_with_ttls { pub trait HostentWithTtlsHandler: Sized { /// `hostent_with_ttls::parse_a` or `parse_aaaa` — selects the c-ares reply - /// parser for [`hostent_with_ttls::callback_wrapper`]. Mirrors the Zig - /// `callbackWrapper(comptime lookup_name, ...)` parameterization. + /// parser for [`hostent_with_ttls::callback_wrapper`]. const PARSE: fn(&[u8]) -> Result, Error>; fn on_hostent_with_ttls( @@ -465,11 +460,6 @@ pub trait HostentWithTtlsHandler: Sized { impl hostent_with_ttls { // toJSResponse alias deleted — lives in bun_runtime::dns_jsc. - // Zig also declared a `hostCallbackWrapper` here whose 4th arg was - // `?*hostent_with_ttls`, force-cast to `ares_host_callback` (which passes - // `?*struct_hostent`) — type-confused and never registered with c-ares. - // It is not ported. - pub unsafe extern "C" fn callback_wrapper( ctx: *mut c_void, status: c_int, @@ -559,9 +549,8 @@ impl Drop for hostent_with_ttls { } } -// Per-record-type newtype aliases. Zig instantiated the resolve machinery over -// the same `struct_hostent` / `hostent_with_ttls` with a comptime `type_name` -// string; Rust callers (`dns.rs`) need distinct type names to monomorphise the +// Per-record-type newtype aliases. +// Callers (`dns.rs`) need distinct type names to monomorphise the // `CAresRecordType` cache-field constant per record. For now these are plain // aliases — the trait impls live downstream. pub type NsHostent = struct_hostent; @@ -660,10 +649,7 @@ impl AddrInfo { unsafe { core::ffi::CStr::from_ptr(self.name_) }.to_bytes() } - // Zig also exposed `cnames()`, which called `bun.span` on a - // `[*c]AddrInfo_cname` (a linked list, not a sentinel-terminated array) - // and returned `[]const AddrInfo_node` — type-confused and unused. It is - // not ported; consumers walk `cnames_` / `node` pointer chains directly. + // Consumers walk `cnames_` / `node` pointer chains directly. pub unsafe extern "C" fn callback_wrapper( ctx: *mut c_void, @@ -719,7 +705,7 @@ bun_opaque::opaque_ffi! { const _: () = assert!(core::mem::size_of::() == 0); /// Implemented by the type that owns a `*mut Channel` and receives socket- -/// state callbacks. Zig: `Container.onDNSSocketState` + `this.channel = ch`. +/// state callbacks. /// /// R-2: methods take `&self`. The c-ares `sock_state_cb` re-enters the /// container while a `&self` borrow may already be live in `on_dns_poll`; @@ -731,9 +717,7 @@ pub trait ChannelContainer: Sized { /// Trait for `Channel::resolve`: ties a lookup-name string to its NSType and /// the `extern "C"` parse-thunk used as the ares_callback. -/// Zig dispatched via `@field(NSType, "ns_t_" ++ lookup_name)` and -/// `cares_type.callbackWrapper(lookup_name, Type, callback)`. This trait is the -/// Rust-side reshaping; the dns_jsc consumer impls it per (T, record-type). +/// The dns_jsc consumer impls it per (T, record-type). pub trait ResolveHandler: Sized { const LOOKUP_NAME: &'static [u8]; const NS_TYPE: NSType; @@ -819,7 +803,7 @@ impl Channel { unsafe { ares_destroy(this) }; } - /// See c-ares `ares_getaddrinfo` documentation (mirrored in the Zig source). + /// See c-ares `ares_getaddrinfo` documentation. pub fn get_addr_info( &mut self, host: &[u8], @@ -1236,7 +1220,7 @@ pub struct struct_ares_addr6ttl { } // SAFETY: `#[repr(C)]` POD — 16-byte byte-array union + `c_int`. All-zero is a -// valid bit pattern (matches Zig `std.mem.zeroes`) (S021). +// valid bit pattern (S021). unsafe impl bun_core::ffi::Zeroable for struct_ares_addr6ttl {} impl Default for struct_ares_addr6ttl { #[inline] @@ -1633,7 +1617,6 @@ impl struct_any_reply { impl Drop for struct_any_reply { fn drop(&mut self) { - // Zig: `inline for (@typeInfo(..).fields)` — written out by hand. // a_reply / aaaa_reply are Box; their Drop frees the // inner hostent via ares_free_hostent. // SAFETY: each field is either null or a c-ares allocation matching its free fn. @@ -2199,17 +2182,8 @@ pub fn get_sockaddr(addr: &[u8], port: u16, sa: &mut sockaddr) -> c_int { /// The C `struct in_addr` (4-byte IPv4 address), as c-ares' `ares_options.servers` /// and the `ares_addr_node`/`ares_addr_port_node` unions declare it. -/// -/// Zig aliased this to `std.posix.sockaddr.in` (the full 16-byte sockaddr_in) — -/// a misnomer that never bit because the only uses are behind pointers -/// (`Options.servers`, never dereferenced; Bun never sets `ARES_OPT_SERVERS`) -/// or as the smaller arm of a union whose size/alignment are pinned by the -/// 16-byte `addr6` arm and whose contents are only accessed through type-erased -/// pointers (`addr_ptr`/`addr_mut_ptr`). Defined correctly here. #[repr(C)] #[derive(Copy, Clone)] pub struct in_addr { pub s_addr: u32, } - -// ported from: src/cares_sys/c_ares.zig diff --git a/src/clap/args.rs b/src/clap/args.rs index 6f2fca38b1b5..445d97a2c5a7 100644 --- a/src/clap/args.rs +++ b/src/clap/args.rs @@ -1,7 +1,7 @@ use core::convert::Infallible; use std::borrow::Cow; -/// Duck-typed arg-iterator surface (Zig used `anytype`). Implemented by +/// Arg-iterator surface. Implemented by /// `OsIterator` and `SliceIterator`; `ShellIterator` does not fit (fallible, /// owned results) and is used standalone. pub trait ArgIter<'a> { @@ -20,7 +20,7 @@ impl ExampleArgIterator { } /// Pop the first element of `remain`, advancing the slice. Shared body for -/// `SliceIterator::next` / `OsIterator::next` (the .zig spec duplicates them). +/// `SliceIterator::next` / `OsIterator::next`. #[inline] fn pop_first<'a>(remain: &mut &'a [&'a [u8]]) -> Option<&'a [u8]> { if remain.is_empty() { @@ -96,10 +96,8 @@ impl ArgIter<'static> for OsIterator { } } -/// Process argv as a `&'static` slice of `&'static [u8]`. -/// -/// Zig: `bun.argv: [][:0]const u8` — the process-global view that includes -/// `BUN_OPTIONS` injection. +/// Process argv as a `&'static` slice of `&'static [u8]` — the process-global +/// view that includes `BUN_OPTIONS` injection. /// /// This used to project `&ZStr → &[u8]` through a `OnceLock>`, /// which (a) allocated a Vec on the `--version` startup path and (b) emitted a @@ -124,8 +122,8 @@ pub enum ShellIteratorError { DanglingEscape, #[error("QuoteNotClosed")] QuoteNotClosed, - // Zig's error union also included `mem.Allocator.Error` (OutOfMemory). Vec aborts - // on OOM under the global mimalloc allocator, so that variant does not exist here. + // There is no OutOfMemory variant: Vec aborts + // on OOM under the global mimalloc allocator. } bun_core::named_error_set!(ShellIteratorError); @@ -161,8 +159,7 @@ impl<'a> ShellIterator<'a> { let mut start: usize = 0; let mut state = State::SkipWhitespace; - // Copy the slice ref so we can reassign `self.str` before returning - // (Zig used `defer iter.str = ...`). + // Copy the slice ref so we can reassign `self.str` before returning. let s: &'a [u8] = self.str; for (i, &c) in s.iter().enumerate() { @@ -278,8 +275,8 @@ impl<'a> ShellIterator<'a> { // The state we end up when after the escape character (`\`). All these // states do is transition back into the previous state. // TODO: Are there any escape sequences that does transform the second - // character into something else? For example, in Zig, `\n` is - // transformed into the line feed ascii character. + // character into something else (e.g. `\n` into the line feed + // ascii character)? State::NoQuoteEscape => { state = State::NoQuote; } @@ -314,7 +311,6 @@ impl<'a> ShellIterator<'a> { // the rest we have to the list and return that. if !list.is_empty() { list.extend_from_slice(res); - // PERF(port): was arena-backed `toOwnedSlice()`. return Ok(Some(Cow::Owned(list))); } Ok(Some(Cow::Borrowed(res))) @@ -337,8 +333,8 @@ mod tests { } fn test_shell_iterator_ok(str: &[u8], allocations: usize, expect: &[&[u8]]) { - // Zig used `testing.FailingAllocator` to count allocations. The Rust port - // has no allocator injection, but every allocating result surfaces as a + // There is no allocator injection to count allocations with, + // but every allocating result surfaces as a // `Cow::Owned`, so counting owned results checks the same property: the // borrowed (zero-copy) fast path is taken whenever possible. let mut owned_results: usize = 0; @@ -433,5 +429,3 @@ mod tests { test_shell_iterator_err(b"a\\", ShellIteratorError::DanglingEscape); } } - -// ported from: src/clap/args.zig diff --git a/src/clap/comptime.rs b/src/clap/comptime.rs index a404b8186afd..345ddad22196 100644 --- a/src/clap/comptime.rs +++ b/src/clap/comptime.rs @@ -7,21 +7,20 @@ use crate::streaming::{self, StreamingClap}; use crate::{Names, Param, ParseOptions, Values}; // ───────────────────────────────────────────────────────────────────────────── -// Compile-time conversion (Zig parity) +// Compile-time conversion // -// Zig's `ComptimeClap(Id, params)` is a comptime type-generator: it iterates -// `params` *at comptime* to (a) re-index every param's `id` to its slot within -// its category (flag / single / multi) and (b) emit a struct with fixed-size -// array fields. `findParam` is `inline for`, so every `args.flag("--foo")` -// compiles to a constant index — zero runtime cost. +// The param-table conversion can run entirely at compile time: (a) re-index +// every param's `id` to its slot within +// its category (flag / single / multi) and (b) emit fixed-size +// array fields, so every `args.flag("--foo")` +// lookup folds to a constant index — zero runtime cost. // -// An earlier draft of this port did all of this at runtime: `convert_params` heap-allocated +// An earlier draft did all of this at runtime: `convert_params` heap-allocated // a `Vec>` on every CLI start, and `find_param` linear-scanned it // for every `flag()`/`option()` lookup (~190 lookups × ~100 params on the -// `bun run` path). perf put this at ~0.25 % of `bun --version` cycles vs ~0 % -// in Zig. +// `bun run` path). perf put this at ~0.25 % of `bun --version` cycles. // -// This module restores the comptime semantics on stable Rust: +// This module does the conversion at compile time on stable Rust: // // * `count_flags` / `count_single` / `count_multi` / `convert_params_array` // / `find_param_index` are all `const fn`, so a param table declared with @@ -29,8 +28,7 @@ use crate::{Names, Param, ParseOptions, Values}; // name lookup can be folded to a constant via `const { find_param_index(..) }`. // // * `comptime_table!` (in `lib.rs`) packages the const-fn output as a -// `&'static ConvertedTable` — the Rust analogue of the Zig comptime -// `converted_params` const baked into the generated type. +// `&'static ConvertedTable` baked into rodata. // // * Every per-subcommand table in `runtime/cli/Arguments.rs` is now a // `pub static *_TABLE: &ConvertedTable = comptime_table!(*_PARAMS)`, and @@ -48,7 +46,7 @@ const fn is_named(p: &Param) -> bool { p.names.long.is_some() || p.names.short.is_some() } -/// Count flag params (named, `takes_value == None`). Zig: comptime loop arm. +/// Count flag params (named, `takes_value == None`). pub const fn count_flags(params: &[Param]) -> usize { let mut n = 0; let mut i = 0; @@ -89,8 +87,8 @@ pub const fn count_multi(params: &[Param]) -> usize { n } -/// Compile-time equivalent of the Zig comptime conversion loop (comptime.zig -/// lines 6–32): re-indexes each param's `id` to its slot within its category. +/// Compile-time conversion loop: +/// re-indexes each param's `id` to its slot within its category. /// `N` must equal `params.len()` (asserted at const-eval). pub const fn convert_params_array(params: &[Param]) -> [Param; N] { const DUMMY: Param = Param { @@ -137,16 +135,14 @@ pub const fn convert_params_array(params: &[Param]) -> [ out } -/// Compile-time name → converted-param index. This is the Rust analogue of -/// Zig's `inline for` `findParam` and is intended to be called inside a +/// Compile-time name → converted-param index. Intended to be called inside a /// `const { }` block so the loop folds to a literal: /// /// ```ignore /// const IDX: usize = find_param_index(TABLE.converted, b"--help"); /// ``` /// -/// Panics (at const-eval, i.e. a build error) if `name` is not in `converted` -/// — matching Zig's `@compileError("no param '…'")`. +/// Panics (at const-eval, i.e. a build error) if `name` is not in `converted`. pub(crate) const fn find_param_index(converted: &[Param], name: &[u8]) -> usize { if name.len() > 2 && name[0] == b'-' && name[1] == b'-' { let (_, key) = name.split_at(2); @@ -300,8 +296,8 @@ impl ConvertedTable { /// Build a table entirely at compile time. All four arguments come from /// the `const fn`s above via [`comptime_table!`](crate::comptime_table), /// so the converted param array, category counts, sorted long-name hash - /// index, and short-name direct index all land in rodata — full Zig - /// `ComptimeClap` parity with zero runtime work. + /// index, and short-name direct index all land in rodata — + /// zero runtime work. pub const fn from_const( converted: &'static [Param], n_flags: usize, @@ -509,17 +505,15 @@ pub fn convert_params(params: &[Param]) -> (Vec>, usize, us /// Deprecated: Use `parse_ex` instead pub struct ComptimeClap { - // Field order matches comptime.zig. - // Inner `&'static [u8]` slices borrow argv (process-lifetime); never freed in Zig `deinit`. + // Inner `&'static [u8]` slices borrow argv (process-lifetime). pub single_options: Box<[Option<&'static [u8]>]>, pub multi_options: Box<[Box<[&'static [u8]]>]>, pub flags: Box<[bool]>, pub pos: Box<[&'static [u8]]>, pub passthrough_positionals: Box<[&'static [u8]]>, - // `mem.Allocator param` field deleted — global mimalloc (see PORTING.md §Allocators). - // Zig captures `converted_params` as a comptime const on the returned type. Rust - // carries it as a `&'static` table — either rodata (`comptime_table!`) or + // The converted params are + // carried as a `&'static` table — either rodata (`comptime_table!`) or // interned-once via the ptr-keyed registry — so `flag`/`option` resolve via // hashed lookup instead of an O(n) scan, and no per-parse `Vec` is allocated. table: &'static ConvertedTable, @@ -571,8 +565,6 @@ impl ComptimeClap { vec![None; table.n_single].into_boxed_slice(); let mut flags: Box<[bool]> = vec![false; table.n_flags].into_boxed_slice(); - // Zig: `StreamingClap(usize, @typeInfo(@TypeOf(iter)).pointer.child)` — the second - // type arg is the pointee of `iter`; in Rust that is just `I`. let mut stream = StreamingClap:: { params: table.converted, iter, @@ -587,9 +579,6 @@ impl ComptimeClap { pos.push(arg.value.unwrap()); if opt.stop_after_positional_at > 0 && pos.len() >= opt.stop_after_positional_at { let mut remaining_ = stream.iter.remain(); - // Zig called `bun.span` (NUL-scan) on `[:0]const u8` argv entries. - // Our `ArgIter` already yields sized `&[u8]`, so `span` is a - // no-op and is dropped. let first: &[u8] = if !remaining_.is_empty() { remaining_[0] } else { @@ -634,10 +623,7 @@ impl ComptimeClap { }) } - // Zig `deinit` only freed `multi_options[*]` and `pos` (not `passthrough_positionals` — - // likely a leak in the deprecated Zig). All are owned here, so `Drop` handles it; - // body deleted per PORTING.md §Idiom map (`pub fn deinit` → `impl Drop`, empty body - // when it only frees owned fields). + // All fields are owned, so `Drop` handles cleanup; no explicit deinit needed. #[inline] pub fn flag(&self, name: &[u8]) -> bool { @@ -683,7 +669,7 @@ impl ComptimeClap { } /// Direct slot accessors — pair with [`find_param_index`] inside - /// `const { }` for true Zig-parity zero-cost lookup at the call site. + /// `const { }` for zero-cost lookup at the call site. #[inline] pub fn flag_at(&self, converted_idx: usize) -> bool { self.flags[self.table.converted[converted_idx].id] @@ -705,8 +691,7 @@ impl ComptimeClap { &self.passthrough_positionals } - /// Zig `hasFlag` is a comptime-only predicate over the captured table. - /// `const fn` here so `const { has_flag(PARAMS, b"--foo") }` folds. + /// `const fn` so `const { has_flag(PARAMS, b"--foo") }` folds. pub const fn has_flag(params: &[Param], name: &[u8]) -> bool { let mut i = 0; while i < params.len() { @@ -743,5 +728,3 @@ impl ComptimeClap { self.table.find(name) } } - -// ported from: src/clap/comptime.zig diff --git a/src/clap/lib.rs b/src/clap/lib.rs index edff37e596a0..768234df2d61 100644 --- a/src/clap/lib.rs +++ b/src/clap/lib.rs @@ -19,8 +19,7 @@ pub use streaming::StreamingClap; pub use bun_clap_macros::{__parse_param_impl, __parse_params_impl}; /// Parse a single param spec string (e.g. `"-h, --help Display this help"`) -/// into a const `Param` literal at compile time. This is the Rust -/// equivalent of Zig's comptime `clap.parseParam(...) catch unreachable`. +/// into a const `Param` literal at compile time. /// /// The argument **must** be a string literal; parse errors surface as compile /// errors at the call site. @@ -31,8 +30,7 @@ macro_rules! parse_param { }; } -/// Alias for [`parse_param!`] matching the Zig call-site spelling -/// (`clap.parseParam` → `clap::param!`). +/// Alias for [`parse_param!`]. #[macro_export] macro_rules! param { ($lit:literal $(,)?) => { @@ -40,8 +38,8 @@ macro_rules! param { }; } -/// Const-time `Param` slice concatenation — the Rust analogue of Zig's -/// comptime `a ++ b ++ c` over param tables. Produces a `&'static [Param]` +/// Const-time `Param` slice concatenation +/// over param tables. Produces a `&'static [Param]` /// baked into rodata; no `LazyLock`, no heap, no init closure in `.text`. /// /// Every `$part` must be a `const`-evaluable `&[Param]` (a `const` item or @@ -57,8 +55,7 @@ macro_rules! concat_params { } /// Build a `&'static ConvertedTable` from a const-evaluable -/// `&[Param]` at compile time — the Rust analogue of Zig's -/// `ComptimeClap(Id, params)` type-generator. The converted `[Param; N]` +/// `&[Param]` at compile time. The converted `[Param; N]` /// array, the three category counts, the short-name index, *and* the sorted /// long-name hash index all land in rodata, so [`parse_with_table`] does no /// heap allocation, no sorting, no locking, and `args.flag(b"--foo")` @@ -206,7 +203,7 @@ pub struct Names { } impl Names { - /// `.{ .short = c }` + /// A name with only a short flag (`-c`). #[inline] pub const fn short(c: u8) -> Self { Self { @@ -216,7 +213,7 @@ impl Names { } } - /// `.{ .long = name }` + /// A name with only a long flag (`--name`). #[inline] pub const fn long(name: &'static [u8]) -> Self { Self { @@ -245,7 +242,6 @@ impl Names { /// long, or any long alias. Shared predicate for `has_flag`/`find_param`. pub fn matches(&self, name: &[u8]) -> bool { if let Some(s) = self.short { - // Zig: mem.eql(u8, name, "-" ++ [_]u8{s}) if name.len() == 2 && name[0] == b'-' && name[1] == s { return true; } @@ -298,8 +294,6 @@ pub struct Param { impl Default for Param { fn default() -> Self { - // SAFETY note: Zig used `std.mem.zeroes(Id)` / `std.mem.zeroes(Names)`. - // We require `Id: Default` instead — same effect for the `Help` payload. Self { id: Id::default(), names: Names::default(), @@ -328,8 +322,8 @@ fn expect_param(expect: Param, actual: Param) { } /// Optional diagnostics used for reporting useful errors -// Zig `Diagnostic` borrows `arg`/`name.long` from the arg iterator. Rust can't tie -// that lifetime through `&mut Diagnostic` without invariance headaches, and this is +// Borrowing `arg`/`name.long` from the arg iterator would tie +// that lifetime through `&mut Diagnostic` with invariance headaches, and this is // an error-path-only struct, so it owns its bytes instead. The `name: Names` field // is flattened to `short`/`long` because `Names.long` is `&'static`. #[derive(Default)] @@ -393,8 +387,8 @@ impl Diagnostic { #[derive(Clone, Copy)] pub struct Help { /// The description text exactly as written in the param spec — may still - /// contain `` colour markup. Used by [`help`]/[`help_ex`], which (like - /// Zig's `clap.help`) emit it verbatim, and as the source for the ANSI form + /// contain `` colour markup. Used by [`help`]/[`help_ex`], which + /// emit it verbatim, and as the source for the ANSI form /// built lazily by [`pretty_help_desc`] on the `bun --help` colour path. pub msg: &'static [u8], /// `msg` with `` colour markup stripped — the non-TTY / piped help form. @@ -441,9 +435,8 @@ fn get_help_simple(param: &Param) -> &'static [u8] { /// output is actually requested. That ``→ANSI rewrite only ever runs on /// `bun --help` / `bun run --help`; `--print` and ordinary runs never reach this /// path, so they pay neither the per-invocation reparse nor the extra rodata a -/// baked-in `msg_ansi` array would cost on every flag and subcommand. (Zig did -/// the rewrite at `comptime` via `Output.prettyFmt` inside -/// `clap.simpleHelpBunTopLevel`; the colour case is rare enough that doing it +/// baked-in `msg_ansi` array would cost on every flag and subcommand. (The +/// colour case is rare enough that doing the rewrite /// lazily at runtime is the better trade for binary size.) #[cold] #[inline(never)] @@ -462,8 +455,6 @@ fn get_value_simple(param: &Param) -> &'static [u8] { param.id.value } -// Zig's `comptime params: []const Param(Id)` type parameter has no stable-Rust -// equivalent; `params` is carried at runtime instead. pub struct Args { pub clap: ComptimeClap, pub exe_arg: Option<&'static [u8]>, @@ -645,7 +636,7 @@ where } /// Shared by `print_param` and `usage_full`: emit the ` ` / ` ?` / -/// ` ...` suffix for a param's `takes_value`. Mirrors clap.zig:459/672. +/// ` ...` suffix for a param's `takes_value`. #[cold] #[inline(never)] fn write_takes_value_suffix( @@ -802,10 +793,7 @@ pub fn simple_help(params: &[Param]) { let spaces_after = vec![b' '; num_spaces_after]; simple_print_param(param).expect("unreachable"); - // Zig's `Output.pretty(" {s} {s}", …)` (clap.zig:567) only runs prettyFmt - // over the comptime template, so `` markers inside `desc_text` leak - // through verbatim there. That is observably wrong (`bun run --help` prints - // literal `$cwd`); `pretty_help_desc` resolves the `` markup + // `pretty_help_desc` resolves the `` markup // (ANSI on a colour TTY, stripped otherwise) so `--help` output is // tag-clean regardless of which helper a command uses. let desc = pretty_help_desc(param); @@ -820,21 +808,18 @@ pub fn simple_help(params: &[Param]) { #[cold] #[inline(never)] pub fn simple_help_bun_top_level(params: &[Param]) { - // Zig evaluated `computed_max_spacing` at `comptime` and emitted - // `@compileError` on overflow; that is not const-evaluable in Rust over a + // `computed_max_spacing` is not const-evaluable over a // slice param, so the overflow check is a runtime debug_assert below. const MAX_SPACING: usize = 30; const SPACE_BUF: &[u8; MAX_SPACING] = b" "; let computed_max_spacing: usize = compute_max_help_spacing(params); - // Zig: @compileError; here a debug-time assert. debug_assert!( computed_max_spacing <= MAX_SPACING, "a parameter is too long to be nicely printed in `bun --help`" ); - // PERF(port): was `inline for` + comptime string concat — profile if hot. for param in params { if !(param.names.short.is_none() && param.names.long.is_none()) { let desc_text = get_help_simple(param); @@ -844,9 +829,7 @@ pub fn simple_help_bun_top_level(params: &[Param]) { let total_len = param_display_width(param); let num_spaces_after = MAX_SPACING - total_len; - // Zig: Output.pretty(space_buf[0..n] ++ desc_text, .{}) — the concat - // is the *format string*, so `` markers inside `desc_text` are - // rewritten at `comptime`. Mirror that via `pretty_help_desc`, which + // `pretty_help_desc` // resolves the markup (ANSI on a colour TTY, stripped otherwise). let desc = pretty_help_desc(param); bun_core::pretty!( @@ -894,8 +877,7 @@ where } if cos.count == 0 { - // Zig wrote "[-" to `stream` (not `cs`), bypassing the counter. - // Preserving that quirk by writing to the inner writer. + // "[-" goes to the inner writer (not `cs`), bypassing the counter. write!(cos.inner(), "[-")?; } cos.write_char(name as char)?; @@ -916,8 +898,7 @@ where b"--" }; - // Zig had a workaround `@as([*]const u8, @ptrCast(s))[0..1]` for taking - // a 1-byte slice of the short char. Rust expresses this as a 1-elem array. + // A 1-elem array gives a 1-byte slice of the short char. let short_buf; let name: &[u8] = if let Some(s) = param.names.short { short_buf = [s]; @@ -1172,7 +1153,7 @@ mod tests { assert_eq!(MACRO_PARAMS[1].takes_value, Values::OneOptional); assert_eq!(MACRO_PARAMS[1].id.value, b"STR"); - // Aliases — proc-macro restores the comptime alias array the runtime parser drops. + // Aliases — the proc-macro restores the static alias array the runtime parser drops. assert_eq!( MACRO_PARAMS[2].names.long, Some(b"test-name-pattern" as &[u8]) @@ -1213,5 +1194,3 @@ mod tests { assert_eq!(CT_TABLE.converted[CT_CONFIG_IDX].takes_value, Values::One); } } - -// ported from: src/clap/clap.zig diff --git a/src/clap/streaming.rs b/src/clap/streaming.rs index 24aa2beb03e9..28c69bd3b680 100644 --- a/src/clap/streaming.rs +++ b/src/clap/streaming.rs @@ -65,7 +65,7 @@ pub struct StreamingClap<'p, 'a, Id, ArgIterator> { pub diagnostic: Option<&'p mut clap::Diagnostic>, } -// ArgIterator was a comptime duck-typed param in Zig; expressed here as the +// ArgIterator is the // `args::ArgIter<'a>` trait so `next()`/`remain()` resolve. impl<'p, 'a, Id, ArgIterator> StreamingClap<'p, 'a, Id, ArgIterator> where @@ -225,7 +225,7 @@ where } // Before we return, we have to set the new state of the clap. - // (Zig did this in a `defer`; hoisting it is equivalent — every path + // (Hoisting this above the returns is fine — every path // below returns, and nothing between here and those returns reads // `self.state`.) if arg.len() <= next_index || param.takes_value != clap::Values::None { @@ -332,7 +332,7 @@ where fn err(&mut self, arg: &[u8], short: Option, long: Option<&[u8]>, e: ArgError) -> ArgError { if let Some(d) = self.diagnostic.as_deref_mut() { - // Zig assigned borrowed `arg`/`name` slices; Rust `Diagnostic` owns + // `Diagnostic` owns // its bytes (error path only) — see lib.rs. d.arg = arg.to_vec(); d.short = short; @@ -400,10 +400,9 @@ mod tests { Ok(None) => break, Err(_err) => { // Bun's `Diagnostic::report` deliberately ignores its writer arg - // and routes through `bun_core::Output` (stderr) — exactly like - // the Zig original (`report(diag, _: anytype, ...)` in clap.zig), - // so the rendered message can't be captured here (and never could - // be in the Zig tests either). Instead, rebuild the flag name the + // and routes through `bun_core::Output` (stderr), + // so the rendered message can't be captured here. + // Instead, rebuild the flag name the // same way `report` does from the diagnostic fields and assert // the expected message names it in quotes. let mut name_buf = [0u8; 1024]; @@ -815,5 +814,3 @@ mod tests { ); } } - -// ported from: src/clap/streaming.zig diff --git a/src/clap_macros/lib.rs b/src/clap_macros/lib.rs index a63db6707fe7..0969a83031f5 100644 --- a/src/clap_macros/lib.rs +++ b/src/clap_macros/lib.rs @@ -1,8 +1,8 @@ //! Proc-macro backend for `bun_clap::parse_param!` / `bun_clap::parse_params!`. //! -//! This is a 1:1 port of the comptime `parseParam` from `src/clap/clap.zig`, lifted into +//! Param-spec parsing implemented as //! a proc-macro so the resulting `Param` values are fully const and usable in -//! `static` tables (the Zig original ran at comptime via `@setEvalBranchQuota`). +//! `static` tables. //! //! Do **not** call these proc-macros directly — they take a leading crate-path argument //! (`$crate`) injected by the `macro_rules!` wrappers in `bun_clap`. Use @@ -76,10 +76,10 @@ impl ParseParamError { } // ───────────────────────────────────────────────────────────────────────────── -// Parser — direct port of clap.zig `parseParam` / `parseParamRest` / `parseLongNames` +// Parser — `parse_param` / `parse_param_rest` / `parse_long_names` // ───────────────────────────────────────────────────────────────────────────── -/// `std.mem.tokenizeAny` with a `.rest()` that skips leading delimiters. +/// Delimiter tokenizer with a `.rest()` accessor that skips leading delimiters. struct TokenizeAny<'a> { buf: &'a [u8], index: usize, @@ -272,8 +272,6 @@ fn byte_str_b(s: &[u8]) -> LitByteStr { /// in rodata. The ANSI form is *not* baked in — it is rare (only `bun --help` on /// a colour TTY) and would otherwise roughly triple the help-string rodata, so /// `bun_clap::pretty_help_desc` derives it from `Help::msg` on demand instead. -/// (Zig did the equivalent rewrite at `comptime` via `Output.prettyFmt` inside -/// `clap.simpleHelpBunTopLevel`.) fn pretty_rewrite(fmt: &[u8], is_enabled: bool) -> Vec { use bun_output_tags::{RESET, color_for_bytes}; let mut out: Vec = Vec::with_capacity(fmt.len() * 2); @@ -322,9 +320,8 @@ fn pretty_rewrite(fmt: &[u8], is_enabled: bool) -> Vec { is_reset = true; "" } else { - // Unknown tag: Zig's comptime `prettyFmt` would `@compileError` - // here, but `pretty_fmt_runtime` (the path this replaces) drops - // it silently and so does Zig's actual `clap.simpleHelp`. Match + // Unknown tag: `pretty_fmt_runtime` (the path this replaces) + // drops it silently. Match // the lenient runtime behaviour — a compile error would be // stricter than what shipped, and param specs don't carry // unknown tags anyway. diff --git a/src/codegen/bindgen-lib-internal.ts b/src/codegen/bindgen-lib-internal.ts index db0e6fb6abc5..373bdbb27a5d 100644 --- a/src/codegen/bindgen-lib-internal.ts +++ b/src/codegen/bindgen-lib-internal.ts @@ -23,7 +23,7 @@ export let structHashToSelf = new Map(); export const str = (v: any) => JSON.stringify(v); /** Capitalize */ export const cap = (s: string) => s[0].toUpperCase() + s.slice(1); -/** Escape a Zig Identifier */ +/** Escape an identifier */ export const zid = (s: string) => (s.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/) ? s : "@" + str(s)); /** Snake Case */ export const snake = (s: string) => @@ -39,7 +39,7 @@ export const camel = (s: string) => /** Pascal Case */ export const pascal = (s: string) => cap(camel(s)); -// Return symbol names of extern values (must be equivalent between C++ and Zig) +// Return symbol names of extern values (must be equivalent on both sides of the FFI boundary) /** The JS Host function, aka fn (*JSC.JSGlobalObject, *JSC.CallFrame) JSValue.MaybeException */ export const extJsFunction = (namespaceVar: string, fnLabel: string) => @@ -779,7 +779,7 @@ export type ArgStrategyChildItem = * In addition to moving a payload over, an additional bit of information * crosses the ABI boundary indicating if the function threw an exception. * - * For simplicity, the possibility of any Zig binding returning an error/calling + * For simplicity, the possibility of any native binding returning an error/calling * `throw` is assumed and there isnt a way to disable the exception check. */ export type ReturnStrategy = diff --git a/src/codegen/bindgen-lib.ts b/src/codegen/bindgen-lib.ts index 37a7d6cddb32..40edfd3d52f5 100644 --- a/src/codegen/bindgen-lib.ts +++ b/src/codegen/bindgen-lib.ts @@ -212,7 +212,6 @@ export namespace t { * ``` * * ```zig - * // foo.zig * pub fn foo(bar: []const u8) void { * // ... * } @@ -288,7 +287,7 @@ export namespace t { } /** - * Equivalent to `stringEnum`, but using an enum sourced from the given Zig + * Equivalent to `stringEnum`, but using an enum sourced from the given * file. Use this to get an enum type that can have functions added. */ export function zigEnum(file: string, impl: string): Type { @@ -322,7 +321,6 @@ interface FuncOptionsWithVariant extends FuncMetadata { * ``` * * ```zig - * // foo.zig * pub fn foo1(a: i32) i32 { * return a; * } diff --git a/src/codegen/bindgen.ts b/src/codegen/bindgen.ts index e98e23ff8ea6..2584f2a38083 100644 --- a/src/codegen/bindgen.ts +++ b/src/codegen/bindgen.ts @@ -1,7 +1,7 @@ // The binding generator to rule them all. -// Converts binding definition files (.bind.ts) into C++ and Zig code. +// Converts binding definition files (.bind.ts) into C++ and native binding code. // -// Generated bindings are available in `bun.generated..*` in Zig, +// Generated bindings are available as `bun.generated..*` on the native side, // or `Generated::::*` in C++ from including `Generated.h`. import assert from "node:assert"; import fs from "node:fs"; @@ -60,8 +60,8 @@ function resolveVariantStrategies(vari: Variant, name: string) { const abiType = !isNullable && arg.type.canDirectlyMapToCAbi(); if (abiType) { arg.loweringStrategy = { - // This does not work in release builds, possibly due to a Zig 0.13 bug - // regarding by-value extern structs in C functions. + // Passing by-value extern structs in C functions did not work + // reliably in release builds. // type: cAbiTypeInfo(abiType)[0] > 8 ? "c-abi-pointer" : "c-abi-value", // Always pass an argument by-pointer for now. type: abiType === "*anyopaque" || abiType === "*JSGlobalObject" ? "c-abi-value" : "c-abi-pointer", @@ -1376,7 +1376,7 @@ for (const [filename, { functions, typedefs }] of files) { cpp.line(`}`); cpp.line(); - // Generated Zig dispatch functions + // Generated dispatch functions variNum = 1; for (const vari of fn.variants) { const dispatchName = extDispatchVariant(namespaceVar, fn.name, variNum); diff --git a/src/codegen/bindgenv2/internal/base.ts b/src/codegen/bindgenv2/internal/base.ts index 4162f711ae11..93732f3fbd58 100644 --- a/src/codegen/bindgenv2/internal/base.ts +++ b/src/codegen/bindgenv2/internal/base.ts @@ -27,7 +27,7 @@ export abstract class Type { return this.bindgenType + ".ZigType"; } - /** This must be overridden if bindgen.zig defines a custom `OptionalZigType`. */ + /** This must be overridden if a custom `OptionalZigType` is defined. */ optionalZigType(style?: CodeStyle): string { return `?${this.zigType(style)}`; } diff --git a/src/codegen/bindgenv2/internal/dictionary.ts b/src/codegen/bindgenv2/internal/dictionary.ts index e2a7030c0180..47598e9e50c0 100644 --- a/src/codegen/bindgenv2/internal/dictionary.ts +++ b/src/codegen/bindgenv2/internal/dictionary.ts @@ -19,7 +19,7 @@ export interface DictionaryMember { type: Type; /** Optional default value to use when this member is missing or undefined. */ default?: any; - /** The name used in generated Zig/C++ code. Defaults to the public JS name. */ + /** The name used in generated code. Defaults to the public JS name. */ internalName?: string; /** Alternative JavaScript names for this member. */ altNames?: string[]; @@ -39,7 +39,7 @@ interface DictionaryOptions { name: string; /** Used in error messages. Defaults to `name`. */ userFacingName?: string; - /** Whether to generate a Zig `fromJS` function. */ + /** Whether to generate a `fromJS` conversion function. */ generateConversionFunction?: boolean; } diff --git a/src/codegen/bindgenv2/internal/union.ts b/src/codegen/bindgenv2/internal/union.ts index e452f8b1f08a..737f8c4f6f59 100644 --- a/src/codegen/bindgenv2/internal/union.ts +++ b/src/codegen/bindgenv2/internal/union.ts @@ -31,7 +31,7 @@ export function union(name: string, alternatives: NamedAlternatives): NamedUnion /** * The order of types in this union is significant. Each type is tried in order, and the first one - * that successfully converts determines the active field in the corresponding Zig tagged union. + * that successfully converts determines the active field in the corresponding native tagged union. * * This means that it is an error to specify `RawAny` or `StrongAny` as anything other than the * last alternative, as conversion to any subsequent types would never be attempted. diff --git a/src/codegen/class-definitions.ts b/src/codegen/class-definitions.ts index 8049e12e5aec..86827d34c65b 100644 --- a/src/codegen/class-definitions.ts +++ b/src/codegen/class-definitions.ts @@ -140,7 +140,7 @@ export class ClassDefinition { /** * Class constructor needs `this` value. * - * Makes the code generator call the Zig constructor function **after** the + * Makes the code generator call the native constructor function **after** the * JSValue is instantiated. Only use this if you must, as it probably isn't * good for GC since it means if the constructor throws the GC will have to * clean up the object that never reached JS. diff --git a/src/codegen/cppbind.ts b/src/codegen/cppbind.ts index 224277697ba8..f5b3f479d780 100644 --- a/src/codegen/cppbind.ts +++ b/src/codegen/cppbind.ts @@ -1,8 +1,8 @@ /* -cppbind - C++ to Zig binding generator for Bun +cppbind - C++ binding generator for Bun -This tool automatically generates Zig bindings for C++ functions marked with [[ZIG_EXPORT(...)]] attributes. +This tool automatically generates Rust bindings for C++ functions marked with [[ZIG_EXPORT(...)]] attributes. It runs automatically when C++ files change during the build process. To run manually: @@ -18,7 +18,7 @@ To run manually: printf("hello world\n"); } ``` - Zig usage: `bun.cpp.hello_world();` + Rust usage: `bun_jsc::cpp::hello_world();` 2. **zero_is_throw** - Function returns JSValue, where .zero indicates an exception: ```cpp @@ -29,7 +29,7 @@ To run manually: return result; } ``` - Zig usage: `try bun.cpp.create_object(globalThis);` + Rust usage: `bun_jsc::cpp::create_object(global_this)?;` 3. **check_slow** - Function that may throw, performs runtime exception checking: ```cpp @@ -39,7 +39,7 @@ To run manually: RETURN_IF_EXCEPTION(scope, ); } ``` - Zig usage: `try bun.cpp.process_data(globalThis);` + Rust usage: `bun_jsc::cpp::process_data(global_this)?;` ### Parameters @@ -401,7 +401,7 @@ type ExportTag = "check_slow" | "zero_is_throw" | "false_is_throw" | "null_is_th // ─────────────────────────── Rust output (cpp.rs) ─────────────────────────── // -// Mirror of `generateZigFn` for the Rust port: each `[[ZIG_EXPORT(mode)]]` C++ +// Each `[[ZIG_EXPORT(mode)]]` C++ // function gets a typed `pub fn` in `bun_jsc::cpp` that wraps the raw extern in // the appropriate exception scope and converts to `JsResult`. The wrapper opens // the scope *before* calling into C++ so the callee's `DECLARE_THROW_SCOPE` dtor @@ -421,7 +421,7 @@ const rustSharedTypes: Record = { // Primitives "bool": "bool", // `char` signedness is platform-dependent (signed on x86_64-linux/windows, - // unsigned on aarch64); match Zig's `c_char` so a future by-value return + // unsigned on aarch64); use `core::ffi::c_char` so a future by-value return // doesn't silently sign-flip. "char": "core::ffi::c_char", "unsigned char": "u8", @@ -560,7 +560,7 @@ function generateRustType(type: CppType, parent: CppType | null): string { // Unknown opaque — only valid behind a pointer (the per-type shim casts the // pointee). Behind a pointer we degrade to c_void; in by-value position that // would emit `-> core::ffi::c_void` (a ZST in Rust → silent ABI corruption), - // so match the Zig generator and fail loudly at the C++ source location. + // so fail loudly at the C++ source location. if (parent?.type === "pointer") return "core::ffi::c_void"; throwError( type.position, @@ -661,7 +661,7 @@ function generateRustFn(fn: CppFn, rustRaw: string[], rustWrap: string[]): void const globalArg = fn.parameters.find(p => isGlobalObjectPtr(p.type)); if (!globalArg) { - // Same constraint as the Zig generator; emit a stub so the module still + // Emit a stub so the module still // compiles and the symbol name is greppable. rustWrap.push(`// skipped ${fn.name}: ${fn.tag} requires a JSGlobalObject* parameter`); return; @@ -672,8 +672,8 @@ function generateRustFn(fn: CppFn, rustRaw: string[], rustWrap: string[]): void // Inline the `top_scope!` body (rather than the `call_check_slow` *function* form, // which routes `SourceLocation::from_caller()` → thread-local intern probe per call // in debug builds). This is the highest-volume mode — keep it as cheap as the - // zero/false/null arms below. `src!()` resolves to the wrapper file/line, matching - // Zig's `cpp.zig` (`@src()` inside the wrapper); `#[track_caller]` would be a no-op + // zero/false/null arms below. `src!()` resolves to the wrapper file/line; + // `#[track_caller]` would be a no-op // against a syntactic `file!()`, so don't emit it. rustWrap.push( `#[inline]`, @@ -709,7 +709,7 @@ function generateRustFn(fn: CppFn, rustRaw: string[], rustWrap: string[]): void } else assertNever(fn.tag); // `validation_scope!` expands `src!()` syntactically (resolves to this generated - // file/line — parity with Zig's `@src()` inside cpp.zig wrappers). `#[track_caller]` + // file/line). `#[track_caller]` // can't influence a compile-time `file!()`, so don't emit it. rustWrap.push( `#[inline]`, diff --git a/src/codegen/generate-classes.ts b/src/codegen/generate-classes.ts index 09ee612b6eaa..b659bb334bf0 100644 --- a/src/codegen/generate-classes.ts +++ b/src/codegen/generate-classes.ts @@ -2740,7 +2740,7 @@ const rustModuleResolver = (() => { // `pub use bun_jsc::{BuildMessage, ResolveMessage};` so route there. return `crate::api::${name}`; }, - /** Resolve an absolute `.rs` (or `.zig`) file path to its `crate::…` module path. */ + /** Resolve an absolute `.rs` file path to its `crate::…` module path. */ resolveFile(absRs: string): string | null { return fileToMod.get(path.resolve(absRs)) ?? null; }, @@ -2844,8 +2844,7 @@ function generateRust( // ── exported #[no_mangle] thunks ───────────────────────────────────────── // Direct dispatch: each thunk calls an *inherent* method on the user's real // Rust struct (re-exported as `${typeName}` below). No trait, no opaque - // placeholder, no `unimplemented!()` — a missing method is a compile error, - // mirroring the Zig path's `@import("…").${T}.${fn}` behaviour. + // placeholder, no `unimplemented!()` — a missing method is a compile error. const thunks: string[] = []; const symbols: string[] = []; function thunk(sym: string, sig: string, body: string) { @@ -3071,9 +3070,9 @@ function generateRust( } // ── C++→Rust extern imports + safe wrappers ────────────────────────────── - // Emitted as free functions in a per-class `js_${T}` sub-module (mirrors - // Zig's `pub const js = jsc.Codegen.JS${T};`) so they don't collide with - // inherent `from_js`/`to_js` already defined on the real struct. + // Emitted as free functions in a per-class `js_${T}` sub-module so they + // don't collide with inherent `from_js`/`to_js` already defined on the + // real struct. const cachedExterns = gc_fields .map( ([name]) => @@ -3153,9 +3152,8 @@ ${gcAccessors} // ════════════════════════════════════════════════════════════════════════════ /// Native backing type for \`JS${typeName}.m_ctx\`. Re-export of the real -/// hand-ported struct so the thunks below call its inherent methods directly -/// (mirrors Zig's \`@import("…").${typeName}.\`). A missing method is a -/// compile error — fix it in \`${rustPath}\`, not here. +/// struct so the thunks below call its inherent methods directly. A missing +/// method is a compile error — fix it in \`${rustPath}\`, not here. pub use ${rustPath} as ${typeName}; ${thunks.join("\n\n")} @@ -3546,8 +3544,8 @@ function writeCppSerializers() { // ── Rust output: per-class thunks for `lang === "rust"` (default). ───────── // Zig output is still emitted for every class so the C++ side's `extern` -// declarations stay satisfied during incremental migration; the Zig thunks for -// rust-lang classes simply go unreferenced once the Rust crate links. +// declarations stay satisfied; the Zig thunks for rust-lang classes simply go +// unreferenced once the Rust crate links. { const rustClasses = classes.filter(a => (a.lang ?? "rust") === "rust"); let totalSyms = 0; @@ -3753,8 +3751,8 @@ if (!process.env.ONLY_ZIG) { } /** - * Generates a basic TypeScript type signature string and corresponding Zig source comment - * for a given property definition. + * Generates a basic TypeScript type signature string for a given property + * definition. * Returns null if the property should not be included in the types (e.g., private). */ function getPropertySignatureWithComment( diff --git a/src/codegen/generate-host-exports.ts b/src/codegen/generate-host-exports.ts index 2529465c25e7..05cae8b8c8dd 100644 --- a/src/codegen/generate-host-exports.ts +++ b/src/codegen/generate-host-exports.ts @@ -379,7 +379,7 @@ function emitThunk(e: Export): string { const loc = `${path.relative(repoRoot, e.file)}:${e.line}`; // `JsResult` impls need `to_js_host_call` (exception-scope assert + // panic barrier + Err→empty mapping). Plain-`JSValue` impls are bare - // `callconv(jsc.conv)` bodies in the .zig spec — wrap them and you trip + // host-call-ABI bodies (no exception-scope wrapper) — wrap them and you trip // `assert_exception_presence_matches(false)` whenever the body legitimately // leaves an exception pending while returning non-empty (e.g. // `Bun__drainMicrotasksFromJS`). Match `#[bun_jsc::host_call]`: deref + call, diff --git a/src/codegen/generate-js2native.ts b/src/codegen/generate-js2native.ts index 0a640d1b6443..86bfed725774 100644 --- a/src/codegen/generate-js2native.ts +++ b/src/codegen/generate-js2native.ts @@ -254,7 +254,7 @@ export function getJS2NativeZig(gs2NativeZigPath: string) { // the C++ side declares in GeneratedJS2Native.h. The C++ output is invariant; // only the implementer of the symbol changes. // -// Two ABI shapes (mirroring the Zig output exactly): +// Two ABI shapes: // • nativeCalls (type "zig") → `${sym}_workaround(global) -> JSValue` // • wrapperCalls (type "zig") → `${sym}(global, callframe) -> JSValue` // diff --git a/src/codegen/generate-jssink.ts b/src/codegen/generate-jssink.ts index fc9e87f47457..c047c862e435 100644 --- a/src/codegen/generate-jssink.ts +++ b/src/codegen/generate-jssink.ts @@ -1033,16 +1033,14 @@ extern "C" void ${name}__onClose(JSC::EncodedJSValue controllerValue, JSC::Encod // `BUN_DECLARE_HOST_FUNCTION(${name}__{construct,write,end,flush,start})` plus // the two non-host-fn externs `${name}__getInternalFd` / `${name}__memoryCost`. // Each thunk calls an inherent method on the real sink struct in -// `crate::webcore` (mirroring Zig's `@import("…").${name}.`); a missing -// method is a compile error. +// `crate::webcore`; a missing method is a compile error. // // Calling convention: `BUN_DECLARE_HOST_FUNCTION` and `endWithSink` use // `SYSV_ABI` (= `extern "sysv64"` on win-x64, `"C"` elsewhere) — wrapped in // `bun_jsc::jsc_host_abi!`. The remaining `ZIG_DECL` / plain `extern "C"` // symbols (finalize/close/updateRef/getInternalFd/memoryCost) stay `extern "C"`. function rustSink() { - // All sink structs live (or will live) under `crate::webcore`; the Zig - // originals are `src/runtime/webcore/streams.zig::${name}`. + // All sink structs live (or will live) under `crate::webcore`. const sinkPaths: Record = { ArrayBufferSink: "crate::webcore::array_buffer_sink::ArrayBufferSink", FileSink: "crate::webcore::file_sink::FileSink", diff --git a/src/collections/StaticHashMap.rs b/src/collections/StaticHashMap.rs index f5b48039c4d2..4769089f1c2f 100644 --- a/src/collections/StaticHashMap.rs +++ b/src/collections/StaticHashMap.rs @@ -7,7 +7,7 @@ use core::marker::PhantomData; use bun_alloc::AllocError; // ────────────────────────────────────────────────────────────────────────── -// Context trait — models Zig's `Context: type` with `.hash(k)` / `.eql(a, b)` +// Context trait — `.hash(k)` / `.eql(a, b)` // ────────────────────────────────────────────────────────────────────────── // Canonical definitions live in `crate::zig_hash_map`; re-exported here so the @@ -16,7 +16,7 @@ use bun_alloc::AllocError; pub use crate::zig_hash_map::{AutoHashContext as AutoContext, HashContext}; // ────────────────────────────────────────────────────────────────────────── -// Type aliases (Zig: AutoHashMap) +// Type aliases // ────────────────────────────────────────────────────────────────────────── pub type AutoHashMap = @@ -47,12 +47,11 @@ impl Entry { K: Copy + Default, V: Copy + Default, { - // Zig used `std.mem.zeroes(K)` / `undefined` — key/value of - // an empty entry (hash == EMPTY_HASH) are never read. Rust cannot use - // `mem::zeroed()` here: K may be `&[u8]` (or any `Copy` type with a - // niche), for which all-zero bytes violate the validity invariant - // regardless of whether the value is later read. Use `Default` for the - // unread placeholder instead. + // Key/value of an empty entry (hash == EMPTY_HASH) are never read. + // `mem::zeroed()` cannot be used here: K may be `&[u8]` (or any `Copy` + // type with a niche), for which all-zero bytes violate the validity + // invariant regardless of whether the value is later read. Use + // `Default` for the unread placeholder instead. Self { hash: EMPTY_HASH, key: K::default(), @@ -74,23 +73,20 @@ impl fmt::Display for Entry { pub use crate::hash_map::GetOrPutResult; // ────────────────────────────────────────────────────────────────────────── -// comptime helpers (Zig top-of-fn const expressions) +// const helpers // ────────────────────────────────────────────────────────────────────────── #[inline] const fn compute_shift(capacity: u64) -> u8 { - // Zig: 63 - math.log2_int(u64, capacity) + 1 (63 - capacity.ilog2() + 1) as u8 } #[inline] const fn compute_overflow(capacity: u64, shift: u8) -> u64 { - // Zig: capacity / 10 + (63 - @as(u64, shift) + 1) << 1 - // Zig precedence: `+` binds tighter than `<<`, so this is (a + b) << 1. (capacity / 10 + (63 - shift as u64 + 1)) << 1 } -/// Checked u64→usize narrowing for table indices (Zig indexes by u64 directly). +/// Checked u64→usize narrowing for table indices. #[inline] fn to_idx(x: u64) -> usize { usize::try_from(x).expect("int cast") @@ -121,7 +117,7 @@ pub struct StaticHashMap< > { pub entries: [Entry; SLOTS], pub len: usize, - /// Zig `u6`; stored as u8. + /// Hash shift; always < 64, stored as u8. pub shift: u8, // put_probe_count: usize, // get_probe_count: usize, @@ -177,7 +173,7 @@ impl Has pub struct HashMap { pub entries: Box<[Entry]>, pub len: usize, - /// Zig `u6`; stored as u8. + /// Hash shift; always < 64, stored as u8. pub shift: u8, // put_probe_count: usize, // get_probe_count: usize, @@ -220,7 +216,6 @@ impl< let overflow = compute_overflow(capacity, shift); let n = usize::try_from(capacity + overflow).expect("int cast"); - // Zig: gpa.alloc + @memset(.{}) let entries = vec![Entry::::empty(); n].into_boxed_slice(); Ok(Self { @@ -255,9 +250,6 @@ impl< let mut map = Self::init_capacity(capacity * 2)?; - // Zig walks raw `[*]Entry` pointers (`src`, `dst`, `end`); here we - // iterate by index over the old slice and index into the new boxed - // slice. let mut dst: usize = 0; let mut src: usize = 0; while src != end { @@ -268,7 +260,6 @@ impl< } else { 0 }; - // Zig: dst = if (@intFromPtr(p) >= @intFromPtr(dst)) p else dst; if i >= dst { dst = i; } @@ -278,7 +269,7 @@ impl< dst += 1; } - // Zig: self.deinit(gpa); — old Box drops on assignment below. + // Old Box drops on assignment below. self.entries = map.entries; self.shift = map.shift; Ok(()) @@ -312,8 +303,8 @@ impl< // HashMapMixin — shared method bodies for StaticHashMap & HashMap // ────────────────────────────────────────────────────────────────────────── -/// Mirrors Zig's `fn HashMapMixin(Self, K, V, Context) type`. Implementors -/// supply the backing storage; default methods provide the Robin-Hood logic. +/// Implementors supply the backing storage; default methods provide the +/// Robin-Hood logic. pub trait HashMapMixin { fn storage(&self) -> &[Entry]; fn storage_mut(&mut self) -> &mut [Entry]; @@ -329,11 +320,10 @@ pub trait HashMapMixin { *self.len_mut() = 0; } - /// Full backing slice (capacity + overflow). Matches Zig's `slice()`. + /// Full backing slice (capacity + overflow). fn slice(&mut self) -> &mut [Entry] { - // Zig recomputes `capacity + overflow` from `shift`; with Box<[T]>/[T; N] - // the storage already carries its exact length, so just return it. - // The assert is kept for parity with Zig's implicit invariant. + // The storage carries its exact length; the assert checks it stays + // consistent with the `shift`-derived `capacity + overflow` size. let capacity = 1u64 << (63 - self.shift() + 1); let overflow = compute_overflow(capacity, self.shift()); debug_assert_eq!( @@ -379,9 +369,8 @@ pub trait HashMapMixin { V: Copy + Default, Ctx: HashContext, { - // Zig left `value = undefined` (never read until the caller writes via - // `value_ptr`). Use `Default` for the placeholder — V may not be - // zero-valid. + // `value` is never read until the caller writes via `value_ptr`. Use + // `Default` for the placeholder — V may not be zero-valid. let mut it: Entry = Entry { hash: Ctx::ctx_hash(&key), key, @@ -552,16 +541,12 @@ pub trait HashMapMixin { mod tests { use super::*; - /// Zig's `std.rand.DefaultPrng`: xoshiro256++ with the state seeded by - /// splitmix64. Key sequences are byte-identical to the Zig tests, so the - /// key-only expectations (duplicate-free keys, len- and count-driven - /// shift progression) carry over exactly. Hash values do NOT carry over: - /// `AutoHashContext` routes through `bun_wyhash::auto_hash` (mum-mix), - /// not Zig's `Wyhash.hash(0, asBytes)`, so probe displacement is a fresh - /// draw. The 100%-load probe bound of the static test was re-validated - /// for this hash by exact simulation of all 128 seeds: max slot index - /// touched (incl. delete's `i + 1` backshift read) is 548 of 632, with - /// no 64-bit hash collisions among any seed's 512 keys. + /// xoshiro256++ with the state seeded by splitmix64. `AutoHashContext` + /// routes through `bun_wyhash::auto_hash` (mum-mix). The 100%-load probe + /// bound of the static test was validated for this hash by exact + /// simulation of all 128 seeds: max slot index touched (incl. delete's + /// `i + 1` backshift read) is 548 of 632, with no 64-bit hash collisions + /// among any seed's 512 keys. struct Xoshiro256PlusPlus { s: [u64; 4], } @@ -676,5 +661,3 @@ mod tests { } } } - -// ported from: src/collections/StaticHashMap.zig diff --git a/src/collections/array_hash_map.rs b/src/collections/array_hash_map.rs index b60b3ee92293..f7a8c3b29489 100644 --- a/src/collections/array_hash_map.rs +++ b/src/collections/array_hash_map.rs @@ -1,4 +1,4 @@ -//! Port of Zig's `std.ArrayHashMap` family + Bun's string-keyed wrappers +//! Insertion-ordered hash maps (`ArrayHashMap`) + Bun's string-keyed wrappers //! (`bun.StringArrayHashMap`, `bun.StringHashMap`, //! `bun.CaseInsensitiveASCIIStringArrayHashMap`, `bun.StringHashMapUnowned`). //! @@ -10,12 +10,11 @@ //! * `getOrPut` hands back a stable `key_ptr` / `value_ptr` / `index` triple //! so callers can fill the slot in-place after the lookup. //! -//! Zig builds a separate `index_header` (open-addressed `hash → entry_index` -//! table) once `len > 8` so lookups stay O(1). This port mirrors that with a -//! lazily-built `hashbrown::HashTable` keyed by the cached u32 hash: +//! A separate index — a lazily-built `hashbrown::HashTable` keyed by the +//! cached u32 hash — is built once `len > 8` so lookups stay O(1): //! linear scan below the threshold, indexed lookup above it. Point removals -//! (`pop`, `swap_remove`) patch the index in place (O(1), matching Zig's -//! `removeFromIndexByIndex`); wholesale permutations (`sort`, +//! (`pop`, `swap_remove`) patch the index in place (O(1)); +//! wholesale permutations (`sort`, //! `ordered_remove`) drop and immediately rebuild it so lookups never //! silently degrade to O(n). @@ -32,38 +31,36 @@ use core::ops::{Deref, DerefMut}; use bun_alloc::AllocError; // ────────────────────────────────────────────────────────────────────────── -// Free functions (Zig: `std.array_hash_map.hashString` / `std.hash_map.hashString`) +// Free functions // ────────────────────────────────────────────────────────────────────────── -/// `std.array_hash_map.hashString` — wyhash(seed=0) truncated to u32. +/// wyhash(seed=0) truncated to u32. #[inline] pub fn hash_string(s: &[u8]) -> u32 { - bun_wyhash::hash(s) as u32 // @truncate + bun_wyhash::hash(s) as u32 } // ────────────────────────────────────────────────────────────────────────── -// Context traits (Zig: `Context` / `Adapter` duck types) +// Context traits // ────────────────────────────────────────────────────────────────────────── -/// Hash/eql strategy for an `ArrayHashMap`. -/// Zig passes these as `anytype`; here it's a trait so the map can be generic -/// over the strategy without each method taking a `ctx` argument. +/// Hash/eql strategy for an `ArrayHashMap`. A trait so the map can be +/// generic over the strategy without each method taking a `ctx` argument. pub trait ArrayHashContext: Default { fn hash(&self, key: &K) -> u32; - /// `b_index` is the index of `b` in the entry array (Zig passes it so - /// adapted contexts can look at sibling storage). + /// `b_index` is the index of `b` in the entry array (so adapted contexts + /// can look at sibling storage). fn eql(&self, a: &K, b: &K, b_index: usize) -> bool; } /// Adapted lookup: hash a `Q` and compare it against the stored `K`s without -/// constructing a `K` first (Zig: `getOrPutAdapted` / `getOrPutContextAdapted`). +/// constructing a `K` first. pub trait ArrayHashAdapter { fn hash(&self, key: &Q) -> u32; fn eql(&self, a: &Q, b: &K, b_index: usize) -> bool; } -/// Default context: `Hash` + `Eq` driven through wyhash, mirroring Zig's -/// `AutoContext` / `getAutoHashFn`. +/// Default context: driven through `Hash` + `Eq`. #[derive(Default, Clone, Copy)] pub struct AutoContext; @@ -72,9 +69,8 @@ impl ArrayHashContext for AutoContext { fn hash(&self, key: &K) -> u32 { // Keys here are small POD (`Ref`, `u32`, indices). FxHash is a single // mul+rotate per word — measurably cheaper than wyhash's `mum` fold for - // 8-byte keys, and what rustc uses for the same workload shape. The Zig - // port used wyhash only because that's what `std.array_hash_map` defaults - // to; nothing persists these hashes across runs. + // 8-byte keys, and what rustc uses for the same workload shape; + // nothing persists these hashes across runs. use core::hash::Hasher; let mut h = rustc_hash::FxHasher::default(); key.hash(&mut h); @@ -86,7 +82,7 @@ impl ArrayHashContext for AutoContext { } } -/// `std.array_hash_map.StringContext` — byte-slice keys hashed with wyhash. +/// Byte-slice keys hashed with wyhash. #[derive(Default, Clone, Copy)] pub struct StringContext; @@ -101,8 +97,8 @@ impl ArrayHashContext<[u8]> for StringContext { } } -/// `bun.CaseInsensitiveASCIIStringContext` (src/bun.zig) — ASCII-lowercased -/// wyhash + ASCII-case-insensitive equality. Used for env-var maps on Windows. +/// ASCII-lowercased wyhash + ASCII-case-insensitive equality. Used for +/// env-var maps on Windows. #[derive(Default, Clone, Copy)] pub struct CaseInsensitiveAsciiStringContext; @@ -111,7 +107,8 @@ impl CaseInsensitiveAsciiStringContext { bun_wyhash::hash_ascii_lowercase(0, s) as u32 // @truncate } - /// `bun.CaseInsensitiveASCIIStringContext.pre` (src/bun.zig:1031). + /// Precompute the case-folded hash of `input` so repeated probes against + /// the same key skip the lowercasing pass. #[inline] pub fn pre(input: &[u8]) -> CaseInsensitiveAsciiPrehashed<'_> { CaseInsensitiveAsciiPrehashed { @@ -121,9 +118,8 @@ impl CaseInsensitiveAsciiStringContext { } } -/// `bun.CaseInsensitiveASCIIStringContext.Prehashed` (src/bun.zig:1035) — -/// caches the case-folded hash for `input` so repeated probes against the same -/// key skip the lowercasing pass. +/// Caches the case-folded hash for `input` so repeated probes against the +/// same key skip the lowercasing pass. pub struct CaseInsensitiveAsciiPrehashed<'a> { pub value: u32, pub input: &'a [u8], @@ -187,9 +183,8 @@ impl ArrayHashContext<[u8]> for CaseInsensitiveAsciiStringContext { // ────────────────────────────────────────────────────────────────────────── /// Result of `get_or_put*`. When `found_existing == false`, `*value_ptr` is a -/// freshly-defaulted slot the caller is expected to overwrite (Zig leaves it -/// `undefined`; Rust cannot, so the value type carries a `Default` bound on the -/// inserting paths). +/// freshly-defaulted slot the caller is expected to overwrite (the value type +/// carries a `Default` bound on the inserting paths). pub struct GetOrPutResult<'a, K, V> { pub found_existing: bool, pub index: usize, @@ -197,22 +192,19 @@ pub struct GetOrPutResult<'a, K, V> { pub value_ptr: &'a mut V, } -/// Zig: `std.ArrayHashMap.KV` — owned key/value pair returned by -/// `fetchSwapRemove` / `fetchOrderedRemove`. +/// Owned key/value pair returned by the `fetch_*_remove` methods. pub struct KV { pub key: K, pub value: V, } -/// Iterator entry — both halves mutable, matching Zig's `Entry { key_ptr: *K, -/// value_ptr: *V }`. +/// Iterator entry — both halves mutable. pub struct Entry<'a, K, V> { pub key_ptr: &'a mut K, pub value_ptr: &'a mut V, } -/// Insertion-order iterator yielding `Entry`. Resettable (Zig callers do -/// `it.reset()` to rewind; here `index = 0`). +/// Insertion-order iterator yielding `Entry`. Resettable via `reset()`. pub struct Iter<'a, K, V> { keys: *mut K, values: *mut V, @@ -263,10 +255,9 @@ pub trait ArrayHashMapExt { // ArrayHashMap // ────────────────────────────────────────────────────────────────────────── -/// Zig `index_header` threshold: at or below this many entries the -/// hash-prefiltered linear scan over `hashes` wins (the whole `Vec` fits -/// in one cache line); above it we build/maintain the SwissTable index. Same -/// `linear_scan_max` cut-off as `std/array_hash_map.zig`. +/// At or below this many entries the hash-prefiltered linear scan over +/// `hashes` wins (the whole `Vec` fits in one cache line); above it we +/// build/maintain the SwissTable index. const INDEX_THRESHOLD: usize = 8; /// Widen the cached `u32` entry hash to the `u64` hashbrown probes with. The @@ -328,8 +319,7 @@ fn index_insert_unique( /// [`index_insert_unique`] — keep the `reserve_rehash` monomorph in this crate /// rather than re-emitting it per `` instantiation. Called from the /// `reserve` / `ensure_*_capacity` paths so a caller that pre-sizes the map -/// (the Zig originals' `ensureTotalCapacityContext`, which also sizes the index -/// header) pays the SwissTable grow once instead of `O(log n)` times across the +/// pays the SwissTable grow once instead of `O(log n)` times across the /// following `push_entry` loop. #[inline(never)] fn index_reserve( @@ -448,8 +438,8 @@ pub struct ArrayHashMap { /// `INDEX_THRESHOLD` crossover. index: Option>, A>>, ctx: C, - // Zig `pointer_stability: std.debug.SafetyLock` — debug-only re-entrancy - // guard around operations that may invalidate entry pointers. `AtomicBool` + // Debug-only re-entrancy guard around operations that may invalidate + // entry pointers. `AtomicBool` // (not `Cell`) so the field doesn't strip `Sync` off the map in // debug builds — a debug-only diagnostic must not change the type's // auto-trait surface vs release (callers store maps in `static LazyLock`, @@ -465,7 +455,7 @@ impl Default for ArrayHashMap { } impl ArrayHashMap { - /// Zig `clone()` is fallible (OOM); kept as `Result` for API parity. + /// Fallible (OOM) clone; kept as `Result` for API stability. pub fn clone(&self) -> Result { Ok(Self { keys: self.keys.clone(), @@ -517,16 +507,14 @@ impl ArrayHashMap { self.keys.is_empty() } - /// Zig: `capacity()` — number of entries the backing storage can hold - /// without reallocating. + /// Number of entries the backing storage can hold without reallocating. #[inline] pub fn capacity(&self) -> usize { self.keys.capacity() } - /// Zig: `pop()` — remove and return the last entry in insertion order, or - /// `None` when empty. O(1); patches the index in place (Zig - /// `removeFromIndexByIndex`) so subsequent lookups stay O(1). + /// Remove and return the last entry in insertion order, or `None` when + /// empty. O(1); patches the index in place so subsequent lookups stay O(1). pub fn pop(&mut self) -> Option> { let key = self.keys.pop()?; // SAFETY: keys/values/hashes always share the same length. @@ -536,8 +524,8 @@ impl ArrayHashMap { Some(KV { key, value }) } - /// Zig: `clearAndFree(allocator)` — drop every entry and release the - /// backing allocations (capacity goes to zero). + /// Drop every entry and release the backing allocations (capacity goes to + /// zero). pub fn clear_and_free(&mut self) { self.keys = Vec::new_in(A::default()); self.values = Vec::new_in(A::default()); @@ -554,10 +542,9 @@ impl ArrayHashMap { Ok(()) } - /// Zig: `map.entries.len = n` after `ensureTotalCapacity(n)` — bulk-resize - /// the backing columns so callers can `keys_mut().copy_from_slice(...)` / - /// `values_mut().copy_from_slice(...)` and then `re_index()`. Mirrors the - /// pattern in `lockfile/bun.lockb.zig`'s `Serializer.load`. + /// Bulk-resize the backing columns so callers can + /// `keys_mut().copy_from_slice(...)` / `values_mut().copy_from_slice(...)` + /// and then `re_index()`. /// /// # Safety /// `n` must not exceed reserved capacity, and every element in @@ -569,7 +556,8 @@ impl ArrayHashMap { debug_assert!(n <= self.keys.capacity()); debug_assert!(n <= self.values.capacity()); debug_assert!(n <= self.hashes.capacity()); - // SAFETY: caller contract above; matches Zig `.entries.len = n`. + // SAFETY: caller contract above — `n` is within reserved capacity and + // the uninit window is filled before any read. unsafe { self.keys.set_len(n); self.values.set_len(n); @@ -579,11 +567,9 @@ impl ArrayHashMap { self.drop_index(); } - /// Zig `ensureTotalCapacityContext`: same as `ensure_total_capacity` but - /// takes an explicit `ctx` for the stored key type. This port maintains no - /// separate index header (lookup scans the cached `hashes` vec), so the - /// context is accepted and ignored — capacity reservation is purely a Vec - /// operation here. + /// Same as `ensure_total_capacity` but takes an explicit `ctx` for the + /// stored key type. Capacity reservation is purely a Vec operation here, + /// so the context is accepted and ignored. #[inline] pub fn ensure_total_capacity_context( &mut self, @@ -593,7 +579,7 @@ impl ArrayHashMap { self.ensure_total_capacity(n) } - /// Zig `putAssumeCapacityContext`: insert/replace using an externally-supplied + /// Insert/replace using an externally-supplied /// hash/eql context instead of the stored `C`. Used when `C = AutoContext` /// can't satisfy `K: Hash` (e.g. `bun_semver::String`, whose hash needs the /// owning `arg_buf`/`existing_buf`). Takes closures rather than an @@ -612,7 +598,6 @@ impl ArrayHashMap { self.values[i] = value; return; } - // PERF(port): was assume_capacity — Vec::push is amortized O(1) regardless. self.push_entry(key, value, h); } @@ -647,12 +632,12 @@ impl ArrayHashMap { } } - /// Zig: `shrinkAndFree(new_len)` — truncate to `new_len` entries (dropping - /// any tail) and release excess capacity. Insertion order is preserved, so - /// no rehash of the surviving prefix is needed. + /// Truncate to `new_len` entries (dropping any tail) and release excess + /// capacity. Insertion order is preserved, so no rehash of the surviving + /// prefix is needed. pub fn shrink_and_free(&mut self, new_len: usize) { - // Drop tail index slots first (Zig: removeFromIndexByIndex loop), so - // the surviving accelerator stays valid for O(1) lookups. + // Drop tail index slots first, so the surviving accelerator stays + // valid for O(1) lookups. if self.index.is_some() { for i in new_len..self.hashes.len() { let h = self.hashes[i]; @@ -739,9 +724,7 @@ impl ArrayHashMap { self.index = None; } - /// std-HashMap-compat alias for `clear_retaining_capacity`. Zig callers - /// frequently spell this `clearRetainingCapacity()`; ported call sites that - /// went through the std-alias path expect bare `clear()`. + /// std-HashMap-compat alias for `clear_retaining_capacity`. #[inline] pub fn clear(&mut self) { self.clear_retaining_capacity(); @@ -749,14 +732,13 @@ impl ArrayHashMap { /// std-HashMap-compat: shared iteration over `(key, value)` pairs in /// insertion order. Distinct from [`iterator`](Self::iterator) which yields - /// mutable `Entry { key_ptr, value_ptr }` (Zig shape) and requires - /// `&mut self`. + /// mutable `Entry { key_ptr, value_ptr }` and requires `&mut self`. #[inline] pub fn iter(&self) -> core::iter::Zip, core::slice::Iter<'_, V>> { self.keys.iter().zip(self.values.iter()) } - /// Zig `getIndexContext` for callers whose context is an inherent-method + /// Index lookup for callers whose context is an inherent-method /// struct (no `ArrayHashAdapter` impl). Takes the precomputed `u32` hash /// plus an `eql` closure so e.g. `bun_semver::String::ArrayHashContext` /// (which needs `arg_buf`/`existing_buf`) can drive a `&self` lookup. @@ -848,7 +830,7 @@ impl ArrayHashMap { } /// Remove the index slot pointing at `tail` (the just-popped last entry). - /// O(1); mirrors Zig `removeFromIndexByIndex` for the `pop`/`shrink` path. + /// O(1); used for the `pop`/`shrink` path. #[inline] fn index_remove_tail(&mut self, tail: usize, tail_hash: u32) { let Some(index) = self.index.as_deref_mut() else { @@ -862,7 +844,7 @@ impl ArrayHashMap { /// Patch the index after a `Vec::swap_remove(removed)`: drop the slot for /// `removed`, then retarget the slot that still says `old_last` (the /// pre-swap tail index, == `self.keys.len()` post-swap) to `removed`. - /// O(1); mirrors Zig `removeFromIndexByIndex` + `updateEntryIndex`. + /// O(1). #[inline] fn index_swap_remove(&mut self, removed: usize, removed_hash: u32) { let Some(index) = self.index.as_deref_mut() else { @@ -882,7 +864,7 @@ impl ArrayHashMap { } } - /// Zig `ArrayHashMap.sort` — stable in-place sort of keys/values/hashes by + /// Stable in-place sort of keys/values/hashes by /// a caller-supplied index comparator. The closure receives borrows of the /// key and value slices so it can compare on either without re-borrowing /// `self`. @@ -962,7 +944,7 @@ impl ArrayHashMap { Some((&mut self.keys[index], &mut self.values[index])) } - /// Zig `swapRemoveAt` — remove the entry at `index` by swapping in the last + /// Remove the entry at `index` by swapping in the last /// entry. O(1); does not preserve insertion order. Returns the removed pair. pub fn swap_remove_at(&mut self, index: usize) -> (K, V) { let k = self.keys.swap_remove(index); @@ -972,7 +954,7 @@ impl ArrayHashMap { (k, v) } - // ── adapted lookup (Zig: getAdapted / getIndexAdapted) ───────────────── + // ── adapted lookup ────────────────────────────────────────────────────── /// Look up by `key` using `adapter` for hash/eql, without constructing a `K`. #[inline] @@ -993,8 +975,7 @@ impl ArrayHashMap { .map(|i| &self.values[i]) } - /// Zig `getPtrContext` / `getPtrAdapted` — mutable value lookup using an - /// externally-supplied hash/eql adapter. + /// Mutable value lookup using an externally-supplied hash/eql adapter. #[inline] pub fn get_ptr_adapted(&mut self, key: &Q, adapter: &Ad) -> Option<&mut V> where @@ -1035,7 +1016,7 @@ impl, A: MapAllocator> ArrayHashMap { self.get_index(key).map(|i| &self.values[i]) } - /// Zig `getPtr` — mutable value lookup. + /// Mutable value lookup. pub fn get_ptr_mut(&mut self, key: &K) -> Option<&mut V> { let i = self.get_index(key)?; Some(&mut self.values[i]) @@ -1057,8 +1038,7 @@ impl, A: MapAllocator> ArrayHashMap { pub fn put(&mut self, key: K, value: V) -> Result<(), AllocError> { let h = self.ctx.hash(&key); if let Some(i) = self.find_hash(h, |k, idx| self.ctx.eql(&key, k, idx)) { - // Zig putContext (std/array_hash_map.zig:941): only assigns - // `result.value_ptr.*`; the original key is preserved. + // Only the value is assigned on hit; the original key is preserved. self.values[i] = value; } else { self.push_entry(key, value, h); @@ -1077,8 +1057,8 @@ impl, A: MapAllocator> ArrayHashMap { Ok(()) } - /// PERF(port): Zig skips the grow check; this port does too but `Vec::push` - /// will still reallocate if the caller lied about capacity. + /// PERF: skips the grow check, but `Vec::push` will still reallocate if + /// the caller lied about capacity. pub fn put_assume_capacity(&mut self, key: K, value: V) { let _ = self.put(key, value); } @@ -1087,7 +1067,7 @@ impl, A: MapAllocator> ArrayHashMap { pub fn insert(&mut self, key: K, value: V) -> Option { let h = self.ctx.hash(&key); if let Some(i) = self.find_hash(h, |k, idx| self.ctx.eql(&key, k, idx)) { - // std::HashMap::insert and Zig put: keep the original key on hit. + // Like std::HashMap::insert: keep the original key on hit. Some(core::mem::replace(&mut self.values[i], value)) } else { self.push_entry(key, value, h); @@ -1103,15 +1083,15 @@ impl, A: MapAllocator> ArrayHashMap { true } - /// Zig: `fetchSwapRemove` — swap-remove returning the removed `(K, V)` pair, - /// or `None` if `key` was not present. + /// Swap-remove returning the removed `(K, V)` pair, or `None` if `key` + /// was not present. pub fn fetch_swap_remove(&mut self, key: &K) -> Option<(K, V)> { let i = self.get_index(key)?; Some(self.swap_remove_at(i)) } - /// Zig: `orderedRemove` — preserves insertion order of remaining entries. - /// Returns `true` if the key was present (matching Zig's `bool` return). + /// Preserves insertion order of remaining entries. + /// Returns `true` if the key was present. #[inline] pub fn ordered_remove(&mut self, key: &K) -> bool { self.remove(key).is_some() @@ -1124,9 +1104,8 @@ impl, A: MapAllocator> ArrayHashMap { self.keys.remove(i); self.hashes.remove(i); // Ordered remove shifts every index ≥ i; rebuild rather than patching - // each slot. Immediate rebuild keeps subsequent lookups O(1) (Zig - // patches in place; this is the simpler-correct equivalent for the - // rare ordered path). + // each slot. Immediate rebuild keeps subsequent lookups O(1) on this + // rare path. self.drop_index(); if self.keys.len() > INDEX_THRESHOLD { self.rebuild_index(); @@ -1235,7 +1214,7 @@ impl<'a, K, V, C, A: MapAllocator> MapEntry<'a, K, V, C, A> { } impl, A: MapAllocator> ArrayHashMap { - /// Zig `getOrPut`: look up `key`; if absent, append it with a defaulted + /// Look up `key`; if absent, append it with a defaulted /// value slot and return `found_existing = false`. pub fn get_or_put(&mut self, key: K) -> Result, AllocError> { let h = self.ctx.hash(&key); @@ -1246,20 +1225,20 @@ impl, A: MapAllocator> ArrayHashMap GetOrPutResult<'_, K, V> { let h = self.ctx.hash(&key); if let Some(i) = self.find_hash(h, |k, idx| self.ctx.eql(&key, k, idx)) { return self.gop_at(i, true); } - // PERF(port): `push_within_capacity` is unstable; `push` is a no-grow + // PERF: `push_within_capacity` is unstable; `push` is a no-grow // when the prior `ensure_unused_capacity` reserved the slot. let i = self.push_entry(key, V::default(), h); self.gop_at(i, false) } - /// Zig `getOrPutValue`: like `get_or_put` but writes `value` when absent. + /// Like `get_or_put` but writes `value` when absent. pub fn get_or_put_value( &mut self, key: K, @@ -1280,7 +1259,7 @@ impl, A: MapAllocator> ArrayHashMap ArrayHashMap { - /// Zig `getOrPutAdapted`: look up by `key` using `adapter` for hash/eql; + /// Look up by `key` using `adapter` for hash/eql; /// on miss, append a *defaulted* `K`/`V` pair — caller fills both via /// `key_ptr` / `value_ptr`. pub fn get_or_put_adapted( @@ -1299,9 +1278,9 @@ impl ArrayHashMap { Ok(self.gop_at(i, false)) } - /// Zig `getOrPutContextAdapted`: same as `get_or_put_adapted` but takes an - /// explicit `ctx` for the *stored* key type. This port does not need `ctx` - /// for the index header (none yet), so it is accepted and ignored. + /// Same as `get_or_put_adapted` but takes an explicit `ctx` for the + /// *stored* key type. The index does not need `ctx`, so it is accepted + /// and ignored. #[inline] pub fn get_or_put_context_adapted( &mut self, @@ -1335,9 +1314,7 @@ impl ArrayHashMapExt for ArrayHashMap { /// `std.StringArrayHashMap(V)` / `bun.CaseInsensitiveASCIIStringArrayHashMap(V)`. /// /// Newtype (not an alias) so `get_or_put` / `get` / `put` can take `&[u8]` -/// borrows — the Zig API stores `[]const u8` keys and lets the caller decide -/// whether to dupe them; here keys are `Box<[u8]>` and the borrowing methods -/// box on insert. +/// borrows — keys are `Box<[u8]>` and the borrowing methods box on insert. pub struct StringArrayHashMap { inner: ArrayHashMap, V, BoxedSliceContext, A>, // The string context is consulted for hash/eql on `[u8]` borrows. The inner @@ -1346,7 +1323,7 @@ pub struct StringArrayHashMap { ctx: C, } -/// Windows env-var map (`src/bun.zig` `CaseInsensitiveASCIIStringArrayHashMap`). +/// Windows env-var map. pub type CaseInsensitiveAsciiStringArrayHashMap = StringArrayHashMap; @@ -1360,7 +1337,7 @@ impl Default for StringArrayHashMap { } impl StringArrayHashMap { - /// Zig `clone()` is fallible (OOM); kept as `Result` for API parity. + /// Fallible (OOM) clone; kept as `Result` for API stability. pub fn clone(&self) -> Result { Ok(Self { inner: self.inner.clone()?, @@ -1461,8 +1438,8 @@ impl + Default, A: MapAllocator> StringArrayHashMap true } - /// Zig: `StringArrayHashMap.fetchSwapRemove` — removes the entry (swapping - /// the last element into its slot) and returns the owned key/value pair. + /// Removes the entry (swapping the last element into its slot) and + /// returns the owned key/value pair. pub fn fetch_swap_remove(&mut self, key: &[u8]) -> Option, V>> { let i = self.find(key)?; let (k, v) = self.inner.swap_remove_at(i); @@ -1484,7 +1461,7 @@ impl + Default, A: MapAllocator> StringArrayHashMap impl + Default, A: MapAllocator> StringArrayHashMap { /// See `ArrayHashMap::get_or_put`. The key is boxed on insert; callers that /// then write `*gop.key_ptr = Box::from(key)` are doing a redundant alloc — - /// harmless, and lets the Zig-shaped call sites compile unchanged. + /// harmless. pub fn get_or_put( &mut self, key: &[u8], @@ -1527,21 +1504,21 @@ impl ArrayHashMapExt for StringArrayHashMap { // StringHashMap — unordered `[]const u8`-keyed map // ────────────────────────────────────────────────────────────────────────── -/// `std.StringHashMap(V)`. Thin newtype over `hashbrown::HashMap` that adds -/// the Zig `getOrPut` / `getOrPutValue` entry points while keeping the +/// Thin newtype over `hashbrown::HashMap` that adds +/// the `get_or_put` / `get_or_put_value` entry points while keeping the /// `hashbrown` surface (`.get`, `.contains_key`, `.reserve`, `.insert`, …) /// reachable via `Deref`. /// /// Allocator-generic so AST containers (`Scope::members` &c.) can route both /// the table *and* the owned-key boxes through `bun_alloc::AstAlloc`, -/// matching Zig's `Unmanaged` semantics where the map's backing store lives +/// so the map's backing store lives /// in the same arena as the AST nodes that hold it. The `A = Global` default /// keeps every existing `StringHashMap` site source-compatible. -// Hashed with seed-0 wyhash (matches Zig's `std.hash_map.StringContext`) — +// Hashed with seed-0 wyhash — // deterministic across runs and ~3-5× faster than `RandomState`/SipHash on // the short identifier keys the parser/printer/renamer churn. // -// The `A: Default` bound is the substitute for Zig's per-call `Allocator` +// The `A: Default` bound replaces a per-call allocator // parameter: hashbrown's `HashMap<_, _, _, A>` stores the allocator by value, // and every key `Box<[u8], A>` needs its own `A` too. For zero-sized // allocators (`Global`, `AstAlloc`) `A::default()` is a no-op constant; if a @@ -1561,14 +1538,12 @@ pub type StringHashMapInner = /// default produced by `put`/`get_or_put`) or a borrowed `&'static [u8]` /// (`Static`, produced by `put_static_key`). /// -/// Zig's `std.StringHashMap` always *borrows* the caller's `[]const u8` key — -/// the map never copies it. The Rust port originally heap-boxed every key on -/// `put` for safety, which profiling showed as the dominant cost of +/// Heap-boxing every key on `put` profiled as the dominant cost of /// `DirEntry::add_entry` (the resolver's per-file hot path): the key bytes /// there already live in the process-static `FilenameStore`/`EntryStore`, so /// the `Box<[u8]>` was a redundant second copy. The `Static` variant lets such -/// callers store the existing slice directly, matching Zig's zero-copy -/// behaviour without giving up owned-key safety for everyone else. +/// callers store the existing slice directly — zero-copy without giving up +/// owned-key safety for everyone else. /// /// `Deref` + `Borrow<[u8]>` keep `.get(&[u8])`, /// `.contains_key(&[u8])`, and `&**key` working unchanged at every call site, @@ -1578,7 +1553,7 @@ pub type StringHashMapInner = /// Packed `(ptr, len | OWNED_BIT)` instead of a 2-variant enum. The enum had /// no usable niche (both `Box<[u8]>` and `&[u8]` start with a non-null /// pointer), so it was 24 B; folding the owned/borrowed discriminant into the -/// top bit of `len` brings it to 16 B — same as Zig's `[]const u8`. For +/// top bit of `len` brings it to 16 B. For /// `Scope::members` (`hashbrown::RawTable<(StringHashMapKey, Member)>`) that /// shrinks the stored tuple 40 B → 32 B, cutting the module-scope table's /// page footprint (and `reserve_rehash` `memcpy` traffic) by ~20 %. @@ -1835,7 +1810,7 @@ impl StringHashMap self.inner.len() } - /// Zig `valueIterator()`. Inherent forwarder so callers can name + /// Inherent forwarder so callers can name /// `StringHashMap::values` without relying on `Deref` resolution. #[inline] pub fn values(&self) -> hashbrown::hash_map::Values<'_, StringHashMapKey, V> { @@ -1864,8 +1839,7 @@ impl StringHashMap } /// Insert `value` under `key` **without copying the key bytes**. This is - /// the zero-copy path that matches Zig's `StringHashMap.put` (which always - /// borrows). `key` is stored as `StringHashMapKey::Static`, so the caller + /// the zero-copy path: `key` is stored as `StringHashMapKey::Static`, so the caller /// must guarantee the bytes genuinely live for `'static` — in practice /// that means slices into a process-lifetime arena (`FilenameStore`, /// `EntryStore`, AST heap) where the `'static` was minted via an explicit @@ -1928,12 +1902,11 @@ impl StringHashMap } /// Insert `value` under `key` **without copying the key bytes** — the - /// arena-lifetime twin of [`put_static_key`]. Zig's - /// `StringHashMapUnmanaged.put` stores the caller's `[]const u8` slice by - /// value; the safe Rust [`put`] heap-boxes it instead, which profiling + /// arena-lifetime twin of [`put_static_key`]. The safe [`put`] heap-boxes + /// the key, which profiling /// flagged as the dominant `_mi_malloc_generic` caller in the parser /// (`Scope::members` takes one box per declared identifier per scope). - /// This entry point restores the Zig zero-copy behaviour for callers whose + /// This entry point provides zero-copy insertion for callers whose /// key bytes already live in an arena that outlives the map. /// /// # Safety @@ -1953,29 +1926,29 @@ impl StringHashMap } /// Insert a pre-boxed key without re-allocating it. Uses `try_reserve` so - /// OOM surfaces as `Err` instead of aborting (matches Zig `put` returning - /// `error.OutOfMemory`); callers can roll back side effects on failure. + /// OOM surfaces as `Err` instead of aborting; callers can roll back side + /// effects on failure. pub fn put_owned(&mut self, key: Box<[u8], A>, value: V) -> Result<(), AllocError> { self.inner.try_reserve(1).map_err(|_| AllocError)?; self.inner.insert(StringHashMapKey::owned(key), value); Ok(()) } - /// PERF(port): Zig skips the grow check; std::HashMap cannot, so this is + /// PERF: std::HashMap cannot skip the grow check, so this is /// just `put` without the `Result`. #[inline] pub fn put_assume_capacity(&mut self, key: &[u8], value: V) { self.inner.insert(owned_key::(key), value); } - /// Zig `putNoClobber` — asserts the key was not already present. + /// Asserts the key was not already present. pub fn put_no_clobber(&mut self, key: &[u8], value: V) -> Result<(), AllocError> { let prev = self.inner.insert(owned_key::(key), value); debug_assert!(prev.is_none(), "put_no_clobber: key already present"); Ok(()) } - /// Zig `getAdapted` — look up by `key` using `adapter` for hash/eql. + /// Look up by `key` using `adapter` for hash/eql. /// /// The adapter's precomputed hash is ignored; the lookup falls back to the /// normal `get(key)` path (correctness is preserved — `adapter.eql` is byte @@ -2001,7 +1974,7 @@ impl StringHashMap pub use crate::hash_map::GetOrPutResult as StringHashMapGetOrPut; impl StringHashMap { - /// PERF(port): the previous shape (`contains_key` + `entry(Box::from(key))`) + /// PERF: the previous shape (`contains_key` + `entry(Box::from(key))`) /// hashed `key` twice and unconditionally heap-allocated the `Box` even on /// hit. `Scope::members` calls this once per declared identifier during /// parse, so on three.js that was ~thousands of redundant `Box` @@ -2019,7 +1992,7 @@ impl StringHash Ok(self.inner.entry(owned_key::(key)).or_insert(value)) } - /// Zig `getOrPutContextAdapted` on `StringHashMap` — see `get_adapted` for + /// See `get_adapted` for /// why the adapter's precomputed hash is currently ignored. pub fn get_or_put_context_adapted( &mut self, @@ -2073,7 +2046,7 @@ impl StringHash } // ────────────────────────────────────────────────────────────────────────── -// StringHashMapContext + Prehashed adapters (src/bun.zig) +// StringHashMapContext + Prehashed adapters // ────────────────────────────────────────────────────────────────────────── /// `bun.StringHashMapContext` — wyhash(seed=0) over byte slices, full 64-bit. @@ -2103,10 +2076,10 @@ pub mod StringHashMapContext { pub use super::string_hash_map::{Prehashed, PrehashedCaseInsensitive, hash}; } -/// Namespace mirroring `std.hash_map` so call sites can write +/// String-hash helpers, namespaced so call sites can write /// `bun_collections::string_hash_map::{hash, Prehashed, GetOrPutResult}`. pub mod string_hash_map { - /// `std.hash_map.hashString` — wyhash(seed=0), full u64. + /// wyhash(seed=0), full u64. #[inline] pub fn hash(s: &[u8]) -> u64 { bun_wyhash::hash(s) @@ -2177,7 +2150,7 @@ pub mod string_hash_map { } // ────────────────────────────────────────────────────────────────────────── -// StringSet (src/bun.zig) — `StringArrayHashMap<()>` with key-duping insert +// StringSet — `StringArrayHashMap<()>` with key-duping insert // ────────────────────────────────────────────────────────────────────────── /// `bun.StringSet` — insertion-ordered set of owned byte-string keys. @@ -2192,7 +2165,7 @@ impl StringSet { Self::default() } - /// Zig `init(allocator)` — allocator dropped (global mimalloc). + /// Alias for `new()` (the global allocator is implicit). #[inline] pub fn init() -> Self { Self::default() @@ -2220,10 +2193,9 @@ impl StringSet { } /// Insert `key`, duping it on miss. Returns `Ok(())` whether or not the key - /// was already present (Zig signature). + /// was already present. pub fn insert(&mut self, key: &[u8]) -> Result<(), AllocError> { - // get_or_put already boxes `key` on miss; the Zig second-dupe is - // redundant under owned `Box<[u8]>` keys. + // get_or_put already boxes `key` on miss. let _ = self.map.get_or_put(key)?; Ok(()) } @@ -2241,7 +2213,7 @@ impl StringSet { pub fn clear_and_free(&mut self) { // Keys are `Box<[u8]>`; `clear` drops them. self.map.clear_retaining_capacity(); - // Unlike Zig, this does not free the backing arrays; Vec keeps capacity here + // This does not free the backing arrays; Vec keeps capacity here // (callers wanting that can drop the whole `StringSet`). } @@ -2249,13 +2221,13 @@ impl StringSet { } // ────────────────────────────────────────────────────────────────────────── -// StringHashMapUnowned (src/bun.zig) — pre-hashed string key +// StringHashMapUnowned — pre-hashed string key // ────────────────────────────────────────────────────────────────────────── /// `bun.StringHashMapUnowned.Key` — a string identity reduced to `(hash, len)` /// so the map never stores the string bytes. Collisions on both fields are -/// treated as equal (matches the Zig — used for side-effects globs where a -/// false positive is acceptable). +/// treated as equal (used for side-effects globs where a false positive is +/// acceptable). #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct StringHashMapUnownedKey { pub hash: u64, @@ -2276,8 +2248,7 @@ impl StringHashMapUnownedKey { pub mod string_hash_map_unowned { pub use super::StringHashMapUnownedKey as Key; - /// Adapter feeding `Key.hash` straight through (Zig - /// `bun.StringHashMapUnowned.Adapter`). + /// Adapter feeding `Key.hash` straight through. #[derive(Default, Clone, Copy)] pub struct Adapter; @@ -2293,8 +2264,6 @@ pub mod string_hash_map_unowned { } } -// ported from: vendor/zig/lib/std/array_hash_map.zig - #[cfg(test)] mod index_tests { use super::*; diff --git a/src/collections/array_list.rs b/src/collections/array_list.rs index 48206e3e2d9d..510ddce2f969 100644 --- a/src/collections/array_list.rs +++ b/src/collections/array_list.rs @@ -1,16 +1,5 @@ #![forbid(unsafe_code)] //! Managed `ArrayList` wrappers. -//! -//! The Zig original wraps `std.ArrayListAlignedUnmanaged` to add two things: -//! 1. A stored allocator (managed vs unmanaged split). -//! 2. "Deep" semantics — `deinit`/`clear`/`shrink`/`replaceRange` call `deinit` on each -//! removed item, with `*Shallow` variants that skip that. -//! -//! In Rust, (1) disappears entirely — `Vec` uses the global mimalloc allocator and the -//! `Allocator` type parameter is dropped per §Allocators in PORTING.md. (2) is the *default* -//! behavior of `Vec`: removing/dropping elements runs their `Drop`. So the "deep" methods -//! map to ordinary `Vec` operations; the `*Shallow` variants had no in-tree callers and are -//! not ported. use core::mem; @@ -20,66 +9,40 @@ use super::vec_ext::VecExt; /// Managed `ArrayList` using an arbitrary allocator. /// Prefer using a concrete type, like `ArrayListDefault`. -/// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// Zig's `std.mem.Allocator` type param is dropped — global mimalloc (non-AST crate). pub type ArrayList = ArrayListAlignedIn; /// Managed `ArrayList` using the default allocator. No overhead compared to an unmanaged /// `ArrayList`. -/// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// Zig's `bun.DefaultAllocator` type param is dropped — global mimalloc. pub type ArrayListDefault = ArrayListAlignedIn; /// Managed `ArrayList` using a specific kind of allocator. -/// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// Zig's `Allocator` type param is dropped — global mimalloc. pub type ArrayListIn = ArrayListAlignedIn; /// Managed `ArrayListAligned` using an arbitrary allocator. -/// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// Zig's const-generic alignment param is dropped: Rust `Vec` uses `align_of::()` and has -// no over-alignment knob. A call site that needs a non-null `alignment` must wrap `T` in a -// `#[repr(align(N))]` newtype instead. +// Rust `Vec` uses `align_of::()` and has no over-alignment knob. A call site that needs +// over-alignment must wrap `T` in a `#[repr(align(N))]` newtype instead. pub type ArrayListAligned = ArrayListAlignedIn; /// Managed `ArrayListAligned` using the default allocator. -/// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. pub type ArrayListAlignedDefault = ArrayListAlignedIn; /// Managed `ArrayListAligned` using a specific kind of allocator. /// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// Zig's `fn(...) type` factory → generic struct (PORTING.md §Idiom map). -// Allocator type param dropped; alignment param dropped (see `ArrayListAligned` above). +/// NOTE: dropping this type (and the aliases above) runs `Drop` on each of the items. #[derive(Default)] pub struct ArrayListAlignedIn { - /// Zig: `#unmanaged: Unmanaged = .empty` unmanaged: Unmanaged, - // Zig: `#std.mem.Allocator param` — dropped (global mimalloc). } -/// Zig: `Unmanaged = std.ArrayListAlignedUnmanaged(T, alignment)` pub(crate) type Unmanaged = Vec; -/// Zig: `Slice = Unmanaged.Slice` (= `[]align(alignment) T`, an owned slice when detached). -// Zig `Slice` is used both as a borrow (`items()`) and as an owned return (`toOwnedSlice`). -// Rust splits these: borrows are `&[T]`/`&mut [T]`, owned is `Box<[T]>`. pub type Slice = Box<[T]>; -// Zig `SentinelSlice` is not ported — sentinel-terminated slices have no std Rust equivalent. -// A caller that needs one maps to `bun_core::ZStr`/`WStr` at the call site. - impl ArrayListAlignedIn { pub fn items(&self) -> &[T] { self.unmanaged.as_slice() } - // Zig `items()` returns a mutable `[]T`; Rust splits const/mut borrows. pub fn items_mut(&mut self) -> &mut [T] { self.unmanaged.as_mut_slice() } @@ -89,7 +52,6 @@ impl ArrayListAlignedIn { } pub fn init() -> Self { - // Zig: `.initIn(bun.memory.initDefault(Allocator))` — allocator dropped. Self::init_in() } @@ -104,25 +66,21 @@ impl ArrayListAlignedIn { } pub fn init_capacity_in(num: usize /* allocator dropped */) -> Result { - // Zig: `try .initCapacity(bun.allocators.asStd(allocator_), num)` - // PERF(port): Vec::with_capacity aborts on OOM rather than returning Err. Could swap to + // Vec::with_capacity aborts on OOM rather than returning Err. Could swap to // `Vec::try_with_capacity` (nightly) or a fallible wrapper if OOM recovery matters. Ok(Self { unmanaged: Vec::with_capacity(num), }) } - // Zig `pub fn deinit` → `impl Drop` (see below). Body only deinits items + frees backing, - // both of which `Vec`'s `Drop` already does, so no explicit `Drop` impl is needed. - pub fn from_owned_slice(slice: Slice) -> Self { Self { unmanaged: Vec::from(slice), } } - // Sentinel-terminated owned slices are not a Rust type, so unlike Zig's - // `fromOwnedSliceSentinel` this takes a `Box<[T]>` with the sentinel already stripped. + // Sentinel-terminated owned slices are not a Rust type, so this takes a `Box<[T]>` with + // the sentinel already stripped. pub fn from_owned_slice_sentinel(/* sentinel: T, */ slice: Slice) -> Self { Self { unmanaged: Vec::from(slice), @@ -131,13 +89,11 @@ impl ArrayListAlignedIn { /// This method empties `self`. pub fn move_to_unmanaged(&mut self) -> Unmanaged { - // Zig: `defer self.#unmanaged = .empty; return self.#unmanaged;` mem::take(&mut self.unmanaged) } /// Unlike `move_to_unmanaged`, this method *consumes* `self`. pub fn into_unmanaged_with_allocator(self) -> (Unmanaged, ()) { - // Zig: returns `(Unmanaged, Allocator)`; allocator dropped → unit. (self.unmanaged, ()) } @@ -148,15 +104,10 @@ impl ArrayListAlignedIn { } pub fn to_owned_slice(self) -> Result, AllocError> { - // Zig: `self.#unmanaged.toOwnedSlice(...)` — shrinks cap→len then returns the slice. Ok(self.unmanaged.into_boxed_slice()) } /// Creates a copy of this `ArrayList` with copies of its items. - /// - /// Zig makes *bitwise* (shallow) copies regardless of whether `T` has a `deinit`. Rust - /// cannot bit-copy a non-`Copy` `T` safely. This is bound on `T: Clone`; callers that - /// relied on shallow-copy-then-`deinitShallow` need a redesign. pub fn clone(&self) -> Result where T: Clone, @@ -180,7 +131,6 @@ impl ArrayListAlignedIn { } pub fn insert_assume_capacity(&mut self, i: usize, item: T) { - // PERF(port): was assume_capacity — profile if hot. self.unmanaged.insert(i, item); } @@ -194,7 +144,6 @@ impl ArrayListAlignedIn { where T: Clone, { - // Zig: `addManyAt` reserves `count` uninit slots at `index`, then `@memset(result, value)`. self.unmanaged .splice(index..index, core::iter::repeat_n(value, count)); Ok(&mut self.unmanaged[index..index + count]) @@ -205,14 +154,12 @@ impl ArrayListAlignedIn { where T: Clone, { - // PERF(port): was assume_capacity — profile if hot. self.unmanaged .splice(index..index, core::iter::repeat_n(value, count)); &mut self.unmanaged[index..index + count] } - /// Note that this `Clone`s each element of `new_items` (Zig took `[]const T` and - /// bit-copied, transferring ownership; a borrowed Rust slice cannot transfer ownership). + /// Note that this `Clone`s each element of `new_items`. pub fn insert_slice(&mut self, index: usize, new_items: &[T]) -> Result<(), AllocError> where T: Clone, @@ -233,8 +180,7 @@ impl ArrayListAlignedIn { where T: Clone, { - // Zig deinits `items[start..start+len]` then calls the shallow path. - // `Vec::splice` already drops the removed range, so deep == direct splice. + // `Vec::splice` drops the removed range. self.unmanaged .splice(start..start + len, new_items.iter().cloned()); Ok(()) @@ -246,7 +192,6 @@ impl ArrayListAlignedIn { where T: Clone, { - // PERF(port): was assume_capacity — profile if hot. let _ = self.replace_range(start, len, new_items); } @@ -256,7 +201,6 @@ impl ArrayListAlignedIn { } pub fn append_assume_capacity(&mut self, item: T) { - // PERF(port): was assume_capacity — profile if hot. self.unmanaged.push(item); } @@ -282,7 +226,6 @@ impl ArrayListAlignedIn { where T: Clone, { - // PERF(port): was assume_capacity — profile if hot. self.unmanaged.extend_from_slice(new_items); } @@ -291,9 +234,8 @@ impl ArrayListAlignedIn { where T: Clone, { - // Zig `[]align(1) const T` allows reading T from an under-aligned address. Rust `&[T]` - // is always naturally aligned, so this is identical to `append_slice`; a caller that - // truly has unaligned bytes needs `ptr::read_unaligned` at the call site. + // Rust `&[T]` is always naturally aligned, so this is identical to `append_slice`; a + // caller that truly has unaligned bytes needs `ptr::read_unaligned` at the call site. self.unmanaged.extend_from_slice(new_items); Ok(()) } @@ -303,7 +245,6 @@ impl ArrayListAlignedIn { where T: Clone, { - // PERF(port): was assume_capacity — profile if hot. self.unmanaged.extend_from_slice(new_items); } @@ -323,7 +264,6 @@ impl ArrayListAlignedIn { where T: Clone, { - // PERF(port): was assume_capacity — profile if hot. self.unmanaged.extend(core::iter::repeat_n(value, n)); } @@ -335,10 +275,6 @@ impl ArrayListAlignedIn { where T: Clone, { - // Zig calls `resizeWithoutDeinit` first, *then* deinits the tail via a raw - // pointer past `len` (`items().ptr[new_len..len]`). That ordering is to avoid a failed - // realloc leaving already-deinited items in the list. `Vec::resize` already drops the - // truncated tail in the shrink case and never fails, so the ordering concern vanishes. self.unmanaged.resize(new_len, init_value); Ok(()) } @@ -358,14 +294,12 @@ impl ArrayListAlignedIn { /// This method `Drop`s all items. pub fn clear_retaining_capacity(&mut self) { - // Zig: `bun.memory.deinit(self.items()); self.clearRetainingCapacityShallow();` - // `Vec::clear` drops all items and retains capacity — exactly the deep semantics. + // `Vec::clear` drops all items and retains capacity. self.unmanaged.clear(); } /// This method `Drop`s all items. pub fn clear_and_free(&mut self) { - // Zig: `bun.memory.deinit(self.items()); self.clearAndFreeShallow();` self.unmanaged = Vec::new(); } @@ -391,7 +325,6 @@ impl ArrayListAlignedIn { { let len = self.unmanaged.len(); let cap = self.unmanaged.capacity(); - // Zig: `self.#unmanaged.expandToCapacity(); @memset(self.items()[len..], init_value);` self.unmanaged .extend(core::iter::repeat_n(init_value, cap - len)); debug_assert_eq!(self.unmanaged.len(), cap); @@ -402,13 +335,11 @@ impl ArrayListAlignedIn { } pub fn get_last(&self) -> &T { - // Zig: `&items_[items_.len - 1]` — panics on empty, same as `[len-1]` here. + // Panics on empty. let items = self.items(); &items[items.len() - 1] } - // Zig returns `*T` (mutable) from a `*const Self` receiver via interior aliasing. - // Rust splits this into `&T` / `&mut T` accessors. pub fn get_last_mut(&mut self) -> &mut T { let len = self.unmanaged.len(); &mut self.unmanaged[len - 1] @@ -432,27 +363,15 @@ impl ArrayListAlignedIn { new_len <= items_len, "new_len ({new_len}) cannot exceed current len ({items_len})", ); - // Zig: `bun.memory.deinit(items_[new_len..])` — drop the tail in place. - // `Vec::truncate` does exactly that and keeps capacity. + // `Vec::truncate` drops the tail in place and keeps capacity. self.unmanaged.truncate(new_len); } - - // Zig `getStdAllocator` — dropped (no allocator field). } impl ArrayListAlignedIn { - /// Zig `writer()` returns an `std.io.Writer` that appends bytes; the Rust equivalent is - /// `impl std::io::Write for Vec` (already in std), so hand out the backing `Vec`. + /// Hands out the backing `Vec`, which already implements `std::io::Write`. /// Only exposed for `T = u8` — there is no meaningful writer for other element types. pub fn writer(&mut self) -> &mut Vec { &mut self.unmanaged } } - -// Zig `pub fn deinit` → `impl Drop`. The Zig body is -// `bun.memory.deinit(self.items()); self.deinitShallow();` -// i.e. drop every item, then free the backing buffer. `Vec`'s own `Drop` does both, so per -// PORTING.md ("If the body only frees/deinits owned fields, delete the body entirely") no -// explicit `impl Drop for ArrayListAlignedIn` is written. - -// ported from: src/collections/array_list.zig diff --git a/src/collections/bit_set.rs b/src/collections/bit_set.rs index 0af25a034498..c1b843e51396 100644 --- a/src/collections/bit_set.rs +++ b/src/collections/bit_set.rs @@ -1,8 +1,3 @@ -//! This is a fork of Zig standard library bit_set.zig -//! - https://github.com/ziglang/zig/pull/14129 -//! - AutoBitset which optimally chooses between a dynamic or static bitset. -//! Prefer our fork over std.bit_set. -//! //! This file defines several variants of bit sets. A bit set //! is a densely stored set of integers with a known maximum, //! in which each integer gets a single bit. Bit sets have very @@ -43,22 +38,21 @@ use bun_alloc::AllocError; // ───────────────────────────── helpers ───────────────────────────── -/// Equivalent to `std.math.boolMask(MaskInt, value)`: returns `~0` if `value` -/// else `0`, in the requested integer width. +/// Returns `usize::MAX` if `value`, else `0`. #[inline(always)] const fn bool_mask_usize(value: bool) -> usize { if value { usize::MAX } else { 0 } } /// `1 << (index % usize::BITS)` — selects the bit within a `usize` word. -/// Shared by `ArrayBitSet` and `DynamicBitSetUnmanaged` (Zig: `maskBit`). +/// Shared by `ArrayBitSet` and `DynamicBitSetUnmanaged`. #[inline(always)] const fn word_mask_bit(index: usize) -> usize { 1usize << ((index as u32) & (usize::BITS - 1)) // @truncate } /// `index / usize::BITS` — selects which `usize` word holds the bit. -/// Shared by `ArrayBitSet` and `DynamicBitSetUnmanaged` (Zig: `maskIndex`). +/// Shared by `ArrayBitSet` and `DynamicBitSetUnmanaged`. #[inline(always)] const fn word_mask_index(index: usize) -> usize { index >> usize::BITS.trailing_zeros() @@ -66,7 +60,7 @@ const fn word_mask_index(index: usize) -> usize { /// Shared multi-mask implementation of `set_range_value` over `&mut [usize]` /// storage. Used by both `ArrayBitSet` and `DynamicBitSetUnmanaged` so the -/// per-word range masking logic lives in one place (Zig: `setRangeValue`). +/// per-word range masking logic lives in one place. #[inline] fn set_range_value_masks(masks: &mut [usize], range: Range, value: bool) { const MASK_LEN: u32 = usize::BITS; @@ -117,12 +111,10 @@ fn set_range_value_masks(masks: &mut [usize], range: Range, value: bool) { /// can be copied by value, and does not require deinitialization. /// Both possible implementations fulfill the same interface. /// -// Zig's `StaticBitSet(size)` returns `IntegerBitSet(size)` when -// `size <= @bitSizeOf(usize)` and `ArrayBitSet(usize, size)` otherwise. Stable -// Rust cannot select a struct definition from a const generic, so this alias -// is the integer form and is only valid for `SIZE <= usize::BITS` (enforced by -// `IntegerBitSet`'s debug asserts). Callers needing more bits must use -// `ArrayBitSet` directly. +// Stable Rust cannot select a struct definition from a const generic, so this +// alias is the integer form and is only valid for `SIZE <= usize::BITS` +// (enforced by `IntegerBitSet`'s debug asserts). Callers needing more bits +// must use `ArrayBitSet` directly. pub type StaticBitSet = IntegerBitSet; // ───────────────────────────── IntegerBitSet ───────────────────────────── @@ -131,10 +123,8 @@ pub type StaticBitSet = IntegerBitSet; /// This set is good for sets with a small size, but may generate /// inefficient code for larger sets, especially in debug mode. /// -// Zig uses `std.meta.Int(.unsigned, size)` for an exact-width backing integer -// (u0..u65535). Rust has no arbitrary-width ints; this is backed by `usize` -// and requires `SIZE <= usize::BITS` (misuse surfaces via `FULL_MASK` -// saturation + debug asserts). +// Backed by `usize`; requires `SIZE <= usize::BITS` (misuse surfaces via +// `FULL_MASK` saturation + debug asserts). #[repr(transparent)] #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct IntegerBitSet { @@ -220,12 +210,10 @@ impl IntegerBitSet { let mut mask = bool_mask_usize(true) << start_bit; if range.end != Self::BIT_LENGTH { let end_bit = u32::try_from(range.end).expect("int cast"); - // Zig shifts a SIZE-bit MaskInt so `~0 >> (SIZE - end_bit)` yields the - // low `end_bit` bits. With a usize backing the shift must be relative - // to usize::BITS to get the same low-`end_bit`-bits mask. + // `~0 >> (usize::BITS - end_bit)` yields the low `end_bit` bits. mask &= bool_mask_usize(true) >> (usize::BITS - end_bit); } - // also clear bits above SIZE since our backing int is wider than Zig's + // also clear bits above SIZE since the backing `usize` may be wider than SIZE bits mask &= Self::FULL_MASK; self.mask &= !mask; @@ -390,7 +378,7 @@ impl IntegerBitSet { } /// Iterate indices of set bits in ascending order. - /// Convenience wrapper for `iterator::()` (Zig's `.iterator(.{ .kind = .set })`). + /// Convenience wrapper for `iterator::()`. #[inline] pub fn iter_set(self) -> SingleWordIterator { self.iterator::() @@ -443,9 +431,6 @@ pub const fn num_masks_for(bit_length: usize) -> usize { /// A bit set with static size, which is backed by an array of usize. /// This set is good for sets with a larger size, but may use /// more bytes than necessary if your set is small. -/// -// Zig is generic over `MaskIntType`; every in-tree caller uses `usize`, so -// the type parameter is dropped. #[repr(C)] #[derive(Clone, Copy)] pub struct ArrayBitSet { @@ -760,9 +745,8 @@ impl ArrayBitSet { /// A bit set with runtime-known size, backed by an allocated slice /// of usize. The allocator must be tracked externally by the user. /// -// Layout invariant: like the Zig type, `masks` is a `[*]MaskInt` where -// `masks[-1]` holds the true allocation length (Zig's allocator API requires -// the original length on free). The Rust port keeps that exact layout because +// Layout invariant: `masks` is a raw pointer where `masks[-1]` holds the true +// allocation length (needed on free). This layout is load-bearing because // `DynamicBitSetList` constructs borrowed views into a shared buffer that must // look like freestanding `DynamicBitSetUnmanaged`s — do not swap the storage // for `Vec` without reworking `DynamicBitSetList`. @@ -775,14 +759,12 @@ pub struct DynamicBitSetUnmanaged { pub masks: *mut usize, // This pointer is one usize after the actual allocation. // That slot holds the size of the true allocation, which - // is needed by Zig's allocator interface in case a shrink - // fails. + // is needed when freeing. } const DYN_MASK_BITS: u32 = usize::BITS; -// Never modified — the Zig comment about needing `static mut` was a Zig -// limitation (no const-ptr → mut-ptr cast at comptime). All writes through +// Never modified. All writes through // `self.masks` are guarded by `num_masks() > 0`, which is false for the empty // sentinel (bit_length == 0). Kept in a `RacyCell` (not `.rodata`) so that // forming a `*mut usize` to it remains a legally-mutable pointer target — @@ -814,8 +796,8 @@ impl Drop for DynamicBitSetUnmanaged { } impl DynamicBitSetUnmanaged { - // Zig's `pub const empty: Self = .{ ... }` const value has no Rust equivalent here - // (the empty sentinel pointer is computed at runtime); use `Self::default()`. + // There is no `const` empty value (the empty sentinel pointer is computed at + // runtime); use `Self::default()`. /// Borrow the mask words as a shared slice of length `num_masks(bit_length)`. #[inline(always)] @@ -1379,8 +1361,8 @@ impl Drop for DynamicBitSetList { // the owning struct between threads is as safe as moving a `Box<[usize]>`. unsafe impl Send for DynamicBitSetList {} -// Raw allocation helpers for DynamicBitSetUnmanaged. These mirror Zig's -// allocator.alloc/realloc/free with the size-at-[-1] header convention. +// Raw allocation helpers for DynamicBitSetUnmanaged, using the size-at-[-1] +// header convention. unsafe fn dyn_free(base: *mut usize, len: usize) { if len == 0 { @@ -1418,7 +1400,7 @@ unsafe fn dyn_realloc( // ───────────────────────────── AutoBitSet ───────────────────────────── -/// Static arm size: `@bitSizeOf(DynamicBitSetUnmanaged) - 1`. +/// Static arm size: one less than the bit-size of `DynamicBitSetUnmanaged`. pub(crate) const AUTO_STATIC_BITS: usize = mem::size_of::() * 8 - 1; pub(crate) type AutoBitSetStatic = @@ -1430,11 +1412,9 @@ pub enum AutoBitSet { } // ─── two-arm forward helper ──────────────────────────────────────────── -// Zig had `switch (this.*) { inline else => |*b| b.method() }` for the -// symmetric arms (setAll/count/findFirstSet/Iterator.next). The Rust port -// regressed those to open-coded matches; this macro restores the collapse -// and is applied to every method whose Static/Dynamic arms are textually -// identical. Asymmetric arms (clone, raw_bytes, has_intersection, Drop) +// This macro forwards a call to whichever arm is active and is applied to +// every method whose Static/Dynamic arms are textually identical. +// Asymmetric arms (clone, raw_bytes, has_intersection, Drop) // stay open-coded — they genuinely differ. macro_rules! auto_forward { ($self:expr, |$b:ident| $body:expr) => { @@ -1555,9 +1535,6 @@ impl Drop for AutoBitSet { /// A bit set with runtime-known size, backed by an allocated slice /// of usize. Thin wrapper around DynamicBitSetUnmanaged which keeps /// track of the allocator instance. -/// -// In Rust the managed/unmanaged split disappears (global allocator); this -// wrapper is kept so call sites diff cleanly against the Zig source. #[derive(Default)] pub struct DynamicBitSet { /// The number of valid items in this bit set @@ -1604,7 +1581,7 @@ impl DynamicBitSet { self.unmanaged.capacity() } - /// Zig spelling of `capacity()` (`.bit_length`). + /// Alias for `capacity()`. #[inline(always)] pub fn bit_length(&self) -> usize { self.unmanaged.capacity() @@ -1757,8 +1734,6 @@ pub enum IteratorDirection { // ───────────────────────────── BitSetIterator ───────────────────────────── // The iterator is reusable between several bit set types. -// Zig is generic over `MaskInt`; fixed to `usize` here since every in-tree -// caller uses `usize`. pub struct BitSetIterator<'a, const KIND_SET: bool, const DIR_FWD: bool> { // all bits which have not yet been iterated over bits_remain: usize, @@ -1858,8 +1833,3 @@ pub struct Range { /// The index immediately after the last bit of interest. pub end: usize, } - -// The Zig source's test helpers (`testEql`, `testBitSet`, …) were dead code -// carried from the std fork — no `test` block invoked them — and were not ported. - -// ported from: src/collections/bit_set.zig diff --git a/src/collections/comptime_string_map.rs b/src/collections/comptime_string_map.rs index 72001d1f078b..a2d68d57e4a0 100644 --- a/src/collections/comptime_string_map.rs +++ b/src/collections/comptime_string_map.rs @@ -1,21 +1,16 @@ //! Comptime string map optimized for small sets of disparate string keys. -//! Works by separating the keys by length at comptime and only checking strings of +//! Works by separating the keys by length up front and only checking strings of //! equal length at runtime. //! //! `kvs` expects a list literal containing list literals or an array/slice of structs //! where `.0` is the `&[u8]` key and `.1` is the associated value of type `V`. -// The Zig original is a `fn(comptime KeyType, comptime V, comptime kvs_list) type` -// that does heavy comptime work: sorts kvs by (len, bytes), builds a `len_indexes` table, -// then every lookup is an `inline while` over lengths × `inline for` over same-length keys -// with `eqlComptime` (length-known SIMD compare). Rust cannot replicate the per-callsite -// monomorphization without a proc-macro, so this models it as a const-constructed struct -// holding the precomputed tables plus a `comptime_string_map!` macro that callers use. -// Runtime loops replace `inline for`/`inline while`; each is tagged `PERF(port)`. +// A struct holding precomputed tables (kvs sorted by (len, bytes), plus a `len_indexes` +// table), built via the `comptime_string_map!` macro that callers use. // -// Per PORTING.md crate-map, downstream callers of `bun.ComptimeStringMap(V, .{...})` should -// prefer `phf::phf_map!` directly when they only need `.get()`/`.has()`. This struct exists -// for the call sites that need `get_with_eql` / `get_any_case` / `index_of` / `get_key`. +// Downstream callers should prefer `phf::phf_map!` directly when they only need +// `.get()`/`.has()`. This struct exists for the call sites that need `get_with_eql` / +// `get_any_case` / `index_of` / `get_key`. use bun_core::strings; @@ -34,8 +29,7 @@ pub struct ComptimeStringMapWithKeyType< const N: usize, const LEN_TABLE: usize, > { - // In Zig these were `precomputed.{min_len,max_len,sorted_kvs,len_indexes}` - // computed in a `comptime blk:`. Here they are filled by the constructor macro. + // Filled by the constructor macro. min_len: usize, max_len: usize, /// Sorted ascending by (key.len, key bytes). @@ -47,8 +41,7 @@ pub struct ComptimeStringMapWithKeyType< pub type ComptimeStringMap = ComptimeStringMapWithKeyType; -/// Trait abstracting "has a length" for `get_with_eql` inputs — Zig used -/// `if (@hasField(Input, "len")) input.len else input.length()`. +/// Trait abstracting "has a length" for `get_with_eql` inputs. pub trait HasLength { fn length(&self) -> usize; } @@ -71,9 +64,6 @@ impl HasLength for &bun_alloc::String { } } -// Zig's `pub const Value = V;` (inherent assoc type) is nightly-only in Rust; -// callers write `V` directly. - impl ComptimeStringMapWithKeyType where K: Copy + Eq + Ord + 'static, @@ -81,9 +71,8 @@ where { /// Builds the precomputed tables. Called by the `comptime_string_map!` macro. /// - /// Mirrors the `comptime blk:` in the Zig: sort by (len asc, bytes asc), then - /// fill `len_indexes[len]` = first index whose key.len >= len. - // PERF(port): Zig did this at comptime (zero runtime cost); this runs once at init. + /// Sorts by (len asc, bytes asc), then fills `len_indexes[len]` = first index whose + /// key.len >= len. pub fn new(mut sorted_kvs: [KV; N]) -> Self { // lenAsc comparator sorted_kvs.sort_by(|a, b| { @@ -133,10 +122,6 @@ where } /// Contiguous range in `kvs` whose keys have exactly `len`. - /// - /// The .zig spec open-coded this at every lookup site because `len` was - /// `comptime` there and each needed its own `comptime brk:` block. In the Rust port - /// `len` is runtime, so the duplication is vestigial — extract once and inline. #[inline(always)] fn len_bucket(&self, len: usize) -> core::ops::Range { let start = self.len_indexes[len]; @@ -147,18 +132,15 @@ where start..end } - // Zig's `comptime len: usize` → runtime `len: usize`. The Zig used the comptime - // value to compute `end` at comptime and `inline for` the range; we loop at runtime. - // PERF(port): was comptime monomorphization — profile if hot. pub fn get_with_length(&self, str: &[K], len: usize) -> Option { let core::ops::Range { start, end } = self.len_bucket(len); // This benchmarked faster for both small and large lists of strings than using a big switch statement // But only so long as the keys are a sorted list. for i in start..end { - // PERF(port): Zig used `strings.eqlComptimeCheckLenWithType(K, str, kvs[i].key, false)` - // (length-known SIMD compare). Plain slice == here; could swap to - // `bun_core::strings::eql_comptime_check_len_with_type` if hot. + // PERF: plain slice == here; could swap to + // `bun_core::strings::eql_comptime_check_len_with_type` + // (length-known SIMD compare) if hot. if str == self.kvs[i].key { return Some(self.kvs[i].value); } @@ -180,7 +162,6 @@ where // This benchmarked faster for both small and large lists of strings than using a big switch statement // But only so long as the keys are a sorted list. - // PERF(port): was `inline for` — profile if hot. for i in start..end { if eqls(str, self.kvs[i].key) { return Some(self.kvs[i].value); @@ -195,8 +176,6 @@ where return None; } - // PERF(port): Zig `inline while (i <= max_len)` dispatched to a monomorphized - // `getWithLength(str, comptime i)`. We call the runtime version directly. self.get_with_length(str, str.len()) } @@ -211,14 +190,11 @@ where // This benchmarked faster for both small and large lists of strings than using a big switch statement // But only so long as the keys are a sorted list. - // PERF(port): was `inline for` over comptime range. (start..end).find(|&i| str == self.kvs[i].key) } - // Zig's `fromJS` / `fromJSCaseInsensitive` were thin shims to - // `jsc/comptime_string_map_jsc.zig`; their Rust port lives in - // `src/jsc/comptime_string_map_jsc.rs` (the base `bun_collections` crate has - // no JSC dependency). + // `fromJS` / `fromJSCaseInsensitive` live in `src/jsc/comptime_string_map_jsc.rs` + // (the base `bun_collections` crate has no JSC dependency). pub fn get_with_eql(&self, input: I, eql: impl Fn(I, &'static [K]) -> bool) -> Option where @@ -229,7 +205,6 @@ where return None; } - // PERF(port): was `inline while` dispatch to comptime-len variant. self.get_with_length_and_eql(input, length, eql) } @@ -240,7 +215,6 @@ where where V: PartialEq, { - // PERF(port): was `inline for` — profile if hot. for kv in &self.kvs { if kv.value == value { return Some(kv.key); @@ -255,8 +229,8 @@ impl ComptimeStringMapWithKeyType Option { self.get_with_eql(str, bun_alloc::String::eql_comptime) } @@ -275,9 +249,8 @@ where return None; } - // PERF(port): Zig built a `[i]u8` stack buffer per comptime length; we use a - // bounded stack buffer for the common case (every in-tree map has small keys) - // and fall back to a heap buffer for maps whose keys exceed it. + // Bounded stack buffer for the common case (every in-tree map has small keys); + // fall back to a heap buffer for maps whose keys exceed it. let mut stack_buf = [0u8; 256]; let mut heap_buf: Vec; let buf: &mut [u8] = if length <= stack_buf.len() { @@ -301,13 +274,12 @@ where /// ... /// ]); /// ``` -// Zig sorted + built len_indexes at comptime. A `macro_rules!` cannot sort, so this -// lazy-inits via `once_cell::Lazy` + `ComptimeStringMapWithKeyType::new` — correct, with a -// one-time runtime init cost instead of true const construction. +// A `macro_rules!` cannot sort, so this lazy-inits via `once_cell::Lazy` + +// `ComptimeStringMapWithKeyType::new` — correct, with a one-time runtime init cost +// instead of true const construction. #[macro_export] macro_rules! comptime_string_map { ($V:ty, [ $( ($key:expr, $val:expr) ),* $(,)? ]) => {{ - // PERF(port): was comptime; now lazy runtime init. ::once_cell::sync::Lazy::new(|| { $crate::comptime_string_map::ComptimeStringMapWithKeyType::::new([ $( $crate::comptime_string_map::KV { key: $key, value: $val } ),* @@ -377,8 +349,6 @@ mod tests { #[test] fn comptime_string_map_array_of_structs() { - // Zig tested that anonymous-struct and named-struct kv inputs both work. - // In Rust there is one input shape (`KV`), so this collapses to the same test. let map = ComptimeStringMapWithKeyType::::new([ KV { key: b"these", @@ -497,8 +467,6 @@ mod tests { } // `TestEnum2` + its 39-entry `map`/`official` table existed only as a - // benchmark fixture against `std.ComptimeStringMap` (no `test` block references it). + // benchmark fixture (no `test` block references it). // Omitted; can re-add as a criterion bench if needed. } - -// ported from: src/collections/comptime_string_map.zig diff --git a/src/collections/hive_array.rs b/src/collections/hive_array.rs index 9851ae2eefa5..4570ebd467a6 100644 --- a/src/collections/hive_array.rs +++ b/src/collections/hive_array.rs @@ -8,8 +8,6 @@ use bun_core::asan; /// Fixed-width occupancy bitset for [`HiveArray`]. /// -/// Zig's `std.bit_set.IntegerBitSet(N)` is backed by an exact-width -/// `uN` integer (`u128`, `u256`, `u2048`, …). The Rust port's /// [`IntegerBitSet`](crate::bit_set::IntegerBitSet) is backed by a single /// `usize`, so for `N > 64` it silently held only 64 usable bits — every /// `HiveArray<_, 128/256/2048>` pool degraded to 64 effective slots and spilled @@ -171,8 +169,6 @@ impl HiveBitSetIter { /// All slot operations take `&self` and the buffer is `UnsafeCell` — slot /// pointers come from `UnsafeCell::get()` and so survive `&self` reborrows of /// the pool (the `bumpalo` / `typed-arena` shape). `HiveArray` is `!Sync`. -// Zig's `capacity: u16` is widened to `usize` here because Rust array -// lengths require a `usize` const generic on stable. pub struct HiveArray { buffer: UnsafeCell<[MaybeUninit; CAPACITY]>, pub used: HiveBitSet, @@ -181,10 +177,6 @@ pub struct HiveArray { impl HiveArray { pub const SIZE: usize = CAPACITY; - // Zig had `pub var empty: Self` as a mutable static to work around - // https://github.com/ziglang/zig/issues/22462 and /21988. Rust has no such - // limitation; callers should use `init()` (which is `const`). - pub const fn init() -> Self { Self { buffer: UnsafeCell::new([const { MaybeUninit::uninit() }; CAPACITY]), @@ -338,7 +330,7 @@ impl HiveArray { /// into this hive, returns `false` and is a no-op. Use when the caller has /// already moved the contents out / destructured them, or when `T` is POD /// and the slot is being released on an error path before it was fully - /// initialized (Zig `value.* = undefined`). + /// initialized. /// /// # Safety /// No live token ([`HiveSlot`], [`HiveBox`]) may exist for this slot — once @@ -403,9 +395,7 @@ impl HiveArray { debug_assert!(self.used.is_set(index as usize)); debug_assert!(self.ptr_at(index as usize).cast_const() == value.cast_const()); - // Zig wrote `value.* = undefined;` — Zig has no destructors, - // so the slot was simply marked logically uninitialized. In the Rust - // port several `T` carry owned heap data (e.g. `NumberScope.name_counts: + // Several `T` carry owned heap data (e.g. `NumberScope.name_counts: // StringHashMap`, `NetworkTask.url_buf: Box<[u8]>`); drop the slot // before recycling so the put/get cycle does not leak it. Callers that // pre-clean fields (`PooledSocket::release_parked_refs`) leave only @@ -589,15 +579,11 @@ impl Drop for HiveBox<'_, T, CAPACITY> { } } -// In Zig this was the nested type `HiveArray(T, capacity).Fallback`. -// Rust cannot nest a generic struct that captures outer generics, so it lives at -// module scope with the same parameters. The Zig field -// `hive: if (capacity > 0) Self else void` is always materialized here; the -// `CAPACITY > 0` checks below preserve the original gating. -// PERF(port): zero-capacity case carried a zero-size hive in Zig — profile in Phase B. +// Rust cannot nest a generic struct that captures outer generics, so this lives +// at module scope with the same parameters as `HiveArray`. The hive is always +// materialized; the `CAPACITY > 0` checks below gate its use. pub struct Fallback { pub hive: HiveArray, - // (Zig's `std.mem.Allocator` field is gone — the fallback uses the global allocator.) } impl Fallback { @@ -635,7 +621,7 @@ impl Fallback { /// for `MaybeUninit`). /// /// The returned allocation is leaked — callers stash it in a per-thread - /// static for the process lifetime (Zig: `threadlocal var pool`). + /// static for the process lifetime. #[inline] pub fn new_boxed() -> NonNull { let mut boxed = Box::::new_uninit(); @@ -768,14 +754,12 @@ impl Fallback { // HiveRef // ────────────────────────────────────────────────────────────────────────── // -// Ground truth is `bun.HiveRef` in src/bun.zig. It lives here (not -// in the `bun` crate) because every consumer names it through +// Lives here (not in the `bun` crate) because every consumer names it through // `bun_collections::HiveRef`, and its only collaborator is `Fallback` above. // -// Zig defines `const HiveAllocator = HiveArray(@This(), capacity).Fallback` -// inside the returned struct; Rust spells the self-referential pool type out -// as `Fallback, CAPACITY>`. CAPACITY is `usize` (widened -// from Zig's `u16`) to line up with `HiveArray`/`Fallback`'s const generic. +// The self-referential pool type is spelled out as +// `Fallback, CAPACITY>`. CAPACITY is `usize` to line up +// with `HiveArray`/`Fallback`'s const generic. /// Intrusive ref-counted slot allocated from a `HiveArray::Fallback` pool. /// `pool` is a BACKREF (LIFETIMES.tsv class) — the pool strictly outlives @@ -791,7 +775,8 @@ pub struct HiveRef { } impl HiveRef { - /// Zig: `pub fn init(value, allocator) !*@This()`. + /// Allocate a slot from `pool` (heap fallback when full) and initialize it + /// with `value` at refcount 1. /// /// # Safety /// `pool` must be valid for the entire lifetime of the returned @@ -816,8 +801,8 @@ impl HiveRef { self } - /// Zig: `pub fn unref(this) ?*@This()` — returns `None` when the count hit - /// zero and the slot was returned to the pool. + /// Returns `None` when the count hit zero and the slot was returned to + /// the pool. /// /// # Safety /// `this` must point at a live `HiveRef` produced by [`init`](Self::init). @@ -825,8 +810,7 @@ impl HiveRef { pub unsafe fn unref(this: *mut Self) -> Option<*mut Self> { // SAFETY: caller contract — `this` is a live `HiveRef` slot, and // `(*this).pool` outlives every slot it hands out (`init` contract). - // Zig's `if @hasDecl(T, "deinit") this.value.deinit()` maps to `T::drop`, - // which `Fallback::put` runs (drops the whole `HiveRef` in place). + // `Fallback::put` runs `T::drop` (drops the whole `HiveRef` in place). unsafe { let ref_count = (*this).ref_count.get(); (*this).ref_count.set(ref_count - 1); @@ -941,7 +925,6 @@ mod tests { const SIZE: usize = 64; // Choose an integer with a weird alignment - // (Zig used `u127`; Rust has no arbitrary-width ints, so `u128` is the closest.) type Int = u128; let a = HiveArray::::init(); @@ -1486,5 +1469,3 @@ mod tests { assert!(unsafe { pool.box_at(999) }.is_none()); } } - -// ported from: src/collections/hive_array.zig diff --git a/src/collections/identity_context.rs b/src/collections/identity_context.rs index 0bfd37eac639..5539c7fab929 100644 --- a/src/collections/identity_context.rs +++ b/src/collections/identity_context.rs @@ -1,7 +1,6 @@ use core::marker::PhantomData; -/// Trait covering the Zig `@typeInfo(Key)` switch in `IdentityContext.hash`: -/// `.@"enum" => @intFromEnum(key)`, `.int => key`, `else => @compileError(...)`. +/// Trait for keys usable with `IdentityContext`. /// Implement for any int (`self as u64`) or `#[repr(uN)]` enum (`self as uN as u64`). pub trait IdentityHash: Copy + Eq { fn identity_hash(self) -> u64; @@ -22,7 +21,6 @@ pub struct IdentityContext(PhantomData); impl IdentityContext { pub fn hash(self, key: Key) -> u64 { - // Zig: switch (comptime @typeInfo(Key)) { .@"enum" => @intFromEnum(key), .int => key, else => @compileError } key.identity_hash() } @@ -59,8 +57,8 @@ impl ArrayIdentityContextU64 { } } -// Zig's `ArrayIdentityContext.U64` nesting — inherent assoc types are unstable, -// so expose as a free path alias instead. Callers: `identity_context::U64`. +// Inherent assoc types are unstable, so expose this as a free path alias. +// Callers: `identity_context::U64`. pub type U64 = ArrayIdentityContextU64; // ArrayHashMap requires `C: ArrayHashContext`, so wire the inherent impls @@ -88,5 +86,3 @@ impl crate::array_hash_map::ArrayHashContext for ArrayIdentityContextU64 { a == b } } - -// ported from: src/collections/identity_context.zig diff --git a/src/collections/lib.rs b/src/collections/lib.rs index 37613dca4413..4f24bb75e068 100644 --- a/src/collections/lib.rs +++ b/src/collections/lib.rs @@ -1,5 +1,4 @@ -//! bun_collections — crate root. -//! Thin re-export hub mirroring `src/collections/collections.zig`. +//! bun_collections — crate root. Thin re-export hub. #![feature( type_info, @@ -52,16 +51,15 @@ pub use bit_set::{ // from here); canonical impl lives in `bun_core::strings`. pub use bun_core::strings::{const_bytes_eq, const_str_eq}; -/// `bun.bit_set` namespace alias (Zig: `bun.bit_set.List`). +/// Namespace alias for the bit-set types. pub mod dynamic_bit_set { pub use super::bit_set::DynamicBitSet; pub use super::bit_set::DynamicBitSetList as List; } // ────────────────────────────────────────────────────────────────────────── -// `PriorityQueue` — port of `std.PriorityQueue(T, Context, lessThan)`. -// Min-heap backed by a `Vec`; the comparator context is held by value so -// callers can rebind it (Zig stores `context: Context` directly on the queue). +// `PriorityQueue` — min-heap backed by a `Vec`; the comparator context is +// held by value so callers can rebind it. // ────────────────────────────────────────────────────────────────────────── pub trait PriorityCompare { fn compare(&self, a: &T, b: &T) -> core::cmp::Ordering; @@ -98,7 +96,7 @@ impl PriorityQueue { } } impl> PriorityQueue { - /// Zig: `add(elem) !void` — push and sift-up. + /// Push and sift-up. pub fn add(&mut self, elem: T) -> Result<(), bun_alloc::AllocError> { self.items.push(elem); let mut child = self.items.len() - 1; @@ -117,7 +115,7 @@ impl> PriorityQueue { } Ok(()) } - /// Zig: `removeOrNull()` — pop min, sift-down; `None` when empty. + /// Pop min, sift-down; `None` when empty. pub fn remove_or_null(&mut self) -> Option { if self.items.is_empty() { return None; @@ -187,10 +185,10 @@ pub use bun_ptr::tagged_pointer::{TaggedPtr as TaggedPointer, TaggedPtrUnion}; pub use bun_ptr::{RawSlice, detach_lifetime, detach_ref}; // ────────────────────────────────────────────────────────────────────────── -// SmallList — `bun.SmallList(T, N)` (Zig: src/css/small_list.zig). +// SmallList // // Thin `#[repr(transparent)]` newtype over `smallvec::SmallVec<[T; N]>` that -// preserves the Zig-named API surface (`append`, `slice`, `at`, `len()->u32`, +// preserves the existing API surface (`append`, `slice`, `at`, `len()->u32`, // `init_capacity`, …) so the ~300 CSS-parser call sites stay untouched. // Replaces the bespoke ~800-line `Data`/`HeapData` union + raw-ptr container // that previously lived in `bun_css::small_list` (which was itself a port of @@ -334,7 +332,7 @@ impl SmallList { // invariant of `from_raw_parts` is never exercised. Self(unsafe { smallvec::SmallVec::from_raw_parts(slab.as_mut_ptr(), len, len) }) } - /// Zig `fromList` / `fromBabyList` — adopt a `Vec` as the heap buffer + /// Adopt a `Vec` as the heap buffer /// (O(1) header transfer; no element copy). #[inline] pub fn from_list(list: Vec) -> Self { @@ -342,7 +340,7 @@ impl SmallList { } // ── access ───────────────────────────────────────────────────────────── - /// Zig `len()` returns `u32` (not `usize`); preserved so the ~300 call-site + /// Returns `u32` (not `usize`); preserved so the ~300 call-site /// integer arithmetic in `bun_css` stays unchanged. Inherent shadows the /// `[T]::len()->usize` reachable via `Deref`. #[inline] @@ -457,13 +455,17 @@ impl SmallList { self.0.reserve_exact(new_capacity as usize - cur); } } - /// Zig `setLen` — exposed as safe for API parity with the previous port - /// (whose only external caller shrinks to 0). Growing past the initialised - /// region is the caller's responsibility, same as before. + /// Exposed as safe: every current external caller only shrinks + /// (`new_len <= len`). Growing past the initialised region is the + /// caller's responsibility. #[inline] pub fn set_len(&mut self, new_len: u32) { - // SAFETY: matches the previous bun_css::SmallList::set_len contract - // (Zig callers treat this as a raw length store). + // SAFETY: a raw length store. All current external callers only + // shrink (`new_len <= self.len()`), so `[0..new_len]` stays + // initialised and `new_len <= capacity`, satisfying + // `SmallVec::set_len`'s preconditions (the truncated tail merely + // leaks). Any caller that grows takes responsibility for + // initialising the exposed region. unsafe { self.0.set_len(new_len as usize) } } @@ -484,7 +486,7 @@ impl SmallList { Self(self.0.clone()) } - // ── iteration helpers (Zig-named) ────────────────────────────────────── + // ── iteration helpers ─────────────────────────────────────────────────── #[inline] pub fn any(&self, predicate: impl Fn(&T) -> bool) -> bool { self.0.iter().any(predicate) @@ -498,12 +500,12 @@ impl SmallList { } // ────────────────────────────────────────────────────────────────────────── -// HashMap — `std.AutoHashMap(K, V)` / `std.HashMap(K, V, Ctx, max_load)`. +// HashMap // -// Ported linear-probe layout (open-addressing, tombstones, power-of-two cap, -// 80% load) so iteration order matches Zig exactly — required by callers that +// Linear-probe layout (open-addressing, tombstones, power-of-two cap, +// 80% load) with deterministic iteration order — required by callers that // snapshot the iteration sequence (lockfile debug stringify, etc.). The `Ctx` -// type parameter is now load-bearing: `AutoHashContext` wyhashes the key, +// type parameter is load-bearing: `AutoHashContext` wyhashes the key, // `IdentityContext` uses `k as u64` so pre-hashed keys aren't re-hashed. // ────────────────────────────────────────────────────────────────────────── @@ -523,20 +525,17 @@ pub mod hash_map { pub value_ptr: &'a mut V, } - /// Zig `std.HashMap.KV` — owned `{key, value}` pair returned from - /// `fetchRemove` / `fetchPut`. Identical to `std.ArrayHashMap.KV`; re-exported - /// from `array_hash_map` rather than duplicated. + /// Owned `{key, value}` pair returned from `fetchRemove` / `fetchPut`; + /// re-exported from `array_hash_map` rather than duplicated. pub use crate::array_hash_map::KV; } pub mod array_list; -// Per PORTING.md the managed/unmanaged ArrayList split collapses to `Vec` -// (global mimalloc) outside AST crates; these aliases exist for Zig-diff parity. -pub use array_list::ArrayList; // any `std.mem.Allocator` +// All of these aliases collapse to `Vec` (global mimalloc); they exist for +// back-compat at existing call sites. +pub use array_list::ArrayList; pub use array_list::ArrayListAligned; pub use array_list::ArrayListAlignedDefault; pub use array_list::ArrayListAlignedIn; -pub use array_list::ArrayListDefault; // always default allocator (no overhead) -pub use array_list::ArrayListIn; // specific type of generic allocator - -// ported from: src/collections/collections.zig +pub use array_list::ArrayListDefault; +pub use array_list::ArrayListIn; diff --git a/src/collections/linear_fifo.rs b/src/collections/linear_fifo.rs index 8dc2636544d2..1fa7a04dcd48 100644 --- a/src/collections/linear_fifo.rs +++ b/src/collections/linear_fifo.rs @@ -1,6 +1,3 @@ -// clone of zig stdlib -// except, this one vectorizes - // FIFO of fixed size items // Usually used for e.g. byte buffers @@ -10,16 +7,14 @@ use core::ptr; use bun_alloc::AllocError; -// Zig resolves `std.heap.page_size_min` per-target; 4096 is the conservative -// minimum on every platform Bun ships on. +// 4096 is the conservative minimum page size on every platform Bun ships on. const PAGE_SIZE_MIN: usize = 4096; -/// Mirrors Zig's `LinearFifoBufferType = union(enum)`. +/// Selects the fifo's backing-storage strategy. /// -/// In the Zig original this is a *comptime* value that selects a struct layout -/// (`buf: [N]T` vs `buf: []T`, `std.mem.Allocator` param vs `void`). Rust cannot -/// branch struct layout on a const-generic enum payload, so dispatch is done -/// via the [`LinearFifoBuffer`] trait below; this enum is kept for API parity. +/// Rust cannot branch struct layout on a const-generic enum payload, so +/// dispatch is done via the [`LinearFifoBuffer`] trait below; this enum is +/// kept for API parity. pub enum LinearFifoBufferType { /// The buffer is internal to the fifo; it is of the specified size. Static(usize), @@ -29,11 +24,9 @@ pub enum LinearFifoBufferType { Dynamic, } -/// Backing-storage abstraction replacing Zig's `comptime buffer_type` switch. -/// `POWERS_OF_TWO` mirrors the Zig `powers_of_two` const inside the returned -/// struct; `DYNAMIC` mirrors `buffer_type == .Dynamic`. -// The Zig fn returns structurally different layouts per variant; trait + -// assoc-consts is the closest stable-Rust encoding. No in-tree caller +/// Backing-storage abstraction; `DYNAMIC` is true for the `.Dynamic` variant. +// Trait + assoc-consts encode the structurally different layouts per +// variant. No in-tree caller // instantiates `SliceBuffer` directly; `threading::Channel::init_slice` wraps // it for `.Slice` API parity. pub trait LinearFifoBuffer { @@ -116,8 +109,8 @@ fn poison(slice: &mut [T], n: usize) { // ── .Static ─────────────────────────────────────────────────────────────────── /// `buffer_type == .Static` — inline `[T; N]` storage. -// INVARIANT: Zig leaves the array `undefined`; we use MaybeUninit and expose -// it as &[T] via pointer cast (`assume_init_slice`). The Zig-parity public API +// INVARIANT: storage is MaybeUninit and exposed as &[T] via pointer cast +// (`assume_init_slice`). The public API // (`writable_slice` hands out `&mut [T]` over not-yet-written slots) bakes in // the same exposure for every buffer kind. Sound only for `T` whose // any-bit-pattern is valid — and in-tree element types ALREADY violate that: @@ -166,10 +159,8 @@ impl<'a, T> LinearFifoBuffer for SliceBuffer<'a, T> { // ── .Dynamic ────────────────────────────────────────────────────────────────── -/// `buffer_type == .Dynamic` — heap-allocated, growable. -/// -/// Zig stores `std.mem.Allocator` param + `buf: []T`. Per §Allocators (non-AST -/// crate) the allocator param is dropped and global mimalloc backs `Box`. +/// `buffer_type == .Dynamic` — heap-allocated, growable. Global mimalloc +/// backs the `Box`. pub struct DynamicBuffer(Box<[MaybeUninit]>); impl LinearFifoBuffer for DynamicBuffer { @@ -186,7 +177,6 @@ impl LinearFifoBuffer for DynamicBuffer { } fn realloc(&mut self, new_size: usize) -> Result<(), AllocError> { - // Zig: `self.allocator.realloc(self.buf, size)` preserving prefix. let mut new = Box::<[T]>::new_uninit_slice(new_size); let n = self.0.len().min(new_size); // SAFETY: disjoint allocations; MaybeUninit copy is always sound. @@ -204,24 +194,16 @@ impl LinearFifoBuffer for DynamicBuffer { // ── LinearFifo ──────────────────────────────────────────────────────────────── pub struct LinearFifo> { - // Zig field `allocator` is folded into `B` (or dropped) — see DynamicBuffer. buf: B, head: usize, count: usize, _marker: PhantomData, } -// Zig's `SliceSelfArg = if (.Static) *Self else Self` exists because -// returning a slice into a by-value `Self` would dangle when buf is inline. In -// Rust every accessor takes `&self`/`&mut self`, so the distinction disappears. - -// Zig exposes `pub const Reader = std.Io.GenericReader(*Self, error{}, readFn)` -// and a matching Writer. The Rust equivalents are the impls on -// `LinearFifo` after the inherent impl below: `bun_core::write::Write` -// (the canonical byte sink, re-exported as `bun_io::Write` — Zig's -// `std.Io.Writer` port), plus `std::io::Read`, `std::io::Write`, and -// `core::fmt::Write` for std interop. There is no canonical in-tree Read -// trait, so `std::io::Read` stands in for the Zig reader. +// Reader/Writer access is via the impls on `LinearFifo` after the +// inherent impl below: `bun_core::write::Write` (the canonical byte sink, +// re-exported as `bun_io::Write`), plus `std::io::Read`, `std::io::Write`, +// and `core::fmt::Write` for std interop. impl LinearFifo> { /// `init` for `.Static`. @@ -248,8 +230,7 @@ impl<'a, T> LinearFifo> { } impl LinearFifo> { - /// `init` for `.Dynamic`. Zig takes `std.mem.Allocator` param; dropped per - /// §Allocators (non-AST crate). + /// `init` for `.Dynamic`. pub fn init() -> Self { Self { buf: DynamicBuffer(Box::new([])), @@ -269,7 +250,7 @@ impl> LinearFifo { self.buf.len() } - /// Allocated capacity of the backing buffer (Zig: `fifo.buf.len`). + /// Allocated capacity of the backing buffer. /// Distinct from [`readable_length`] (live items) and /// [`writable_length`] (free slots) — `capacity == readable + writable`. /// Used by GC `memoryCost` reporting where the *allocation* size, not the @@ -281,8 +262,7 @@ impl> LinearFifo { /// Rewind `head` to 0 when the queue is empty so the next `write` can use /// the full contiguous buffer without wrapping. Perf-only micro-opt; a - /// no-op when items remain. Mirrors the `head = 0` post-drain idiom in - /// `src/jsc/Task.zig` `tickQueueWithCount`. + /// no-op when items remain. #[inline] pub fn reset_head_if_empty(&mut self) { if self.count == 0 { @@ -301,11 +281,9 @@ impl> LinearFifo { unsafe { ptr::copy(buf.as_ptr().add(head), buf.as_mut_ptr(), count) }; self.head = 0; } else { - // Zig: `var tmp: [page_size_min / 2 / @sizeOf(T)]T = undefined;` // Stable Rust cannot size a stack array by `size_of::()`, so use - // a fixed byte scratch and compute the element count at runtime. - // PERF(port): was stack array sized by page_size/2/sizeof(T) — same - // byte footprint here, no heap. + // a fixed byte scratch (page_size/2 bytes, no heap) and compute the + // element count at runtime. // // The scratch is a `[MaybeUninit; _]` (alignment 1). Reading or // writing through it as `*mut T` would violate @@ -343,7 +321,8 @@ impl> LinearFifo { { let count = self.count; let unused = &mut self.buf.as_mut_slice()[count..]; - // SAFETY: poisoning unused tail; matches Zig `@memset(unused, undefined)`. + // SAFETY: the tail past `count` is logically uninitialized; writing + // the 0xAA poison pattern there cannot invalidate live items. unsafe { ptr::write_bytes( unused.as_mut_ptr().cast::(), @@ -368,7 +347,6 @@ impl> LinearFifo { #[deprecated(note = "deprecated; call `ensure_unused_capacity` or `ensure_total_capacity`")] pub fn ensure_capacity(&mut self, _size: usize) { - // Zig: `pub const ensureCapacity = @compileError(...)` unreachable!("deprecated; call ensure_unused_capacity or ensure_total_capacity"); } @@ -380,12 +358,10 @@ impl> LinearFifo { if B::DYNAMIC { self.realign(); let new_size = if B::POWERS_OF_TWO { - // math.ceilPowerOfTwo(usize, size) catch return error.OutOfMemory size.checked_next_power_of_two().ok_or(AllocError)? } else { size }; - // Zig: alloc new, memcpy readableSlice(0) bytes, free old. let count = self.count; let old = self.buf.alloc_swap(new_size)?; if count > 0 { @@ -520,8 +496,6 @@ impl> LinearFifo { total - dst_left.len() } - // Zig `pub fn reader(self: *Self) Reader` → the `std::io::Read` impl below. - /// Returns number of items available in fifo #[inline] pub fn writable_length(&self) -> usize { @@ -609,9 +583,9 @@ impl> LinearFifo { tail %= self.buf_len(); } // SAFETY: `tail` is in-bounds (capacity reserved by caller). The slot is - // logically uninitialized — `ptr::write` matches Zig assignment semantics - // (no drop of the prior bit-pattern), required for non-`Copy` `T` whose - // backing storage is `MaybeUninit`. + // logically uninitialized — `ptr::write` does not drop the prior + // bit-pattern, which is required for non-`Copy` `T` whose backing + // storage is `MaybeUninit`. unsafe { ptr::write(self.buf.as_mut_slice().as_mut_ptr().add(tail), item) }; self.update(1); } @@ -627,10 +601,6 @@ impl> LinearFifo { Ok(()) } - // Zig `pub fn writer(self: *Self) Writer` → the `bun_core::write::Write` - // (canonical, = `bun_io::Write`), `std::io::Write`, and `core::fmt::Write` - // impls below. - /// Make `count` items available before the current read location fn rewind(&mut self, count: usize) { debug_assert!(self.writable_length() >= count); @@ -750,8 +720,7 @@ impl> LinearFifo { /// Pump data from a reader into a writer /// stops when reader returns 0 bytes (EOF) /// Buffer size must be set before calling; a buffer length of 0 is invalid. - // Zig signature: `src_reader: anytype, dest_writer: *std.Io.Writer`. The - // closure bounds are the stable-Rust encoding of those duck-typed streams: + // The closure bounds encode duck-typed streams: // `src_reader(buf)` ≙ `reader.read(buf)`, `dest_writer(buf)` ≙ // `writer.write(buf)`, both returning a count (`Ok(0)` from the reader // means EOF). This keeps `pump` generic over `T`, which `std::io` cannot. @@ -791,11 +760,11 @@ impl> LinearFifo { } } -// ── Reader/Writer adapters (Zig `reader()` / `writer()`) ───────────────────── +// ── Reader/Writer adapters ──────────────────────────────────────────────────── impl> std::io::Read for LinearFifo { - /// Zig `readFn` — drains up to `dst.len()` buffered bytes. `Ok(0)` means - /// the fifo is empty (Zig `error{}` reader: EOF, never an error). + /// Drains up to `dst.len()` buffered bytes. `Ok(0)` means + /// the fifo is empty (EOF, never an error). #[inline] fn read(&mut self, dst: &mut [u8]) -> std::io::Result { Ok(LinearFifo::read(self, dst)) @@ -803,7 +772,7 @@ impl> std::io::Read for LinearFifo { } impl> std::io::Write for LinearFifo { - /// Zig `appendWrite` — appends the whole buffer, growing if `.Dynamic`. + /// Appends the whole buffer, growing if `.Dynamic`. #[inline] fn write(&mut self, src: &[u8]) -> std::io::Result { LinearFifo::write(self, src) @@ -817,19 +786,19 @@ impl> std::io::Write for LinearFifo { } } -/// Zig `fifo.writer().print(fmt, args)` equivalent: `write!(fifo, ...)`. +/// Enables `write!(fifo, ...)`. impl> core::fmt::Write for LinearFifo { fn write_str(&mut self, s: &str) -> core::fmt::Result { LinearFifo::write(self, s.as_bytes()).map_err(|AllocError| core::fmt::Error) } } -/// Canonical in-tree byte sink (port of Zig `std.Io.Writer`, re-exported as +/// Canonical in-tree byte sink (re-exported as /// `bun_io::Write`), so a `LinearFifo` can be passed to every /// `impl bun_io::Write` consumer. `written_len` keeps its panicking default: /// a fifo drains, so it does not track total bytes written. impl> bun_core::write::Write for LinearFifo { - /// Zig `appendWrite` — appends the whole buffer, growing if `.Dynamic`. + /// Appends the whole buffer, growing if `.Dynamic`. #[inline] fn write_all(&mut self, buf: &[u8]) -> Result<(), bun_core::Error> { LinearFifo::write(self, buf)?; @@ -915,7 +884,6 @@ mod tests { fifo.shrink(0); - // Zig: `fifo.writer().print("{s}, {s}!", .{ "Hello", "World" })`. { use core::fmt::Write as _; write!(fifo, "{}, {}!", "Hello", "World").unwrap(); @@ -925,8 +893,6 @@ mod tests { assert_eq!(0usize, fifo.readable_length()); } - // Zig: `fifo.writer().writeAll(...)` then - // `fifo.reader().readUntilDelimiterOrEof(&result, ' ')` four times. { std::io::Write::write_all(&mut fifo, b"This is a test").unwrap(); let mut drained = Vec::new(); @@ -935,8 +901,8 @@ mod tests { assert_eq!(vec![&b"This"[..], b"is", b"a", b"test"], words); } - // Zig: pump from `fixedBufferStream("pump test")` into a fixed output - // buffer. The closures play the reader/writer roles. + // Pump from an in-memory reader into a fixed output buffer. The + // closures play the reader/writer roles. { fifo.ensure_total_capacity(1).unwrap(); let input: &[u8] = b"pump test"; @@ -959,7 +925,7 @@ mod tests { } } - // Shared body for the T×buffer_type matrix below (Zig's inner test block). + // Shared body for the T×buffer_type matrix below. fn run_generic_fifo_test(mut fifo: LinearFifo) where T: Copy + PartialEq + core::fmt::Debug + From, @@ -994,9 +960,7 @@ mod tests { } } - // Zig: `inline for ([_]type{u1,u8,u16,u64}) |T|` × `inline for (buffer - // types)`. `u1` has no Rust equivalent; the remaining element types are - // crossed with all three buffer kinds. + // The element types are crossed with all three buffer kinds. #[test] fn linear_fifo_generic_matrix() { macro_rules! per_type { @@ -1145,5 +1109,3 @@ mod tests { } } } - -// ported from: src/collections/linear_fifo.zig diff --git a/src/collections/multi_array_list.rs b/src/collections/multi_array_list.rs index 43061c51d035..883f3d72888b 100644 --- a/src/collections/multi_array_list.rs +++ b/src/collections/multi_array_list.rs @@ -1,10 +1,8 @@ -//! Port of `std.MultiArrayList` with the following Bun-specific additions: +//! A struct-of-arrays `MultiArrayList` with the following Bun-specific additions: //! //! * `zero` method to zero-initialize memory. //! * `memory_cost` method, which returns the memory usage in bytes. //! -//! Synchronized with std as of Zig 0.14.1. -//! //! A MultiArrayList stores a list of a struct type. Instead of storing a //! single list of items, MultiArrayList stores separate lists for each field //! of the struct. This allows for memory savings if the struct has padding, @@ -58,7 +56,7 @@ use std::alloc::{Allocator, Global}; use bun_alloc::AllocError; /// Declares typed column-accessor extension traits for a `MultiArrayList<$T>` -/// element struct, mirroring Zig's `list.items(.field)` calling convention. +/// element struct. /// /// ```ignore /// multi_array_columns! { @@ -361,7 +359,7 @@ impl Reflected { out }; - /// Zig `sizes`: `(SIZES_BYTES, SIZES_FIELDS)` — field sizes sorted by + /// `(SIZES_BYTES, SIZES_FIELDS)` — field sizes sorted by /// alignment descending, paired with the original field index at each /// sorted position. Stable sort so equal-alignment fields keep order. const SIZES: ([usize; MAX_FIELDS], [usize; MAX_FIELDS]) = { @@ -539,7 +537,6 @@ impl<'a, F> ColMut<'a, F> { } /// Index-based comparison context for `sort` / `sort_span` / `sort_unstable`. -/// Zig: `ctx: anytype` with `fn lessThan(ctx, a_index: usize, b_index: usize) bool`. pub trait SortContext { fn less_than(&self, a_index: usize, b_index: usize) -> bool; } @@ -715,8 +712,7 @@ impl Slice { /// The returned value is a **bitwise copy** — the SoA storage retains /// ownership of every field. Dropping the gathered struct would free /// columns the storage still owns (double-free on next `get` / `Drop`), - /// so it is wrapped in `ManuallyDrop`. Zig has no destructors so the - /// by-value copy is harmless there. + /// so it is wrapped in `ManuallyDrop`. pub fn get(&self, index: usize) -> ManuallyDrop { assert!( index < self.len, @@ -985,7 +981,7 @@ impl MultiArrayList { Ok(()) } - /// Alias for [`push`] (Zig: `append`). + /// Alias for [`push`]. #[inline] pub fn append(&mut self, elem: T) -> Result<(), AllocError> { self.push(elem) @@ -1278,9 +1274,9 @@ impl MultiArrayList { impl Drop for MultiArrayList { fn drop(&mut self) { - // Zig `deinit(self, gpa)`: `gpa.free(self.allocatedBytes())` — slab - // only, no per-element destructors. This is **intentionally preserved**: - // [`clone`] is a bitwise SoA memcpy (Zig semantics), so two live lists + // Frees the slab only — no per-element destructors. This is + // **intentionally preserved**: + // [`clone`] is a bitwise SoA memcpy, so two live lists // can alias the same column heap pointers — see `bundle_v2.rs` // `clone_ast` / `deinit_without_freeing_arena`, which drains exactly // one alias and relies on the other dropping slab-only. Running @@ -1296,7 +1292,7 @@ impl Drop for MultiArrayList { // ───────────────────────────── helpers ───────────────────────────── -/// `std.atomic.cache_line` — **128** on x86_64 and aarch64, all native targets. +/// Conservative cache-line size — **128** on x86_64 and aarch64, all native targets. const CACHE_LINE: usize = 128; const fn init_capacity() -> usize { @@ -1565,5 +1561,3 @@ mod tests { assert_eq!(list.items::<"c", u64>(), &[0, 10, 20, 30, 40]); } } - -// ported from: src/collections/multi_array_list.zig diff --git a/src/collections/pool.rs b/src/collections/pool.rs index 234ea38400f6..45ac577d8508 100644 --- a/src/collections/pool.rs +++ b/src/collections/pool.rs @@ -8,26 +8,13 @@ use bun_core::Error; // ────────────────────────────────────────────────────────────────────────── // SinglyLinkedList // ────────────────────────────────────────────────────────────────────────── -// -// Zig's `SinglyLinkedList(comptime T: type, comptime Parent: type)` -// threads `Parent` only so that `Node.release()` can call `Parent.release(node)`. -// In Rust the only `Parent` is `ObjectPool`, so `Node::release` is provided as -// an inherent method on `ObjectPool` instead and the `Parent` type param is -// dropped here. Diff readers: `node.release()` call sites become -// `ObjectPool::<..>::release(node)`. /// Node inside the linked list wrapping the actual data. #[repr(C)] pub struct Node { - // INTRUSIVE: pool.zig:7 — next link in singly-linked free list + // INTRUSIVE: next link in singly-linked free list pub next: *mut Node, - // Zig stored `std.mem.Allocator param` here so `destroyNode` - // could free via the originating allocator. In Rust the global mimalloc - // allocator owns every `Box>`, so the field is dropped and - // `destroy_node` uses `heap::take`. - // - // `MaybeUninit` not `T` — Zig's `else undefined` (pool.zig:203) - // is well-defined-until-read, but Rust's `assume_init()` on uninit bytes is + // `MaybeUninit` not `T`: `assume_init()` on uninit bytes is // immediate UB for any `T` with validity invariants. Callers that use // `INIT == None` write `data` before reading, so we keep the bytes // uninitialized and only `assume_init_*` at access sites. @@ -120,13 +107,10 @@ impl Node { count } - // `pub inline fn release(node: *Node) void { Parent.release(node) }` - // is expressed as `ObjectPool::::release(node)` at call sites; see - // module-level note above. } pub struct SinglyLinkedList { - // INTRUSIVE: pool.zig:59 — list head; popFirst hands node to caller + // INTRUSIVE: list head; pop_first hands node to caller pub first: *mut Node, } @@ -176,8 +160,8 @@ impl SinglyLinkedList { // SAFETY: self.first is non-null (else the `==` above would have // matched the null `node`, which callers never pass) let mut current_elm = self.first; - // SAFETY: walk live list nodes; Zig's `.?` would panic on null — - // mirror that with an unchecked deref. + // SAFETY: `node` is in this list (caller contract), so the walk + // visits only live nodes and reaches `node` before hitting null. unsafe { while (*current_elm).next != node { current_elm = (*current_elm).next; @@ -219,35 +203,28 @@ impl SinglyLinkedList { const LOG_ALLOCATIONS: bool = false; -/// Behavior hooks the Zig version expressed via `comptime Init: ?fn(...)` and -/// `std.meta.hasFn(Type, "reset")`. Per PORTING.md §Comptime reflection, -/// optional-decl checks become a trait with default methods. +/// Behavior hooks for pooled types: optional initialization and per-reuse +/// reset. pub trait ObjectPoolType: Sized { - /// Mirrors `comptime Init: ?fn(allocator) anyerror!Type`. `None` ⇒ the - /// Zig path that left `data` as `undefined`. + /// Optional initializer for freshly allocated nodes. `None` ⇒ `data` + /// starts uninitialized. const INIT: Option Result> = None; - /// Mirrors `if (std.meta.hasFn(Type, "reset")) node.data.reset()`. - /// Default is a no-op; types that had `.reset()` in Zig override this. + /// Called when a node is reused from the free list. Default is a no-op. #[inline] fn reset(&mut self) {} } -/// Per-pool mutable state. Zig's `DataStruct`. +/// Per-pool mutable state. pub struct DataStruct { pub list: SinglyLinkedList, pub loaded: bool, - // Zig used `MaxCountInt = std.math.IntFittingRange(0, max_count)`. - // Rust const generics cannot pick an integer type from a const value; use - // `usize` and accept the few extra bytes. - // PERF(port): was IntFittingRange — narrow if it shows up on a hot path. pub count: usize, } impl Default for DataStruct { fn default() -> Self { Self { - // Zig had `list: LinkedList = undefined` — we zero it. list: SinglyLinkedList::default(), loaded: false, count: 0, @@ -309,8 +286,7 @@ impl ObjectPoolTrait } /// RAII handle for a pooled `T`. Derefs to the inner value; on `Drop`, the -/// node is returned to its pool. Replaces the Zig `get()` + `defer release()` -/// pair. +/// node is returned to its pool. pub struct PoolGuard<'a, T: ObjectPoolType + 'static> { node: *mut Node, release: unsafe fn(&mut Node), @@ -385,8 +361,6 @@ where pub fn push(pooled: T) { if cfg!(debug_assertions) { - // Zig gated on `env.allow_assert`; that is - // `Environment.isDebug` ⇒ `cfg!(debug_assertions)`. debug_assert!(!Self::full()); } @@ -424,7 +398,7 @@ where unsafe { (*Self::get_node()).data.as_mut_ptr() } } - /// Zig `get()` — pop a node from the free list or allocate a fresh one. + /// Pop a node from the free list or allocate a fresh one. /// /// When `T::INIT == None` and a fresh node is allocated, the returned /// node's `data` is **uninitialized**; the caller must write a valid `T` @@ -450,8 +424,6 @@ where } if LOG_ALLOCATIONS { - // Zig wrote to stderr via std.fs (banned here); route through - // `bun_core::output` instead. let _ = bun_core::output::File::stderr().write_fmt(format_args!( "Allocate {} - {} bytes\n", core::any::type_name::(), @@ -459,7 +431,6 @@ where )); } - // Matches Zig's `data = if (Init) |i| i(..) else undefined` (pool.zig:203). // For `INIT == None` the bytes stay uninitialized; the caller MUST write // `data` before any read (and before `release()`, since `destroy_node` // assumes it is initialized when dropping). @@ -558,12 +529,6 @@ where } fn destroy_node(node: *mut Node) { - // Zig special-cased `Type != bun.Vec(u8)` here to skip - // `bun.memory.deinit(&node.data)` for `Vec` — a known leak its own - // comment calls out. Dropping `T` below is the moral equivalent of - // `bun.memory.deinit`, applied uniformly; if that compat leak is ever - // needed, gate the `Drop` on the `Vec` port itself, not here. - // // SAFETY: `node` was created via `heap::alloc` in `push`/`get` and // is exclusively owned by the caller. `data` is initialized: `destroy_node` // is only reached from `release()` (caller had a usable node, so `data` @@ -592,7 +557,7 @@ where /// `pub type $Name = ObjectPool<$T, .., $Storage>` alias. `threadsafe` ⇒ /// `thread_local!` (one free list per thread); `global` ⇒ a single /// process-wide `RefCell` (caller is responsible for not touching it from -/// multiple threads — matches the Zig `threadsafe = false` mode). +/// multiple threads). #[macro_export] macro_rules! object_pool { ($vis:vis $name:ident : $ty:ty, threadsafe, $max:expr) => { @@ -645,11 +610,9 @@ macro_rules! __object_pool_storage { fn with( f: impl FnOnce(&::core::cell::RefCell<$crate::pool::DataStruct<$ty>>) -> R, ) -> R { - // Zig's `threadsafe = false` used a plain global - // `var data`; Rust forbids non-`Sync` statics, so this still + // Rust forbids non-`Sync` statics, so the "global" mode still // expands to a thread-local. Single-threaded callers see the - // same one cell; cross-thread callers get per-thread pools - // (a slight behaviour difference, but safe). + // same one cell; cross-thread callers get per-thread pools. ::std::thread_local! { static __OBJECT_POOL_DATA: ::core::cell::RefCell< $crate::pool::DataStruct<$ty> @@ -679,8 +642,7 @@ macro_rules! __paste_storage { // is in `bun_core` (post `bun_string` merge). // ────────────────────────────────────────────────────────────────────────── -/// Zig: `Npm.Registry.BodyPool = ObjectPool(MutableString, MutableString.init2048, true, 8)` -/// (src/install/npm.zig). Init = `init2048`; reuse = `.reset()`. +/// Init = `init2048`; reuse = `.reset()`. impl ObjectPoolType for bun_core::MutableString { const INIT: Option Result> = Some(|| bun_core::MutableString::init2048().map_err(Into::into)); @@ -689,5 +651,3 @@ impl ObjectPoolType for bun_core::MutableString { bun_core::MutableString::reset(self); } } - -// ported from: src/collections/pool.zig diff --git a/src/collections/string_map.rs b/src/collections/string_map.rs index 4d7a7b2859bd..bd7ee5dc3de9 100644 --- a/src/collections/string_map.rs +++ b/src/collections/string_map.rs @@ -1,5 +1,3 @@ -//! Port of `bun.StringMap` (`src/bun.zig`). -//! //! A `StringArrayHashMap>` plus a `dupe_keys` flag controlling //! whether `insert` clones the key bytes. Values are always cloned. @@ -19,7 +17,6 @@ impl Default for StringMap { } impl StringMap { - /// Zig `init(allocator, dupe_keys)` — allocator dropped (global mimalloc). pub fn init(dupe_keys: bool) -> Self { Self { map: StringArrayHashMap::default(), @@ -49,20 +46,18 @@ impl StringMap { self.map.count() } - /// Zig `insert` / `put`: dupe `value`; dupe `key` only when `dupe_keys` - /// and the key is new. (When `dupe_keys == false` Zig stored a borrowed - /// slice; here `Box<[u8]>` forces a copy regardless — the flag is kept for - /// API parity and to skip the redundant second copy.) + /// Dupe `value`; `key` is duped on miss regardless (`Box<[u8]>` forces a + /// copy), so the `dupe_keys` flag is kept for API parity only. pub fn insert(&mut self, key: &[u8], value: &[u8]) -> Result<(), AllocError> { let entry = self.map.get_or_put(key)?; - // get_or_put already boxed `key` on miss; the Zig `dupe_keys` branch - // would dupe again here — that's the same allocation, so skip it. + // get_or_put already boxed `key` on miss; duping again here would be + // the same allocation, so skip it. let _ = self.dupe_keys; *entry.value_ptr = Box::from(value); Ok(()) } - /// Alias matching Zig `pub const put = insert;`. + /// Alias for [`insert`](Self::insert). #[inline] pub fn put(&mut self, key: &[u8], value: &[u8]) -> Result<(), AllocError> { self.insert(key, value) @@ -72,10 +67,9 @@ impl StringMap { self.map.get(key).map(|v| &**v) } - /// Zig `sort(sort_ctx)` — forwards to the inner map's order-preserving - /// sort. Zig's `anytype` ctx exposed `lessThan(a_index, b_index)`; the - /// closure receives the parallel key/value slices plus the two indices so - /// it can compare on either column. + /// Forwards to the inner map's order-preserving sort. The closure + /// receives the parallel key/value slices plus the two indices so it can + /// compare on either column. pub fn sort( &mut self, less_than: impl FnMut(&[Box<[u8]>], &[Box<[u8]>], usize, usize) -> bool, @@ -85,5 +79,3 @@ impl StringMap { // `deinit` → Drop on the inner Vecs. } - -// ported from: src/bun.zig diff --git a/src/collections/vec_ext.rs b/src/collections/vec_ext.rs index bd7328df8c3e..dc55044075ab 100644 --- a/src/collections/vec_ext.rs +++ b/src/collections/vec_ext.rs @@ -1,8 +1,8 @@ -//! `VecExt` / `ByteVecExt` — Zig-ported method vocabulary on `Vec`. +//! `VecExt` / `ByteVecExt` — extension method vocabulary on `Vec`. //! //! Migration shim from the deleted `BabyList` (see //! `docs/BABYLIST_REPLACEMENT.md`): every former `BabyList` site is now a -//! plain `Vec`, and these traits supply the Zig method names (`.slice()`, +//! plain `Vec`, and these traits supply the legacy method names (`.slice()`, //! `.append()`, `.init_capacity()`, …) so call sites needed only a type-level //! rewrite. `Vec` aborts on OOM, so these methods are infallible and return //! `T` / `()` directly (the original `Result<_, AllocError>` shim has been @@ -30,8 +30,7 @@ pub trait VecExt: Sized { fn move_from_list(list: Vec) -> Self; fn from_owned_slice(items: Box<[T]>) -> Self; fn init_with_buffer_vec(buffer: Vec) -> Self; - /// Arena-builder → owned `Vec`. In Zig this was zero-copy (arena ptr - /// adopted as `Borrowed`); in the Rust port the linker always called + /// Arena-builder → owned `Vec`. The linker always calls /// `transfer_ownership` afterwards (full copy), so doing the copy up-front /// here is no worse and lets the arena round-trip disappear. /// @@ -131,8 +130,7 @@ pub trait VecExt: Sized { unsafe fn writable_slice(&mut self, additional: usize) -> &mut [T]; /// # Safety /// As [`writable_slice`] but skips `reserve`; caller must guarantee - /// `len + additional <= capacity` (debug-asserted). Zig: - /// `ArrayList.addManyAsSliceAssumeCapacity`. + /// `len + additional <= capacity` (debug-asserted). unsafe fn writable_slice_assume_capacity(&mut self, additional: usize) -> &mut [T]; /// # Safety /// As [`writable_slice`] but uses `reserve_exact` so the allocation grows @@ -217,7 +215,7 @@ impl VecExt for Vec { #[inline] fn move_from_list(list: Vec) -> Self { // Mirror of the `move_to_list` fast-path: when `A == Global` this is a - // pointer adopt (Zig `moveFromList`, baby_list.zig:46), not a realloc. + // pointer adopt, not a realloc. // Hot Global callers: `FileReader`, `ByteStream`, `shell::Cmd`. if core::any::TypeId::of::() == core::any::TypeId::of::() { let mut list = core::mem::ManuallyDrop::new(list); @@ -568,7 +566,7 @@ impl VecExt for Vec { } } -/// `Vec`-only helpers (Zig `Vec(u8)` extension methods). +/// `Vec`-only helpers. pub trait ByteVecExt { fn append_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<(), AllocError>; fn write(&mut self, str: &[u8]) -> Result; @@ -730,9 +728,8 @@ impl OffsetByteList { /// [`bun_alloc::ArenaVec`]) does not satisfy. `src` and `dst` may use /// distinct allocators. /// -/// Ports the open-coded `reserve → ptr::copy(shift) → copy_nonoverlapping → -/// set_len` pattern that translated Zig's `bun.copy`/`@memcpy` splice for -/// non-`Copy` element types. +/// Implemented as `reserve → ptr::copy(shift) → copy_nonoverlapping → +/// set_len`, which works for non-`Copy` element types. pub fn prepend_from(dst: &mut Vec, src: &mut Vec) { let src_len = src.len(); if src_len == 0 { diff --git a/src/collections/zig_hash_map.rs b/src/collections/zig_hash_map.rs index f8753b433a82..f50a7f21a7d0 100644 --- a/src/collections/zig_hash_map.rs +++ b/src/collections/zig_hash_map.rs @@ -1,15 +1,12 @@ -//! Port of Zig's `std.HashMapUnmanaged` — open-addressing, linear-probe, -//! tombstone-on-delete, power-of-two capacity, 80% max load. Layout (and -//! therefore iteration order) must match the Zig spec exactly because callers -//! snapshot the iteration sequence (e.g. lockfile debug stringify). +//! Open-addressing hash map — linear-probe, tombstone-on-delete, power-of-two +//! capacity, 80% max load. Layout (and therefore iteration order) is +//! load-bearing: callers snapshot the iteration sequence (e.g. lockfile debug +//! stringify). //! -//! Storage differs from Zig's single-allocation `[Header][meta][keys][values]`: -//! `Vec` for metadata + `Vec>` for slots. This costs an -//! `Option` discriminant per slot but keeps the implementation in safe Rust; -//! slot indices and the metadata state machine are bit-identical so iteration -//! order matches. -//! -//! Spec: vendor/zig/lib/std/hash_map.zig +//! Storage is split: `Vec` for metadata + `Vec>` for +//! slots. This costs an `Option` discriminant per slot but keeps the +//! implementation in safe Rust; slot indices and the metadata state machine +//! determine iteration order. use core::borrow::Borrow; use core::hash::Hash; @@ -18,7 +15,6 @@ use core::marker::PhantomData; use crate::identity_context::{IdentityContext, IdentityHash}; // ─── Metadata byte ───────────────────────────────────────────────────────── -// Zig: `packed struct { fingerprint: u7, used: u1 }` — LSB-first packing, so // bit 7 = `used`, bits 0..7 = `fingerprint`. const SLOT_FREE: u8 = 0x00; // used=0, fp=0 const SLOT_TOMBSTONE: u8 = 0x01; // used=0, fp=1 @@ -39,7 +35,7 @@ fn meta_is_tombstone(m: u8) -> bool { fn meta_fingerprint(m: u8) -> u8 { m & 0x7F } -/// Zig `Metadata.takeFingerprint`: top 7 bits of the 64-bit hash. +/// Top 7 bits of the 64-bit hash. #[inline] fn take_fingerprint(hash: u64) -> u8 { (hash >> (64 - 7)) as u8 @@ -50,22 +46,20 @@ fn meta_fill(fp: u8) -> u8 { } const MINIMAL_CAPACITY: u32 = 8; -/// Zig `default_max_load_percentage`. All Bun-side `std.HashMap` instantiations -/// use 80; the const-generic load-factor parameter is dropped here. +/// Maximum load percentage before growth. All instantiations use 80, so this +/// is a const rather than a generic parameter. const MAX_LOAD_PERCENTAGE: u64 = 80; #[inline] fn capacity_for_size(size: u32) -> u32 { - // Zig: `((size * 100) / max_load + 1).ceilPowerOfTwo()` let new_cap = ((size as u64 * 100) / MAX_LOAD_PERCENTAGE + 1) as u32; new_cap.next_power_of_two() } // ─── HashContext ─────────────────────────────────────────────────────────── -// Zig threads a `Context` value with `hash(K) -> u64` / `eql(K, K) -> bool`. -// All Bun contexts are zero-sized, so model as a stateless trait keyed on the -// marker type. `AutoHashContext` covers `std.AutoHashMap`; `IdentityContext` -// covers the `hash(k) == k` case used for pre-hashed keys. +// The hash/eql strategy is a stateless trait keyed on a zero-sized marker +// type. `AutoHashContext` is the default; `IdentityContext` covers the +// `hash(k) == k` case used for pre-hashed keys. /// Hash/eql strategy for [`HashMap`]. Implement on a zero-sized marker type. pub trait HashContext { @@ -80,10 +74,8 @@ pub struct AutoHashContext; impl HashContext for AutoHashContext { #[inline] fn ctx_hash(key: &K) -> u64 { - // Zig autoHash for unique-repr types is `Wyhash.hash(0, asBytes(&key))`. - // Routing through `core::hash::Hash` + wyhash is the closest stable - // approximation without per-type byte-layout plumbing; exact AutoContext - // bucket order isn't relied on by any test today. + // wyhash routed through `core::hash::Hash`; exact bucket order for + // this context isn't relied on by any test today. bun_wyhash::auto_hash(key) } #[inline] @@ -133,7 +125,6 @@ impl HashMap { Self::default() } - /// Zig `count`/std `len`. #[inline] pub fn len(&self) -> usize { self.size as usize @@ -151,13 +142,13 @@ impl HashMap { self.metadata.len() } - /// Zig `deinit` — release storage. + /// Release storage. #[inline] pub fn deinit(&mut self) { *self = Self::default(); } - /// Zig `clearRetainingCapacity`. + /// Clear all entries, retaining capacity. pub fn clear(&mut self) { if self.metadata.is_empty() { return; @@ -172,12 +163,12 @@ impl HashMap { self.available = ((self.metadata.len() as u64 * MAX_LOAD_PERCENTAGE) / 100) as u32; } - /// Zig `lockPointers` — debug-mode pointer-stability assertion. No-op stub - /// kept so the Zig lock/unlock bracketing translates without `#[cfg]` noise - /// at every call site (see `SavedSourceMap`). + /// Debug-mode pointer-stability assertion. No-op stub kept so callers can + /// keep their lock/unlock bracketing without `#[cfg]` noise at every call + /// site (see `SavedSourceMap`). #[inline] pub fn lock_pointers(&self) {} - /// Zig `unlockPointers` — see [`lock_pointers`]. + /// See [`lock_pointers`](Self::lock_pointers). #[inline] pub fn unlock_pointers(&self) {} @@ -219,9 +210,8 @@ impl> HashMap { m } - /// Zig `ensureTotalCapacity` — grow so `new_size` elements fit without - /// further allocation. `Result` kept for call-site `?` symmetry with the - /// Zig OOM-propagating API. + /// Grow so `new_size` elements fit without further allocation. `Result` + /// kept for call-site `?` symmetry. pub fn ensure_total_capacity(&mut self, new_size: usize) -> Result<(), bun_alloc::AllocError> { let new_size = new_size as u32; if new_size > self.size { @@ -230,7 +220,6 @@ impl> HashMap { Ok(()) } - /// Zig `ensureUnusedCapacity`. pub fn ensure_unused_capacity( &mut self, additional: usize, @@ -293,8 +282,7 @@ impl> HashMap { *self = map; } - /// Zig `putAssumeCapacityNoClobber` — linear-probe insert assuming key - /// absent and `available > 0`. + /// Linear-probe insert assuming the key is absent and `available > 0`. fn put_assume_capacity_no_clobber(&mut self, key: K, value: V) { let cap = self.metadata.len(); let hash = C::ctx_hash(&key); @@ -314,7 +302,7 @@ impl> HashMap { self.size += 1; } - /// Zig `getIndex` — probe for `key`, stop on free, skip tombstones. + /// Probe for `key`, stop on free, skip tombstones. fn get_index(&self, key: &Q) -> Option where K: Borrow, @@ -385,13 +373,13 @@ impl> HashMap { self.get_index(key).is_some() } - /// Zig `contains` — `std::HashMap` spells this `contains_key`. + /// Alias for [`contains_key`](Self::contains_key). #[inline] pub fn contains(&self, key: &K) -> bool { self.get_index(key).is_some() } - /// Zig `getOrPutAssumeCapacityAdapted` lifted to grow-on-demand. Returns + /// Grow-on-demand insert-or-lookup probe. Returns /// the slot index and whether it was already occupied; the slot is left /// `None` on miss so the caller can write the (K, V) pair. fn get_or_put_slot(&mut self, key: &K) -> (usize, bool) { @@ -428,10 +416,9 @@ impl> HashMap { (idx, false) } - /// Zig `getOrPut`: single-probe insert-or-lookup. On miss the value slot is - /// left "undefined" in Zig; Rust cannot expose uninit through a `&mut V`, so - /// `V: Default` and the slot is default-initialised — callers overwrite - /// `*value_ptr` when `!found_existing`. + /// Single-probe insert-or-lookup. Uninit values cannot be exposed through + /// a `&mut V`, so `V: Default` and on miss the slot is default-initialised + /// — callers overwrite `*value_ptr` when `!found_existing`. pub fn get_or_put( &mut self, key: K, @@ -450,8 +437,8 @@ impl> HashMap { }) } - /// Zig `getOrPutContext` — alias kept for call-site parity; the context is - /// already bound by the type parameter. + /// Alias of [`get_or_put`](Self::get_or_put) kept for call-site parity; + /// the context is already bound by the type parameter. #[inline] pub fn get_or_put_context( &mut self, @@ -464,7 +451,7 @@ impl> HashMap { self.get_or_put(key) } - /// std `insert` / Zig `fetchPut` — returns the previous value if any. + /// std `insert` — returns the previous value if any. pub fn insert(&mut self, key: K, value: V) -> Option { let (idx, found_existing) = self.get_or_put_slot(&key); if found_existing { @@ -476,14 +463,14 @@ impl> HashMap { } } - /// Zig `put`: insert or overwrite. + /// Insert or overwrite. #[inline] pub fn put(&mut self, key: K, value: V) -> Result<(), bun_alloc::AllocError> { self.insert(key, value); Ok(()) } - /// Zig `putNoClobber`: insert asserting the key is new. + /// Insert asserting the key is new. pub fn put_no_clobber(&mut self, key: K, value: V) -> Result<(), bun_alloc::AllocError> { let prev = self.insert(key, value); debug_assert!(prev.is_none(), "putNoClobber: key already present"); @@ -498,7 +485,7 @@ impl> HashMap { kv } - /// std `remove` / Zig `fetchRemove` value half. + /// std `remove`. pub fn remove(&mut self, key: &Q) -> Option where K: Borrow, @@ -519,7 +506,7 @@ impl> HashMap { self.get_index(key).and_then(|i| self.remove_by_index(i)) } - /// Zig `fetchRemove` — remove and return the owned `{key, value}` pair. + /// Remove and return the owned `{key, value}` pair. pub fn fetch_remove(&mut self, key: &K) -> Option> { self.remove_entry(key) .map(|(k, v)| crate::hash_map::KV { key: k, value: v }) @@ -719,5 +706,3 @@ impl<'a, K, V, C: HashContext> VacantEntry<'a, K, V, C> { &self.key } } - -// ported from: vendor/zig/lib/std/hash_map.zig diff --git a/src/crash_handler/CPUFeatures.rs b/src/crash_handler/CPUFeatures.rs index 5628f371aa3b..5736ea904335 100644 --- a/src/crash_handler/CPUFeatures.rs +++ b/src/crash_handler/CPUFeatures.rs @@ -10,9 +10,8 @@ pub(crate) struct CPUFeatures { pub flags: Flags, } -// Zig: `packed struct(u8)` per-arch. All semantic fields are `bool`; the trailing -// `padding: uN = 0` is unused bits. bitflags! models this directly (unknown bits -// = padding). Bit order matches Zig packed-struct LSB-first layout. +// Per-arch packed `u8`. All semantic fields are single bits; bitflags! models +// this directly (unknown bits = padding). Bit order is LSB-first. #[cfg(target_arch = "x86_64")] bitflags::bitflags! { @@ -48,8 +47,7 @@ bitflags::bitflags! { #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] compile_error!("CPUFeatures: unsupported target architecture"); -// Zig `inline for (@typeInfo(Flags).@"struct".fields)` — comptime reflection over -// field names, skipping "none" and "padding". Expanded to a const table per arch. +// Per-arch const table of flag names, skipping "none" and padding bits. #[cfg(target_arch = "x86_64")] const NAMED_FLAGS: &[(&str, Flags)] = &[ ("sse42", Flags::SSE42), @@ -105,8 +103,6 @@ impl CPUFeatures { #[cfg(target_arch = "x86_64")] { - // Zig: bun.analytics.Features.no_avx / no_avx2 are global mutable - // counters (`+= usize`). Rust port stores them as `AtomicUsize`. use core::sync::atomic::Ordering; bun_analytics::features::no_avx .fetch_add(usize::from(!flags.contains(Flags::AVX)), Ordering::Relaxed); @@ -117,5 +113,3 @@ impl CPUFeatures { CPUFeatures { flags } } } - -// ported from: src/crash_handler/CPUFeatures.zig diff --git a/src/crash_handler/handle_oom.rs b/src/crash_handler/handle_oom.rs index cc5bff36d005..ac295d2fc0a9 100644 --- a/src/crash_handler/handle_oom.rs +++ b/src/crash_handler/handle_oom.rs @@ -1,14 +1,10 @@ use bun_alloc::AllocError; use bun_core::Error; -// fn isOomOnlyError(comptime ErrorUnionOrSet: type) bool -// -// Zig's `isOomOnlyError` is pure comptime `@typeInfo` reflection over an -// error set: it iterates the set's members and checks every name == "OutOfMemory". -// Rust has no error-set reflection. The equivalent is encoded structurally in -// the `HandleOom` trait impls below — the `AllocError` impls ARE the -// "OOM-only" arm (Output = T / Output = !), and the `bun_core::Error` impls -// ARE the "other errors possible" arm (Output = Result / Output = E). +// "OOM-only" vs "other errors possible" is encoded structurally in the +// `HandleOom` trait impls below — the `AllocError` impls ARE the "OOM-only" +// arm (Output = T / Output = !), and the `bun_core::Error` impls ARE the +// "other errors possible" arm (Output = Result / Output = E). /// If `error_union_or_set` is `error.OutOfMemory`, calls `bun.outOfMemory`. Otherwise: /// @@ -38,10 +34,8 @@ pub fn handle_oom(error_union_or_set: A) -> A::Output { error_union_or_set.handle_oom() } -/// Encodes Zig's comptime return-type block (`return_type: { ... }`) of -/// `handleOom`. The Zig branched on `@typeInfo(ArgType)` (error_union vs -/// error_set) and on `isOomOnlyError(ArgType)`; each impl below is one arm of -/// that comptime switch. +/// Output-type selection for [`handle_oom`]: each impl below is one arm of +/// the input-shape × OOM-only matrix (see the section comments). pub trait HandleOom { type Output; fn handle_oom(self) -> Self::Output; @@ -69,8 +63,6 @@ impl HandleOom for AllocError { } // ── .error_union, mixed error set → same union with OOM subtracted ─────── -// Zig computed the narrowed type via -// `@TypeOf(switch (err) { error.OutOfMemory => unreachable, else => |e| e })`. // Rust error enums are nominal, not sets — there is no set subtraction. For // the catch-all `bun_core::Error` we compare against the interned tag and // return the same type. Per-crate `thiserror` enums that carry an @@ -97,5 +89,3 @@ impl HandleOom for Error { } } } - -// ported from: src/crash_handler/handle_oom.zig diff --git a/src/crash_handler/lib.rs b/src/crash_handler/lib.rs index 73066e7b056a..8415ecf32c0a 100644 --- a/src/crash_handler/lib.rs +++ b/src/crash_handler/lib.rs @@ -14,9 +14,6 @@ //! users from having to download symbols, which can be very large. //! //! The remapper is open source: https://github.com/oven-sh/bun.report -//! -//! A lot of this handler is based on the Zig Standard Library implementation -//! for std.debug.panicImpl and their code for gathering backtraces. // The cfg is the union of the two intrinsic call sites: `abort()` on the // non-Windows crash path (all profiles) and `breakpoint()` on Windows debug @@ -34,8 +31,7 @@ pub mod handle_oom; /// Link-time target for `bun_alloc::out_of_memory()` — declared /// `extern "Rust"` in `bun_alloc` (which is below this crate in the dep graph) -/// and defined here. Mirrors `src/bun.zig:outOfMemory()` → -/// `crash_handler.crashHandler(.out_of_memory, null, @returnAddress())`. +/// and defined here. /// `pub(crate)` so external callers route through the T0 `bun_alloc` entry /// rather than bypassing it. #[cold] @@ -70,8 +66,7 @@ pub use draft::*; // ────────────────────────────────────────────────────────────────────────── // Local shim for `bun_debug` (no such crate exists yet). These are -// std.debug.* placeholders the Zig side leaned on; the Rust port will replace -// them with a real debug-info backend in a later pass. +// placeholders to be replaced with a real debug-info backend in a later pass. // ────────────────────────────────────────────────────────────────────────── pub mod debug { use super::draft::StackTrace; @@ -84,14 +79,14 @@ pub mod debug { bun_core::return_address() } - /// Zig: `std.debug.captureStackTrace`. Thin re-export of the canonical safe + /// Thin re-export of the canonical safe /// wrapper in bun_core so this crate's internal callers don't churn. #[inline] pub(crate) fn capture_stack_trace(begin: usize, addrs: &mut [usize]) -> usize { bun_core::capture_stack_trace(begin, addrs) } - /// Zig: `std.debug.panicImpl` fallback when ENABLE == false. + /// Fallback when ENABLE == false. pub fn panic_impl(_ert: Option<&StackTrace<'_>>, _begin: Option, msg: &[u8]) -> ! { panic!("{}", bstr::BStr::new(msg)) } @@ -99,8 +94,8 @@ pub mod debug { pub(crate) const HAVE_ERROR_RETURN_TRACING: bool = false; pub(crate) const STRIP_DEBUG_INFO: bool = !cfg!(debug_assertions); - // ── SelfInfo (vendor/zig/lib/std/debug/SelfInfo.zig) ───────────────── - // D104: canonical home for the dladdr-backed `std.debug.SelfInfo` shim. + // ── SelfInfo ────────────────────────────────────────────────────────── + // D104: canonical home for the dladdr-backed self-debug-info shim. // Previously lived in `bun_jsc::btjs::zig_std_debug`; relocated here so the // crash handler (lower-tier crate) gets real symbol names in debug builds // and `btjs` re-exports from this module. @@ -117,14 +112,11 @@ pub mod debug { address_map: HashMap>, } - /// Port of `SelfInfo.Module`. On Linux Zig uses `Dwarf.ElfModule`; on Darwin a - /// MachO symbol table reader. Both ultimately resolve `address → {name, CU, - /// source_location}`. The DWARF/MachO parsers are not ported; `dladdr(3)` + /// A loaded module, resolving `address → {name, CU, source_location}`. + /// There is no DWARF/MachO parser; `dladdr(3)` /// provides the symbol-name half (which is what `btjs` actually consumes for /// its `__`/`_llint_call_javascript` prefix checks). `source_location` is left /// `None`, which `print_line_info` already handles. - // The full `readElfDebugInfo`/`readMachODebugInfo` (~2k LOC of DWARF) are not - // ported — `dladdr` is the libc-level equivalent for symbol-name resolution. pub struct Module { base_address: usize, name: Box<[u8]>, @@ -222,8 +214,7 @@ pub mod debug { #[cfg(target_vendor = "apple")] fn lookup_module_dyld(&mut self, address: usize) -> Result<&mut Module, Error> { - // Zig walks `_dyld_get_image_header` + LoadCommandIterator. `dladdr` - // gives the same `{base_address, fname}` pair on Darwin without the MachO walk. + // `dladdr` gives the `{base_address, fname}` pair on Darwin without a MachO walk. let mut info: libc::Dl_info = bun_core::ffi::zeroed(); // SAFETY: dladdr only reads; out-param is a valid Dl_info. let rc = unsafe { libc::dladdr(address as *const c_void, &raw mut info) }; @@ -252,12 +243,10 @@ pub mod debug { /// Port of `Module.getSymbolAtAddress`. #[cfg(windows)] pub fn get_symbol_at_address(&mut self, address: usize) -> Result { - // SPEC DIVERGENCE — Zig's `std.debug.SelfInfo` - // resolves symbols on Windows via the loaded PE's PDB - // (`dbghelp.dll` `SymFromAddr`). That path is not yet ported, so + // Windows symbol resolution via the loaded PE's PDB + // (`dbghelp.dll` `SymFromAddr`) is not implemented yet, so // every Windows backtrace currently prints bare addresses even - // when a PDB is shipped. This is NOT equivalent to the Zig spec - // for symbol-bearing builds; return the default-initialized + // when a PDB is shipped. Return the default-initialized // `Symbol` (`name = "???"`) so the caller still prints the // address line, but the dbghelp lookup must be implemented // before Windows crash reports are usable. @@ -276,7 +265,7 @@ pub mod debug { // SAFETY: dladdr only reads; out-param is a valid Dl_info. let rc = unsafe { libc::dladdr(address as *const c_void, &raw mut info) }; if rc == 0 || info.dli_sname.is_null() { - // Zig returns a default-initialized `Symbol` (`.{}` — name "???") here + // Return a default-initialized `Symbol` (name "???") here // rather than erroring, so the caller still prints the address line. return Ok(SymbolInfo { name: b"???".to_vec().into_boxed_slice(), @@ -326,18 +315,17 @@ pub mod debug { Some(bun_paths::basename(name).to_vec().into_boxed_slice()) } - // ── std.debug.getSelfDebugInfo ─────────────────────────────────────── + // ── self debug-info singleton ──────────────────────────────────────── // PORTING.md §Global mutable state: lazy debug-only singleton. RacyCell — // only called from a stopped/crashing process (lldb or the crash handler // after `panicking` has serialized), so no concurrent access; callers // reborrow the returned `*mut` per-access. static SELF_DEBUG_INFO: bun_core::RacyCell> = bun_core::RacyCell::new(None); - /// Port of `std.debug.getSelfDebugInfo`. NOT thread-safe (the Zig original - /// has the same `TODO multithreaded awareness` caveat). + /// NOT thread-safe. pub fn get_self_debug_info() -> Result<*mut SelfInfo, Error> { - // SAFETY: Zig's `var self_debug_info: ?SelfInfo = null` is also a plain - // mutable global; this is debug-only and invoked from a stopped process. + // SAFETY: this is debug-only and invoked from a stopped/crashing + // process (see SELF_DEBUG_INFO above), so no concurrent access. unsafe { let slot = &mut *SELF_DEBUG_INFO.get(); if let Some(info) = slot { @@ -347,7 +335,7 @@ pub mod debug { Ok(std::ptr::from_mut(slot.as_mut().unwrap())) } } - /// Zig: `std.io.tty.detectConfig(std.io.getStdErr())`. + /// Detect whether stderr supports ANSI color escapes. #[allow(dead_code)] pub(crate) fn detect_tty_config_stderr() -> TtyConfig { if bun_core::Output::ENABLE_ANSI_COLORS_STDERR.load(core::sync::atomic::Ordering::Relaxed) { @@ -356,7 +344,7 @@ pub mod debug { TtyConfig::NoColor } } - /// Port of `std.io.tty.Config` (vendor/zig/lib/std/Io/tty.zig). The + /// TTY color configuration. A /// `windows_api` variant is omitted: every consumer here writes into an /// in-memory buffer or raw fd 2, never the live `CONSOLE_SCREEN_BUFFER`, so /// `SetConsoleTextAttribute` would colour the wrong stream. @@ -365,7 +353,7 @@ pub mod debug { NoColor, EscapeCodes, } - /// Port of `std.io.tty.Color` — only the variants Bun actually emits. + /// Terminal colors — only the variants Bun actually emits. #[derive(Clone, Copy, PartialEq, Eq)] pub enum Color { Bold, @@ -377,7 +365,7 @@ pub mod debug { BrightCyan, } impl TtyConfig { - /// Port of `std.io.tty.Config.setColor`. + /// Write the escape sequence for `color` (no-op when colors are disabled). pub fn set_color( self, w: &mut W, @@ -422,8 +410,7 @@ impl Write for StderrWriter { fn write_all(&mut self, bytes: &[u8]) -> Result<(), bun_core::Error> { #[cfg(windows)] { - // Zig spec: `std.fs.File.stderr().writerStreaming(&.{})` — on - // Windows that is `GetStdHandle(STD_ERROR_HANDLE)` + kernel32 + // On Windows this is `GetStdHandle(STD_ERROR_HANDLE)` + kernel32 // `WriteFile`, NOT the CRT. Routing through MSVCRT `_write(2,…)` // would (1) text-mode-translate `\n`→`\r\n` and (2) take the CRT // per-fd lock, which can self-deadlock when the VEH crash handler @@ -499,15 +486,15 @@ mod draft { e } - /// Zig: `Output.enable_ansi_colors_stderr` — runtime flag, exposed in Rust as an - /// `AtomicBool` static. Re-exported here so call sites read like the Zig. + /// Runtime flag, exposed as an + /// `AtomicBool` static. Re-exported here for shorter call sites. use bun_core::output::enable_ansi_colors_stderr; - /// Zig: `std.posix.abort()`. On POSIX this is `libc::abort()` (async-signal-safe). - /// On Windows, Zig's `std.posix.abort()` is *not* MSVCRT `abort()` — it is - /// `if (Debug) @breakpoint(); kernel32.ExitProcess(3);`. UCRT `abort()` would + /// On POSIX this is `libc::abort()` (async-signal-safe). + /// On Windows this is *not* MSVCRT `abort()` — it is + /// `if (Debug) breakpoint(); kernel32.ExitProcess(3);`. UCRT `abort()` would /// raise SIGABRT, may print `R6010 - abort() has been called` to stderr, and - /// can pop a Watson/WER dialog — none of which the Zig spec does. + /// can pop a Watson/WER dialog — none of which we want here. #[inline(always)] fn abort() -> ! { #[cfg(windows)] @@ -525,8 +512,8 @@ mod draft { use super::cpu_features::CPUFeatures; use super::debug::{Color, SelfInfo, SourceLocation, TtyConfig}; - /// Zig: `bun.fmt.fmtArgv` — print an argv vector as a shell-ish line. - /// crash_handler.zig:1024 calls this when the addr2line spawn fails. + /// Print an argv vector as a shell-ish line. + /// Called when the addr2line spawn fails. #[cfg(any(windows, target_os = "linux", target_os = "android"))] fn fmt_argv(w: &mut W, argv: &[Vec]) -> Result<(), bun_core::Error> { for (i, a) in argv.iter().enumerate() { @@ -566,7 +553,7 @@ mod draft { } } - // std.builtin.StackTrace lives in bun_core (T0); the debug-info types are local + // `StackTrace` lives in bun_core (T0); the debug-info types are local // shims (see `super::debug`) until a real bun_debug crate exists. pub use bun_core::StackTrace; @@ -619,7 +606,7 @@ mod draft { // PORTING.md §Concurrency: `bun_threading::Guarded>` instead of bare Mutex + global Vec. // Stores a boxed type-erased closure (not a bare fn pointer) so that // `append_pre_crash_handler` can monomorphize a wrapper that actually invokes the - // caller's typed handler — mirroring Zig's `comptime handler` trampoline. + // caller's typed handler. struct CrashHandlerEntry(*mut c_void, Box); // SAFETY: only accessed under the mutex; the opaque ptr is never dereferenced // except by the registered callback on the crash thread. @@ -784,7 +771,7 @@ mod draft { } /// RAII guard returned by [`scoped_action`] / [`set_current_action_resolver`]. - /// Restores the previous `CURRENT_ACTION` on drop (Zig: `defer current_action = old`). + /// Restores the previous `CURRENT_ACTION` on drop. pub struct ActionGuard(Option); impl Drop for ActionGuard { #[inline] @@ -795,8 +782,7 @@ mod draft { /// Scoped `CURRENT_ACTION = action`. Snapshots the previous value, installs /// `action`, and returns an [`ActionGuard`] that restores the previous value - /// on drop. Zig: `const old = current_action; defer current_action = old; - /// current_action = ...;`. + /// on drop. #[inline] #[must_use] pub fn scoped_action(action: Action) -> ActionGuard { @@ -805,9 +791,9 @@ mod draft { ActionGuard(prev) } - /// Scoped `CURRENT_ACTION = .resolver{...}`. Zig (resolver.zig:672-679) sets - /// this only under `Environment.show_crash_trace` because module resolution is - /// extremely hot and has a low crash rate; the cfg-gate here mirrors that. + /// Scoped `CURRENT_ACTION = Resolver{...}`. Set + /// only under `Environment.show_crash_trace` because module resolution is + /// extremely hot and has a low crash rate. /// /// `source_dir`/`import_path` are caller-interned (DirnameStore / source text) /// and outlive the guard; the `&'static` lifetime erasure matches the existing @@ -845,7 +831,7 @@ mod draft { /// pointers from `fp` (POSIX) / RtlCapture and trim by `pc` (Windows). `pc` /// becomes frame 0. Fault { pc: usize, fp: usize }, - /// A trace was already captured upstream (Zig error return traces). + /// A trace was already captured upstream. ErrorReturn(&'a StackTrace<'a>), /// Walk the current stack and trim the capture machinery above this PC. BeginAddr(usize), @@ -885,7 +871,7 @@ mod draft { // // Output.errorWriter() is not used here because it may not be configured // if the program crashes immediately at startup. - // Zig: std.fs.File.stderr().writerStreaming — a local raw StderrWriter is used + // A local raw StderrWriter is used // because bun_sys's FileWriter only impls std::io::Write, not the local byte-Write trait. let writer = &mut stderr_writer(); @@ -1050,8 +1036,7 @@ mod draft { abort(); } // NOTE: `GetThreadDescription` heap-allocates `name` and the - // caller is meant to `LocalFree` it. The Zig spec leaks it - // identically (crash_handler.zig:316-322) — this runs on a + // caller is meant to `LocalFree` it. This runs on a // `noreturn` crash path immediately before `ExitProcess(3)`, // so the leak is intentional. } else { @@ -1266,7 +1251,7 @@ mod draft { // so that a crash will actually crash. We need this because we want the process to // exit with a signal, and allow tools to be able to gather core dumps. // - // This is done so late (in comparison to the Zig Standard Library's panic handler) + // This is done this late // because if multiple threads segfault (more often the case on Windows), we don't // want another thread to interrupt the crashing of the first one. reset_segfault_handler(); @@ -1320,12 +1305,12 @@ mod draft { crash(); } - /// This is called when `main` returns a Zig error. + /// This is called when `main` returns an error. /// We don't want to treat it as a crash under certain error codes. pub fn handle_root_error(err: bun_core::Error, error_return_trace: Option<&StackTrace>) -> ! { use bun_core::{err_generic, pretty_error}; - /// Zig: `std.posix.getrlimit(.NOFILE)`. bun_sys::posix has no rlimit yet — + /// bun_sys::posix has no rlimit yet — /// thin libc wrapper (POD out-param, never fails on supported targets). #[cfg(unix)] fn getrlimit_nofile() -> Option { @@ -1445,7 +1430,7 @@ mod draft { } } else if err == bun_core::err!("NotOpenForReading") || err == bun_core::err!("Unexpected") { - // The usage of `unreachable` in Zig's std.posix may cause the file descriptor problem to show up as other errors + // The file descriptor problem may show up as other errors #[cfg(unix)] { // SAFETY: zeroed rlimit is valid POD (integers). @@ -1502,7 +1487,7 @@ mod draft { err_generic!("Bun could not find a package.json file to install from"); bun_core::note!("Run \"bun init\" to initialize a project"); } else { - // Zig picked the format string at comptime; the macros need + // The macros need // `:literal`, so branch on the const and call separately. if Environment::SHOW_CRASH_TRACE { err_generic!( @@ -1651,7 +1636,7 @@ mod draft { #[cfg(unix)] extern "C" fn handle_segfault_posix(sig: c_int, info: *mut libc::siginfo_t, ctx: *mut c_void) { // SAFETY: kernel provides a valid siginfo_t; `si_addr` reads the per-platform - // sigfault address field (Zig: `info.fields.sigfault.addr` / `info.addr`). + // sigfault address field. let addr: usize = unsafe { (*info).si_addr() as usize }; crash_handler( @@ -1727,7 +1712,6 @@ mod draft { if Environment::ENABLE_ASAN { return; } - // Zig: std.posix.Sigaction{ .handler = .{ .sigaction = handleSegfaultPosix }, ... }. // SAFETY: zeroed sigaction is valid POD; we overwrite the fields we need. let mut act: libc::sigaction = bun_core::ffi::zeroed(); act.sa_sigaction = handle_segfault_posix as *const () as usize; @@ -1786,15 +1770,14 @@ mod draft { // (`__bun_crash_handler_out_of_memory` / `__bun_crash_handler_dump_stack_trace`) // — no runtime registration needed. // - // Route Rust `panic!()` through the trace-string + report path. Zig wires - // `pub const panic = bun.crash_handler.panic` at the root so every - // `@panic()` reports; the Rust port's bare `panic!` was printing the std - // default hook + unwinding with no trace string and no upload. + // Route Rust `panic!()` through the trace-string + report path. Without + // this hook, a bare `panic!` would print the std + // default hook + unwind with no trace string and no upload. std::panic::set_hook(Box::new(rust_panic_hook)); } /// `std::panic` hook: emit the same trace-string + auto-report as the fatal - /// `crash_handler()` path, then **abort** (matches Zig's `noreturn` panic). + /// `crash_handler()` path, then **abort**. /// With `panic = "abort"` no unwind starts after this hook returns, so there /// are no `catch_unwind` boundaries to reach. #[cold] @@ -1816,8 +1799,7 @@ mod draft { PANIC_STAGE.with(|s| s.set(1)); // Just the panic message — no `(file:line:col)` suffix. The call site is - // captured in the backtrace and symbolized there (matching Zig, which - // never appended a location to the message). With `-Zlocation-detail=none` + // captured in the backtrace and symbolized there. With `-Zlocation-detail=none` // in release the location would be `:0:0` anyway. let mut msg_buf = BoundedArray::::default(); { @@ -1930,15 +1912,13 @@ mod draft { // A Rust `panic!` is a bug. The process must not continue — with // `panic = "abort"` no unwind starts, so `catch_unwind` boundaries are - // unreachable for Rust panics. This matches Zig's - // `pub const panic = bun.crash_handler.panic` (which is `noreturn`). + // unreachable for Rust panics. crash(); } /// Adapter for non-fatal `bun_core::dump_current_stack_trace` callers - /// (fd.rs EBADF debug-warn, ref_count leak reports). Zig routes these through - /// `dumpStackTrace` which on Linux debug spawns `llvm-symbolizer` — but the - /// Rust debug binary's .debug_info is large enough that the symbolizer parse + /// (fd.rs EBADF debug-warn, ref_count leak reports). The + /// debug binary's .debug_info is large enough that an llvm-symbolizer parse /// alone costs ~5s, which is unacceptable on a hot non-fatal path /// (`closeSync(EBADF)` was timing out fs.test.ts at the 5s budget). For these /// advisory dumps we honour `frame_count` and use WTF's dladdr-based printer @@ -2037,7 +2017,7 @@ mod draft { bun_sys::windows::EXCEPTION_ILLEGAL_INSTRUCTION => { // `ExceptionAddress` is the faulting RIP for `STATUS_ILLEGAL_ // INSTRUCTION` (winnt.h); avoids depending on the arch-specific - // `CONTEXT` layout (Zig reached `ContextRecord.Rip` directly). + // `CONTEXT` layout. CrashReason::IllegalInstruction( unsafe { (*info.ExceptionRecord).ExceptionAddress } as usize ) @@ -2245,8 +2225,7 @@ mod draft { ) .map_err(fmt_err)?; - // Zig used `{B:<3.2}`; bun_fmt::bytes() output differs only in spacing - // and adaptive precision — human-readable metadata, not the trace string. + // bun_fmt::bytes() — human-readable metadata, not the trace string. write!( writer, "RSS: {} | Peak: {} | Commit: {} | Faults: {}", @@ -2309,10 +2288,8 @@ mod draft { impl Platform { // Rust cannot concat ident names at const time without a proc-macro; spell out the cfg matrix. const CURRENT: u8 = { - // Android folds into the Linux variants — Zig's `@tagName(Environment.os)` - // (crash_handler.zig:1153) yields `"linux"` for Android because Zig keeps - // it under `os.tag == .linux`. bun.report decodes the same single-char - // codes; introducing new ones would break older decoders. + // Android folds into the Linux variants. bun.report decodes the same + // single-char codes; introducing new ones would break older decoders. #[cfg(all( any(target_os = "linux", target_os = "android"), target_arch = "x86_64", @@ -2386,7 +2363,7 @@ mod draft { /// '2' - same as '1' but this build is known to be a canary build const VERSION_CHAR: &str = if Environment::IS_CANARY { "2" } else { "1" }; - // Zig: `if (git_sha.len > 0) git_sha[0..7] else "unknown"` — the v1/v2 trace-string + // The v1/v2 trace-string // format encodes exactly 7 hex chars. `Environment::GIT_SHA_SHORT` is 9 chars and would // shift every following VLQ byte, making bun.report unable to decode the URL. const GIT_SHA: &str = { @@ -2407,7 +2384,7 @@ mod draft { address: i32, // None -> from bun.exe object: Option>, - // Zig stores a borrowed slice into caller's `name_bytes`; Box<[u8]> here + // Box<[u8]> rather than a borrowed slice into caller's `name_bytes`, // since the only caller writes into a stack buffer and the value is consumed immediately. } @@ -2427,7 +2404,6 @@ mod draft { return Some(StackLine { // To remap this, `pdb-addr2line --exe bun.pdb 0x123456` - // Zig: `@intCast(addr - base_address)` — unchecked in ReleaseFast. // Use a wrapping cast so an oversize/underflowed module offset // produces a junk frame instead of panicking *inside* the crash // handler (which would escalate to a double-panic and lose the @@ -2436,7 +2412,7 @@ mod draft { object: if name != image_path.as_slice() { // GetModuleFileNameW output never has a trailing separator - // or bare drive prefix, so the std.fs.path.basenameWindows + // or bare drive prefix, so `basename_windows`'s // stripping is a no-op on this domain. let basename = bun_paths::basename_windows(name); Some(Box::<[u8]>::from( @@ -2449,8 +2425,6 @@ mod draft { } #[cfg(target_os = "macos")] { - // This code is slightly modified from std.debug.DebugInfo.lookupModuleNameDyld - // https://github.com/ziglang/zig/blob/215de3ee67f75e2405c177b262cb5c1cd8c8e343/lib/std/debug.zig#L1783 let address = if addr == 0 { 0 } else { addr - 1 }; let image_count = bun_sys::c::_dyld_image_count(); @@ -2533,8 +2507,6 @@ mod draft { } #[cfg(not(any(windows, target_os = "macos")))] { - // This code is slightly modified from std.debug.DebugInfo.lookupModuleDl - // https://github.com/ziglang/zig/blob/215de3ee67f75e2405c177b262cb5c1cd8c8e343/lib/std/debug.zig#L2024 let _ = name_bytes; let address = addr.saturating_sub(1); let m = bun_sys::elf::find_loaded_module(address)?; @@ -2721,7 +2693,7 @@ mod draft { writer: &mut impl Write, addr: usize, ) -> Result<(), bun_core::Error> { - // @bitCast(@as(u32, ...)) → reinterpret u32 as i32 + // `as u32 as i32` reinterprets the 32-bit halves, preserving bits. let first = VLQ::encode((((addr as u64) & 0xFFFFFFFF00000000) >> 32) as u32 as i32); let second = VLQ::encode(((addr as u64) & 0xFFFFFFFF) as u32 as i32); writer.write_all(first.slice())?; @@ -2810,7 +2782,6 @@ mod draft { cmd_line.append_slice_assume_capacity(bun_core::w!( "powershell -ExecutionPolicy Bypass -Command \"try{Invoke-RestMethod -Uri '" )); - // PERF(port): was assume_capacity { // `unused_capacity_slice` is `&mut [MaybeUninit]`; // `from_raw_parts_mut::` over that storage would assert the @@ -2865,8 +2836,7 @@ mod draft { // we don't care what happens with the process // NOTE: on success `CreateProcessW` returns two open kernel handles in // `process.hProcess` / `process.hThread` that the caller is meant to - // `CloseHandle`. The Zig spec leaks them identically (crash_handler.zig: - // 1545-1546 `_ = spawn_result;`); `report()` runs immediately before + // `CloseHandle`. `report()` runs immediately before // `crash()` → `ExitProcess(3)`, so the kernel reclaims them anyway. let _ = spawn_result; let _ = url; @@ -2944,7 +2914,6 @@ mod draft { #[cfg(not(windows))] { // Install default handler so that the tkill below will terminate. - // Zig: std.posix.Sigaction{ .handler = SIG.DFL, .mask = sigemptyset(), .flags = 0 }. // bun_sys::posix has no Sigaction yet — use libc directly (async-signal-safe). // SAFETY: all-zero is a valid sigaction (handler = SIG_DFL = 0, flags = 0). let mut sigact: libc::sigaction = bun_core::ffi::zeroed(); @@ -2967,9 +2936,9 @@ mod draft { libc::sigaction(sig, &raw const sigact, core::ptr::null_mut()); } } - // Zig: `@trap()` — emits ud2 (x86_64 → SIGILL) / brk (aarch64 → SIGTRAP). - // `core::intrinsics::abort()` lowers to the same trap instruction, preserving - // the Zig exit signal. Do NOT use `libc::abort()` here — that raises SIGABRT + // `core::intrinsics::abort()` lowers to a trap instruction — + // ud2 (x86_64 → SIGILL) / brk (aarch64 → SIGTRAP). + // Do NOT use `libc::abort()` here — that raises SIGABRT // (exit 134), which is the *Windows* path's behaviour. core::intrinsics::abort(); } @@ -2978,9 +2947,8 @@ mod draft { // Node.js exits with code 134 (128 + SIGABRT) instead. We use abort() as it // includes a breakpoint which makes crashes easier to debug. // - // Zig spec (crash_handler.zig:1592): the `.windows` arm is literally - // `std.posix.abort();` — i.e. our same-module `abort()` helper, which on - // Windows is `@breakpoint()` (Debug only) then `kernel32.ExitProcess(3)`. + // The same-module `abort()` helper on + // Windows is `breakpoint()` (Debug only) then `kernel32.ExitProcess(3)`. // Do NOT call MSVCRT `libc::abort()` here — that raises SIGABRT, may print // the CRT `abort() has been called` message, and can invoke WER. abort() @@ -2995,7 +2963,7 @@ mod draft { err_int_workaround_for_zig_ccall_bug: u16, trace: &StackTrace, ) { - // Zig passed std.meta.Int(.unsigned, @bitSizeOf(anyerror)); bun_core::Error + // bun_core::Error // is errno-based, so the error round-trips through its u16 representation. let err = bun_core::Error::from_errno(err_int_workaround_for_zig_ccall_bug as i32); @@ -3056,7 +3024,7 @@ mod draft { maybe_trace: Option<&StackTrace>, ) { // Rust has no error-return tracing; `HAVE_ERROR_RETURN_TRACING` is const - // false, so this path is currently dead and kept for parity with the Zig spec. + // false, so this path is currently dead. if !debug::HAVE_ERROR_RETURN_TRACING { return; } @@ -3112,7 +3080,7 @@ mod draft { // SAFETY: lazy debug-only singleton; sole `&mut` for the dump below. Ok(d) => unsafe { &mut *d }, Err(err) => { - // Zig: `stderr.print(..) catch return;` — if stderr write fails + // If the stderr write fails // (e.g. broken pipe), bail out entirely; don't fall through. if write!(stderr, "Unable to dump stack trace: Unable to open debug info: {}\nFallback trace:\n", bstr::BStr::new(err.name())).is_err() { return; } break 'attempt_dump; @@ -3198,7 +3166,6 @@ mod draft { ] }; for &program in programs { - // PERF(port): was arena bulk-free + StackFallbackAllocator — using global allocator here. // Only stop once a symbolizer actually ran and exited 0. Any failure // (not found, spawn error, or non-zero exit) tries the next program and // ultimately falls through to the WTF fallback below — a found-but-broken @@ -3209,8 +3176,7 @@ mod draft { } } let _ = limits; - // INTENTIONAL DIVERGENCE from Zig spec (crash_handler.zig:1749-1760 falls - // off the end of the `for (programs)` loop with no further fallback). On + // On // Windows, `spawn_sync_inherit` is stubbed and `pdb-addr2line` is rarely // installed, so without this the user would get *only* "Fallback trace:" // and nothing else. Hand the raw addresses to WTF (always linked) so there @@ -3234,8 +3200,7 @@ mod draft { argv.push({ #[cfg(windows)] { - // `to_utf8_alloc` is infallible (Vec); the Zig version returned - // `![]u8` only for OOM, which Rust handles via abort. + // `to_utf8_alloc` is infallible (Vec); OOM aborts. let image_path = strings::to_utf8_alloc(bun_sys::windows::exe_path_w()); let mut s = image_path[0..image_path.len() - 3].to_vec(); s.extend_from_slice(b"pdb"); @@ -3297,7 +3262,7 @@ mod draft { pub fn suppress_core_dumps_if_necessary() { #[cfg(unix)] { - // Zig: std.posix.getrlimit / setrlimit. bun_sys::posix has no rlimit + // bun_sys::posix has no rlimit // surface yet — go straight to libc (already a dep, async-signal-safe). // SAFETY: all-zero rlimit is valid POD; getrlimit/setrlimit only read/write the struct. let mut existing_limit: libc::rlimit = bun_core::ffi::zeroed(); @@ -3339,9 +3304,8 @@ mod draft { ptr: *mut T, handler: fn(&mut T) -> Result<(), bun_core::Error>, ) -> Result<(), bun_alloc::AllocError> { - // Zig monomorphizes a `wrap.onCrash` that casts the opaque ptr back to *T and calls - // `handler`. Rust can't capture `handler` in a bare `fn` item, so box a closure that - // performs the same cast+call. Errors are intentionally swallowed (best-effort dump). + // Rust can't capture `handler` in a bare `fn` item, so box a closure that + // performs the cast+call. Errors are intentionally swallowed (best-effort dump). let on_crash = Box::new(move |opaque_ptr: *mut c_void| { // SAFETY: `opaque_ptr` is the `ptr.cast()` stored below; it was a valid *mut T // when registered and remove_pre_crash_handler() unregisters it before drop. @@ -3372,14 +3336,12 @@ mod draft { pub source_location: Option, pub symbol_name: Box<[u8]>, pub compile_unit_name: Box<[u8]>, - // Zig stores borrowed slices owned by debug_info; Box<[u8]> here. } - // Zig's `SourceAtAddress.deinit` only freed `source_location.file_name`; - // `Option` owns it as `Box<[u8]>` so Drop handles it — no explicit deinit. + // `Option` owns its file name as `Box<[u8]>` so Drop handles it — no explicit deinit. // D130: deduped — canonical def lives in bun_core (T0). Re-export under the - // Zig-spec name so internal use-sites and any downstream + // old name so internal use-sites and any downstream // `bun_crash_handler::WriteStackTraceLimits` importers keep compiling. pub use bun_core::DumpStackTraceOptions as WriteStackTraceLimits; @@ -3402,7 +3364,7 @@ mod draft { .index .min(stack_trace.instruction_addresses.len()); - // Zig's `while (...) : ({ frames_left -= 1; frame_index = ... })` continue-expression + // The `frames_left -= 1; frame_index = ...` continue-expression // is inlined at every `continue` site and at end-of-loop below. while frames_left != 0 { if frame_index >= limits.frame_count { @@ -3537,7 +3499,6 @@ mod draft { compile_unit_name: &[u8], tty_config: TtyConfig, ) -> Result<(), bun_core::Error> { - // Zig: `Environment.base_path ++ std.fs.path.sep_str` (comptime concat). // `Environment::BASE_PATH` is `&[u8]`, which `const_format::concatcp!` cannot // ingest. The constant is tiny and this path is debug-only — build it once // at runtime in a stack BoundedArray (no heap, async-signal-safe). @@ -3705,7 +3666,6 @@ mod draft { } break 'read_line; } - // unreachable in Zig (`return;` after the if/else above) } let line_without_newline = strings::trim_right(&line_buf[..fbs_len], b"\n"); if source_location.column as usize > line_without_newline.len() { @@ -3774,7 +3734,7 @@ mod draft { if env_var::feature_flag::BUN_INTERNAL_SUPPRESS_CRASH_ON_UV_STUB::get() == Some(true) { suppress_reporting(); } - // SAFETY: name is non-null (Zig dereferences it unconditionally with `.?`) + // SAFETY: per the caller contract above, `name` is a valid NUL-terminated C string (non-null). let name_bytes = unsafe { bun_core::ffi::cstr(name) }.to_bytes(); // PORTING.md §Forbidden: no Box::leak. We're on the noreturn path, so a stack // buffer suffices — `panic_impl` erases to &'static for the abort path. @@ -3826,7 +3786,5 @@ mod draft { pub fn fix_dead_code_elimination() { bun_core::keep_symbols!(CrashHandler__unsupportedUVFunction); } - // In Zig: comptime { _ = &Bun__crashHandler; ... } — Rust links #[no_mangle] symbols unconditionally. + // Rust links #[no_mangle] symbols unconditionally. } // end mod draft - -// ported from: src/crash_handler/crash_handler.zig diff --git a/src/csrf/lib.rs b/src/csrf/lib.rs index 873fe781f3f4..06ff64b7e174 100644 --- a/src/csrf/lib.rs +++ b/src/csrf/lib.rs @@ -28,8 +28,8 @@ bun_core::impl_tag_error!(Error); bun_core::named_error_set!(Error); -/// Options for generating CSRF tokens. Zig had per-field defaults (noted on -/// each field); Rust callers must specify all fields. +/// Options for generating CSRF tokens. Defaults are noted on +/// each field; callers must specify all fields. pub struct GenerateOptions<'a> { /// Secret key to use for signing pub secret: &'a [u8], @@ -44,8 +44,8 @@ pub struct GenerateOptions<'a> { pub algorithm: Algorithm, // = DEFAULT_ALGORITHM } -/// Options for validating CSRF tokens. Zig had per-field defaults (noted on -/// each field); Rust callers must specify all fields. +/// Options for validating CSRF tokens. Defaults are noted on +/// each field; callers must specify all fields. pub struct VerifyOptions<'a> { /// The token to verify pub token: &'a [u8], @@ -163,7 +163,7 @@ pub fn verify(options: &VerifyOptions<'_>) -> bool { // Reshaped for borrowck — compute decoded_len, then borrow buf immutably afterward let decoded_len: usize = match encoding { - // shares same decoder but encoder is different see encoding.zig + // base64 and base64url share the same decoder, but the encoders differ TokenFormat::Base64Url | TokenFormat::Base64 => { // do the same as Buffer.from(token, "base64url" | "base64") // "\r\n\t " ++ VT (0x0b) @@ -275,8 +275,4 @@ pub fn verify(options: &VerifyOptions<'_>) -> bool { boring::constant_time_eq(received_signature, signature) } -// NOTE: the Zig file re-exports csrf__generate / csrf__verify from -// ../runtime/api/csrf_jsc.zig — per PORTING.md these *_jsc aliases are -// deleted; JS bindings live in the *_jsc crate as extension methods. - -// ported from: src/csrf/csrf.zig +// NOTE: JS bindings live in the *_jsc crate as extension methods. diff --git a/src/css/compat.rs b/src/css/compat.rs index 9626e55d8031..2b556c4ddc72 100644 --- a/src/css/compat.rs +++ b/src/css/compat.rs @@ -1,5 +1,4 @@ // This file is autogenerated by build-prefixes.js. DO NOT EDIT! -// (Rust port: per-browser min-version tables emitted from src/css/compat.zig.) use crate::targets::Browsers; @@ -5472,8 +5471,8 @@ impl Feature { /// Returns whether *any* of the given browser targets supports this /// feature natively. pub fn is_partially_compatible(self, targets: &Browsers) -> bool { - // Generic implementation in terms of `is_compatible`, mirroring - // compat.zig:5327-5394 — probe each browser field one at a time. + // Generic implementation in terms of `is_compatible` — + // probe each browser field one at a time. macro_rules! probe { ($field:ident) => { if targets.$field.is_some() { @@ -5497,5 +5496,3 @@ impl Feature { false } } - -// ported from: src/css/compat.zig diff --git a/src/css/context.rs b/src/css/context.rs index 5b734f03b8c9..0c8c53dff0bc 100644 --- a/src/css/context.rs +++ b/src/css/context.rs @@ -18,8 +18,7 @@ pub struct SupportsEntry { pub important_declarations: Vec, } -// Zig's `deinit(this, arena)` is deleted — all fields own their storage and drop -// automatically. `css.deepDeinit` over the Vecs is handled by `Vec`'s Drop. +// No explicit deinit — all fields own their storage and drop automatically. #[derive(Copy, Clone, PartialEq, Eq)] pub enum DeclarationContext { @@ -135,7 +134,6 @@ impl<'a> PropertyHandlerContext<'a> { let mut dest: Vec> = Vec::with_capacity(self.supports.len()); for entry in &self.supports { - // PERF(port): was appendAssumeCapacity dest.push(css::CssRule::Supports(css::SupportsRule { condition: entry.condition.deep_clone(self.arena), rules: css::CssRuleList { @@ -231,9 +229,7 @@ impl<'a> PropertyHandlerContext<'a> { dest } - // Reshaped — Zig passed `comptime dir: []const u8` and `comptime decls: []const u8` - // and used `@field` to select the Direction variant and the self.ltr/self.rtl Vec by name. - // Rust has no @field; pass the Direction value and a borrow of the decls Vec directly. + // Takes the Direction value and a borrow of the decls Vec directly. pub fn get_additional_rules_helper( &self, dir: css::selector::parser::Direction, @@ -327,13 +323,13 @@ impl<'a> PropertyHandlerContext<'a> { } let fallbacks = unparsed.value.get_fallbacks(bump, &self.targets); - // Zig `for (fallbacks.slice()) |c|` copies by value; `SmallList` + // `SmallList` // has no `IntoIterator`, so spill to a Vec to preserve P3-before-LAB order. for condition_and_fallback in fallbacks.to_owned_slice().into_vec() { self.add_conditional_property( condition_and_fallback.0, css::Property::Unparsed(UnparsedProperty { - // `PropertyId` is `Copy`; Zig `deepClone` was identity. + // `PropertyId` is `Copy`. property_id: unparsed.property_id, value: condition_and_fallback.1, }), @@ -341,5 +337,3 @@ impl<'a> PropertyHandlerContext<'a> { } } } - -// ported from: src/css/context.zig diff --git a/src/css/css_modules.rs b/src/css/css_modules.rs index de30c4b15628..2fa394cd183a 100644 --- a/src/css/css_modules.rs +++ b/src/css/css_modules.rs @@ -35,7 +35,6 @@ impl<'a> CssModule<'a> { let source: &[u8] = 'source: { // Make paths relative to project root so hashes are stable if let Some(root) = project_root { - // Zig: `bun.path.Platform.auto.isAbsolute(root)` if bun_paths::is_absolute(root) { alloced = true; break 'source bump.alloc_slice_copy( @@ -45,9 +44,8 @@ impl<'a> CssModule<'a> { } break 'source path.as_ref(); }; - // Zig `defer if (alloced) arena.free(source);` — arena-allocated, bulk-freed on bump.reset() + // `source` is arena-allocated, bulk-freed on bump.reset() let _ = alloced; - // PERF(port): was appendAssumeCapacity — profile if it shows up on a hot path hashes.push(hash( bump, format_args!("{}", bstr::BStr::new(source)), @@ -58,7 +56,6 @@ impl<'a> CssModule<'a> { }; let exports_by_source_index = 'exports_by_source_index: { let mut exports_by_source_index = BumpVec::with_capacity_in(sources.len(), bump); - // PERF(port): was appendNTimesAssumeCapacity — profile if it shows up on a hot path for _ in 0..sources.len() { exports_by_source_index.push(CssModuleExports::default()); } @@ -73,12 +70,9 @@ impl<'a> CssModule<'a> { } } - // Zig's `deinit` was a no-op; Drop is implicit. No `impl Drop` needed. - pub fn get_reference(&mut self, bump: &'a Bump, name: &'a [u8], source_index: u32) { - // Zig `getOrPut` returns an uninitialized value slot; // bun_collections::ArrayHashMap::get_or_put requires `V: Default` - // (CssModuleExport can't be Default — BumpVec field). Reshaped to the + // (CssModuleExport can't be Default — BumpVec field), so use the // entry()-API instead. use bun_collections::array_hash_map::MapEntry; match self.exports_by_source_index[source_index as usize].entry(name) { @@ -101,8 +95,7 @@ impl<'a> CssModule<'a> { } } - // Zig `referenceDashed` took `*Printer` so it could read - // `dest.arena` and call `dest.importRecord(idx)`. In Rust the only + // This does not take `&mut Printer`: the only // caller (`DashedIdentReference::to_css`) already holds a `&mut` borrow of // `dest.css_module` (which *is* `self`), so threading `&mut Printer` in // here would alias. The caller pre-resolves the import-record path and @@ -133,9 +126,8 @@ impl<'a> CssModule<'a> { } None => { // Local export. Mark as used. - // Zig `getOrPut` returns an uninitialized value - // slot; `CssModuleExport` cannot be `Default` (BumpVec field), - // so reshape to the `entry()` API like `get_reference` above. + // `CssModuleExport` cannot be `Default` (BumpVec field), + // so use the `entry()` API like `get_reference` above. use bun_collections::array_hash_map::MapEntry; match self.exports_by_source_index[source_index as usize].entry(name) { MapEntry::Occupied(mut o) => { @@ -172,8 +164,8 @@ impl<'a> CssModule<'a> { false, ); - // std.fmt.allocPrint(arena, "--{s}", .{the_hash}) → bump Vec - // (bumpalo::Vec lacks io::Write; the format string was a pure concat anyway). + // Build `--{the_hash}` as a bump Vec — a plain concat + // (`bumpalo::Vec` lacks `io::Write`, and no formatting is needed). let mut k = BumpVec::with_capacity_in(2 + the_hash.len(), bump); k.extend_from_slice(b"--"); k.extend_from_slice(the_hash); @@ -477,10 +469,8 @@ impl<'a> CssModuleReference<'a> { /// (a leaf crate) so `bun_bundler::LinkerContext::mangle_local_css` can call /// the *same* hasher without depending on `bun_css`. Re-export here so /// in-crate callers (`dependencies.rs`, `rules/import.rs`) keep the -/// `css_modules::hash` path from the Zig spec. +/// `css_modules::hash` path. #[inline] pub fn hash<'a>(bump: &'a Bump, args: Arguments<'_>, at_start: bool) -> &'a [u8] { bun_base64::wyhash_url_safe(bump, args, at_start) } - -// ported from: src/css/css_modules.zig diff --git a/src/css/css_parser.rs b/src/css/css_parser.rs index 740b0fefb61c..cb3a3cb5304e 100644 --- a/src/css/css_parser.rs +++ b/src/css/css_parser.rs @@ -1,8 +1,7 @@ -//! CSS parser — port of `src/css/css_parser.zig`. +//! CSS parser. //! //! This is an AST crate (see PORTING.md §Allocators): allocations are -//! arena-backed in the Zig original. The Rust port keeps `&'bump Bump` -//! threading where it matters and drops `Allocator` params elsewhere. +//! arena-backed, with `&'bump Bump` threaded where it matters. use bun_alloc::ArenaVecExt as _; use core::fmt; @@ -15,8 +14,7 @@ use bun_core::strings; // ───────────────────────────── re-exports ───────────────────────────── // -// The Zig css_parser hub re-exports the entire crate surface. The Rust port -// keeps that shape: cross-module re-exports + parser core live here. +// Cross-module re-exports + parser core live here. /// `bun.ast.Index` — bundler source-file index. Hoisted into /// `bun_options_types` to keep css below the parser tier. @@ -127,9 +125,6 @@ mod gated_shims { } } -// `Maybe` in Zig is `bun.jsc.Node.Maybe` — a tagged result. In Rust we use -// `core::result::Result` directly; callers `.ok()`/`.err()` instead of -// `.asValue()`/`.asErr()`. pub use core::result::Result as Maybe; // PrintErr is hoisted at crate root (single-variant `to_css` error signal); @@ -139,9 +134,6 @@ pub use crate::PrintErr; #[cold] #[inline(never)] pub fn oom(_e: bun_core::Error) -> ! { - if cfg!(debug_assertions) { - // Zig: assert(e == Allocator.Error.OutOfMemory) - } bun_core::out_of_memory(); } @@ -204,10 +196,6 @@ impl SourceLocation { } } - // Note: Zig used `anytype` + `@TypeOf` to dispatch on - // `ParserError | BasicParseError | SelectorParseErrorKind`. In Rust this - // becomes a trait `IntoParserError` implemented by each live variant - // (the `BasicParseError` arm is dead/ill-typed in Zig — see note below). pub fn new_custom_error(self, err: impl IntoParserError) -> ParseError { ParseError { kind: errors_::ParserErrorKind::custom(err.into_parser_error()), @@ -216,8 +204,7 @@ impl SourceLocation { } } -/// Dispatch trait for `SourceLocation::new_custom_error` (replaces Zig -/// `anytype` switch on `@TypeOf`). +/// Dispatch trait for `SourceLocation::new_custom_error`. pub trait IntoParserError { fn into_parser_error(self) -> ParserError; } @@ -227,25 +214,18 @@ impl IntoParserError for ParserError { self } } -// Note: Zig's `newCustomError` had a third `@TypeOf` arm for -// `BasicParseError`, but that arm is dead and ill-typed — it wraps -// `BasicParseError.intoDefaultParseError(err)` (a `ParseError(ParserError)`) -// in `.custom`, which expects a `ParserError`. No caller ever passes -// `BasicParseError`, so Zig's lazy comptime never instantiates it. We -// intentionally do NOT impl `IntoParserError` for `BasicParseError` here. +// We intentionally do NOT impl `IntoParserError` for `BasicParseError` here: +// no caller ever passes one. // `SelectorParseErrorKind` is impl'd in `selectors/parser.rs`. pub type Error = Err; pub type CssResult = Maybe>; -// Zig: `pub fn Result(comptime T: type) type { return Maybe(T, ParseError(ParserError)); }` -// Rust callers use `CssResult` directly. pub type PrintResult = Maybe; #[cold] pub fn todo(msg: &str) -> ! { - // Zig: `bun.analytics.Features.todo_panic = 1;` bun_core::Global::features::TODO_PANIC.store(1, core::sync::atomic::Ordering::Relaxed); panic!("TODO: {msg}"); } @@ -259,28 +239,21 @@ pub fn void_wrap( move |(), p| parsefn(p) } -// ───────────────────────── Derive*-style comptime helpers ───────────────────────── +// ───────────────────────── Derive*-style helpers ───────────────────────── // -// The Zig file defines `DefineListShorthand`, `DefineShorthand`, -// `DefineRectShorthand`, `DefineSizeShorthand`, `DeriveParse`, `DeriveToCss`, -// `DefineEnumProperty`, `DeriveValueType` — all of which use `@typeInfo` / -// `@field` comptime reflection to generate `parse`/`toCss`/etc. for arbitrary -// types. PORTING.md §Comptime reflection: the protocol becomes a trait +// PORTING.md §Comptime reflection: each protocol is a trait // (`ToCss`, `Parse`, `EnumProperty`, ...) and per-type impls are generated by // a `#[derive(...)]` proc-macro. We declare the traits here and stub the // helper bodies that callers in other files reference. /// Shorthand longhand-reconstruction helpers. /// -/// Note: Zig's `DefineShorthand` bodies are `@compileError(todo_stuff.depth)` -/// — i.e. instantiating the comptime fn and reaching any method is a compile -/// error. The faithful Rust mapping is a trait with **no default bodies**: any -/// `impl DefineShorthand for T` that omits a method fails at compile time, same -/// as the Zig. Per-type bodies are emitted by `#[derive(DefineShorthand)]` -/// using the (currently commented-out) `PropertyFieldMap`/`VendorPrefixMap` -/// reflection algorithm in `css_parser.zig` lines 316–500. +/// Note: this trait has **no default bodies**: any `impl DefineShorthand for T` +/// that omits a method fails at compile time. Per-type bodies are emitted by +/// `#[derive(DefineShorthand)]` using the (currently commented-out) +/// `PropertyFieldMap`/`VendorPrefixMap` reflection algorithm. pub trait DefineShorthand: Sized { - /// The shorthand's own `PropertyIdTag` (Zig: `comptime property_name`). + /// The shorthand's own `PropertyIdTag`. const PROPERTY_NAME: PropertyIdTag; /// Returns a shorthand from the longhand properties defined in the given @@ -317,8 +290,8 @@ pub trait DefineShorthand: Sized { fn set_longhand(&mut self, property: &Property) -> bool; } -// Note: Zig's `DefineListShorthand` / `DefineRectShorthand` / -// `DefineSizeShorthand` / `DeriveParse` / `DeriveToCss` comptime fns became +// Note: `DefineListShorthand` / `DefineRectShorthand` / `DefineSizeShorthand` +// / `DeriveParse` / `DeriveToCss` are // proc-macros (`bun_css_derive::*`, re-exported below) plus the // `impl_rect_shorthand!` / `impl_size_shorthand!` macros in // `properties/margin_padding.rs`. The placeholder trait stubs that previously @@ -350,12 +323,12 @@ pub mod enum_property_util { } } -// Derive macros for the comptime helpers above. Re-exported here as well as -// at crate root because some leaf modules alias `crate::css_parser as css` -// (Zig's `css.DefineEnumProperty(...)` lived in this file). +// Derive macros for the helpers above. Re-exported here as well as +// at crate root because some leaf modules alias `crate::css_parser as css`. pub use bun_css_derive::{DefineEnumProperty, Parse, ToCss}; -/// Replaces Zig's `DefineEnumProperty` comptime fn. +/// Keyword-enum CSS properties: case-insensitive parse from an ident plus a +/// canonical string form. pub trait EnumProperty: Sized + Copy + Into<&'static str> { fn from_ascii_case_insensitive(ident: &[u8]) -> Option; @@ -384,16 +357,15 @@ pub trait EnumProperty: Sized + Copy + Into<&'static str> { where Self: Into, { - // Zig hashed the enum's native-width tag bytes; the width only changes - // the hash value, which never leaves the process, so a fixed u32 is fine. + // The hash value never leaves the process, so a fixed u32 tag width is fine. let tag: u32 = (*self).into(); hasher.update(&tag.to_ne_bytes()); } } -/// `DeriveValueType` — maps each enum variant to a `MediaFeatureType`. Zig -/// used a comptime `ValueTypeMap`; the Rust impls are written by hand (see -/// `media_query.rs`, `rules/container.rs`), so no derive macro is needed. +/// `DeriveValueType` — maps each enum variant to a `MediaFeatureType`. Impls +/// are written by hand (see `media_query.rs`, `rules/container.rs`), so no +/// derive macro is needed. pub trait DeriveValueType { fn value_type(&self) -> MediaFeatureType; } @@ -405,8 +377,7 @@ pub trait DeriveValueType { /// (the block is unclosed). #[cold] fn consume_until_end_of_block(block_type: BlockType, tokenizer: &mut Tokenizer) -> bool { - // PERF(port): was SmallList + appendAssumeCapacity — Vec is - // fine for the cold path. + // Vec is fine for the cold path. let mut stack: Vec = Vec::with_capacity(16); stack.push(block_type); @@ -491,7 +462,7 @@ fn parse_custom_at_rule_prelude( } } - // TODO(port): lifetime — `name` borrows the input arena. The detach is the + // TODO: lifetime — `name` borrows the input arena. The detach is the // same `'static` erasure already applied to `Token`/`AtRulePrelude::Unknown`. // SAFETY: `name` points into the parser's source/arena, which outlives every // `AtRulePrelude`/warning produced from this parser (see `src_str`). @@ -558,13 +529,10 @@ fn parse_until_before( parse_fn: impl FnOnce(C, &mut Parser) -> CssResult, ) -> CssResult { let delimiters = parser.stop_before | delimiters_; - // Note: reshaped for borrowck — Zig held `parser.input` aliased - // between the outer Parser and a stack-local "delimited" Parser. In Rust // `&'a mut ParserInput<'a>` is invariant and cannot be reborrowed into a - // second `Parser<'a>` while the first lives. We instead temporarily swap + // second `Parser<'a>` while the first lives, so temporarily swap // `stop_before` on the *same* Parser, run the inner parse, and restore. - // `at_start_of` is *moved into* the inner parse (Zig moved it into the - // delimited Parser and left the outer null) — since we reuse the same + // `at_start_of` is moved into the inner parse — since we reuse the same // Parser it carries through unchanged, and is consumed/cleared below // rather than restored. let saved_stop_before = parser.stop_before; @@ -573,9 +541,7 @@ fn parse_until_before( let result = parser.parse_entirely(closure, parse_fn); if matches!(error_behavior, ParseUntilErrorBehavior::Stop) && result.is_err() { parser.stop_before = saved_stop_before; - // Match Zig: the delimited parser *moved* `at_start_of` out of the - // outer parser (`parser.at_start_of = null;`). Since we reuse the - // same Parser, explicitly clear it so the caller doesn't observe a + // Explicitly clear `at_start_of` so the caller doesn't observe a // stale block-start left behind by the failing inner parse. parser.at_start_of = None; return result; @@ -717,9 +683,6 @@ fn parse_nested_block( } // ───────────────────────── parser-protocol traits ───────────────────────── -// -// Zig used `ValidQualifiedRuleParser(T)` etc. as comptime duck-type checks -// (`@hasDecl`). PORTING.md: trait bounds ARE that check. /// Qualified rules are rules that apply styles to elements in a document. pub trait QualifiedRuleParser { @@ -794,9 +757,8 @@ pub trait CustomAtRuleParser { fn bump_anon_layer_count(this: &mut Self, amount: i32); /// Move the registered `@layer` names accumulated via `on_layer_rule` out - /// of the parser. The Zig spec only populates `StyleSheet.layer_names` - /// when `P == BundlerAtRuleParser` (css_parser.zig:3324); Rust can't - /// type-specialize at the call site, so this is a trait hook with a + /// of the parser. Only `BundlerAtRuleParser` populates + /// `StyleSheet.layer_names`; this is a trait hook with a /// default no-op for parsers that don't track layers. fn take_layer_names(_this: &mut Self) -> Vec { Vec::new() @@ -874,15 +836,11 @@ impl CustomAtRuleParser for DefaultAtRuleParser { pub const ENABLE_TAILWIND_PARSING: bool = false; pub type BundlerAtRule = DefaultAtRule; -// Zig: `if (ENABLE_TAILWIND_PARSING) TailwindAtRule else DefaultAtRule` -// (css_parser.zig:1345); the flag is false, so only the DefaultAtRule form is -// ported. pub struct BundlerAtRuleParser<'a> { pub arena: &'a Bump, /// Raw pointer aliasing the same `Vec` that `Parser.import_records` - /// points to (Zig passes one `*Vec` to both — see `parseBundler`, - /// css_parser.zig:3245). Both views are raw pointers sharing a single + /// points to. Both views are raw pointers sharing a single /// SharedRW provenance (see `parse_bundler`); each materialises a /// short-lived `&mut` only at the point of use, so accesses interleave /// soundly under Stacked Borrows. @@ -896,9 +854,6 @@ pub struct BundlerAtRuleParser<'a> { } impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { - // Zig gates `Prelude = union(enum) { tailwind: TailwindAtRule }` on - // ENABLE_TAILWIND_PARSING (false; css_parser.zig:1368) — only the void - // form is ported. type Prelude = (); type AtRule = BundlerAtRule; @@ -908,8 +863,6 @@ impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { input: &mut Parser, _: &ParserOptions, ) -> CssResult { - // Zig's tailwind branch is comptime-gated on ENABLE_TAILWIND_PARSING - // (false; css_parser.zig:1374) — only the error path ships. Err(input.new_error(BasicParseErrorKind::at_rule_invalid(name))) } @@ -931,8 +884,6 @@ impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { _: &ParserOptions, _: bool, ) -> Maybe { - // Zig's tailwind branch is comptime-gated on ENABLE_TAILWIND_PARSING - // (false; css_parser.zig:1408) — only the error path ships. Err(()) } @@ -984,8 +935,8 @@ impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { let mut cloned = LayerName { v: SmallList::default(), }; - // PERF(port): was appendSliceAssumeCapacity — `SmallList` has no - // public `reserve`, so two `append_slice` calls each grow once. + // `SmallList` has no public `reserve`, so two `append_slice` + // calls each grow once. cloned.v.append_slice(this.enclosing_layer.v.slice()); cloned.v.append_slice(layer.v.slice()); this.layer_names.append_assume_capacity(cloned); @@ -1036,14 +987,14 @@ pub enum AtRulePrelude { FontPaletteValues(DashedIdent), CounterStyle(CustomIdent), Import { - url: &'static [u8], // TODO(port): lifetime — arena-owned slice + url: &'static [u8], // TODO: lifetime — arena-owned slice media: MediaList, supports: Option, layer: Option>, }, Namespace { - prefix: Option<&'static [u8]>, // TODO(port): lifetime - url: &'static [u8], // TODO(port): lifetime + prefix: Option<&'static [u8]>, // TODO: lifetime + url: &'static [u8], // TODO: lifetime }, Charset, CustomMedia { @@ -1074,7 +1025,7 @@ pub enum AtRulePrelude { scope_end: Option, }, Unknown { - name: &'static [u8], // TODO(port): lifetime + name: &'static [u8], // TODO: lifetime /// The tokens of the prelude tokens: TokenList, }, @@ -1112,7 +1063,6 @@ pub enum TopLevelState { } pub struct TopLevelRuleParser<'a, AtRuleParserT: CustomAtRuleParser> { - // Note: Zig threaded `input.arena()` at every call site; the Rust // `DeclarationList = bumpalo::Vec<'bump, Property>` needs the arena up // front, so cache it here (same `'static`-erased borrow `DeclarationBlock` // already uses crate-wide). @@ -1179,13 +1129,12 @@ pub enum ComposesState { DisallowEntirely, } -/// Dispatch trait for `parse_declaration_impl` (replaces Zig -/// `composes_ctx: anytype`). Implemented by `NestedRuleParser`. +/// Dispatch trait for `parse_declaration_impl`. Implemented by `NestedRuleParser`. pub trait ComposesCtx { fn composes_state(&self) -> ComposesState; fn record_composes(&mut self, composes: &mut Composes); } -/// Unit `ComposesCtx` for callers that don't track `composes:` (Zig `void`). +/// Unit `ComposesCtx` for callers that don't track `composes:`. pub struct NoComposesCtx; impl ComposesCtx for NoComposesCtx { #[inline] @@ -1329,11 +1278,10 @@ mod rule_parsers { use super::*; use crate::selectors::parser as selector_parser; - // Note: Zig threaded `composes_ctx: anytype` (pointer to the - // `NestedRuleParser`) directly into `parse_declaration`. Rust's borrow checker - // forbids passing `&mut *this` while also borrowing `this.declarations` / - // `this.important_declarations`, so split-borrow the three composes fields - // into a small adaptor that implements the `ComposesCtx` dispatch trait. + // The borrow checker forbids passing `&mut *this` while also borrowing + // `this.declarations` / `this.important_declarations`, so split-borrow the + // three composes fields into a small adaptor that implements the + // `ComposesCtx` dispatch trait. struct NestedComposesCtx<'a> { state: ComposesState, arena: &'a Bump, @@ -1366,13 +1314,12 @@ mod rule_parsers { input: &mut Parser, ) -> CssResult { // phf-style dispatch on at-rule name (case-insensitive). - // Zig used `bun.ComptimeEnumMap(PreludeEnum)`. crate::match_ignore_ascii_case! { name, { b"import" => { if (this.state as u8) > (TopLevelState::Imports as u8) { return Err(input.new_custom_error(ParserError::unexpected_import_rule)); } - // TODO(port): lifetime — arena-owned slice; same `'static` erasure + // TODO: lifetime — arena-owned slice; same `'static` erasure // as `Token` payloads. // SAFETY: the returned slice borrows `input.src`/arena, which outlives // the `AtRulePrelude` it is stored in (see `src_str`). @@ -1633,13 +1580,11 @@ mod rule_parsers { composes_refs: &mut *self.composes_refs, local_properties: &mut *self.local_properties, }; - // Note: reshaped for borrowck — Zig held `self.*` aliased. Spell - // out the impl with a fresh lifetime so `nested_parser` isn't forced - // to borrow `rules` for `'a`. + // Spell out the impl with a fresh lifetime so `nested_parser` isn't + // forced to borrow `rules` for `'a`. let parse_declarations = as RuleBodyItemParser>::parse_declarations(&nested_parser); // TODO: think about memory management - // PERF(port): was arena bulk-free — profile if hot. let mut errors: Vec> = Vec::new(); let mut iter = RuleBodyParser::new(input, &mut nested_parser); @@ -1726,14 +1671,13 @@ mod rule_parsers { name: &[u8], input: &mut Parser, ) -> CssResult { - // TODO(port): lifetime — `name` borrows the input arena. Detach to + // TODO: lifetime — `name` borrows the input arena. Detach to // `'static` to feed `BasicParseErrorKind::at_rule_invalid` (matches the // `Token` payload erasure throughout this file). // SAFETY: `name` points into the parser's source/arena, which outlives // every prelude/error produced from this parser (see `src_str`). let name: &'static [u8] = unsafe { src_str(name) }; let result: Self::Prelude = 'brk: { - // Zig `ComptimeEnumMap(PreludeEnum)` ASCII-CI dispatch. crate::match_ignore_ascii_case! { name, { b"media" => break 'brk AtRulePrelude::Media(parse_media_list(input, this.options)?), b"supports" => break 'brk AtRulePrelude::Supports(SupportsCondition::parse(input)?), @@ -1751,10 +1695,9 @@ mod rule_parsers { break 'brk AtRulePrelude::Keyframes { name: keyframes_name, prefix }; }, b"page" => { - // Zig: tryParse(parseCommaSeparated(PageSelector.parse)) → on - // .err returns empty list. EOF inside `PageSelector::parse` + // EOF inside `PageSelector::parse` // (e.g. `@page foo` with nothing after) propagates here and is - // swallowed by `try_parse` — matches css_parser.zig:2073. + // swallowed by `try_parse`, yielding an empty list. let selectors: Vec = input .try_parse(|input2| { input2.parse_comma_separated(css_rules::page::PageSelector::parse) @@ -1878,7 +1821,6 @@ mod rule_parsers { let mut decl_parser = css_rules::font_face::FontFaceDeclarationParser; let mut parser = RuleBodyParser::new(input, &mut decl_parser); // todo_stuff.think_mem_mgmt - // PERF(port): was arena bulk-free — profile if hot. let mut properties: Vec = Vec::new(); while let Some(result) = parser.next() { @@ -1978,7 +1920,6 @@ mod rule_parsers { let mut parser = css_rules::keyframes::KeyframesListParser; let mut iter = RuleBodyParser::new(input, &mut parser); // todo_stuff.think_mem_mgmt - // PERF(port): was arena bulk-free — profile if hot. let mut keyframes: Vec = Vec::new(); while let Some(result) = iter.next() { if let Ok(keyframe) = result { @@ -2013,18 +1954,16 @@ mod rule_parsers { Ok(()) } AtRulePrelude::Layer(mut layer) => { - // Note (css_parser.zig:2393): Zig reads - // `prelude.layer.at(0).*` — a struct copy that leaves the list - // intact — *then* calls `onLayerRule(&prelude.layer)` so the - // hook still observes the 1-element list. Mirror that: clone - // slot 0 for the rule's `name`, fire `on_layer_rule`, then + // Clone slot 0 for the rule's `name` (leaving the list + // intact so the `on_layer_rule` hook still observes the + // 1-element list), fire `on_layer_rule`, then // drain the original into `push_to_enclosing_layer`. let name = if layer.len() == 0 { None } else if layer.len() == 1 { // `LayerName` has no `Clone` impl yet; `deep_clone` is the // arena-threaded shallow copy (segments are arena-borrowed - // `&[u8]`, so this is the same field-walk Zig's `*` did). + // `&[u8]`). Some(layer.at(0).deep_clone(this.arena)) } else { return Err(input.new_error(BasicParseErrorKind::at_rule_body_invalid)); @@ -2192,7 +2131,6 @@ mod rule_parsers { input: &mut Parser, ) -> CssResult<()> { let loc = this.get_loc(start); - // Note: Zig `defer this.composes_refs.clearRetainingCapacity();`. // `composes_refs` is `&mut SmallList<..>` borrowed from the parent // `TopLevelRuleParser`, so dropping `NestedRuleParser` on an error path // does NOT clear the underlying storage. A safe `scopeguard::guard` @@ -2333,8 +2271,7 @@ mod rule_parsers { } } - /// `MediaList::parse` thunk. The body lives in `media_query.rs` in Zig; the - /// Rust port hasn't landed it yet. Kept local so the rule-parser arms above + /// `MediaList::parse` thunk. Kept local so the rule-parser arms above /// type-check; becomes a one-line `MediaList::parse(input, options)` forwarder /// once `media_query::MediaList::parse` un-gates. // blocked_on: media_query::{MediaList,MediaQuery}::parse @@ -2351,7 +2288,7 @@ pub struct ToCssResult { pub code: Vec, /// A map of CSS module exports, if the `css_modules` option was enabled /// during parsing. - // TODO(port): arena lifetime — CssModuleExports/References borrow the + // TODO: arena lifetime — CssModuleExports/References borrow the // parser arena. `'static` placeholder until `<'bump>` threads. pub exports: Option>, /// A map of CSS module references, if the `css_modules` config had @@ -2458,7 +2395,6 @@ impl CssRef { } pub fn to_real_ref(self, source_index: u32) -> bun_ast::Ref { - // Spec (css_parser.zig) constructs `Ref{ .tag = .symbol, ... }`. bun_ast::Ref::new(self.inner_index(), source_index, bun_ast::RefTag::Symbol) } } @@ -2487,7 +2423,7 @@ pub struct ComposesEntry { pub struct PropertyUsage { pub bitset: PropertyBitset, - pub custom_properties: Box<[&'static [u8]]>, // TODO(port): lifetime — arena slices + pub custom_properties: Box<[&'static [u8]]>, // TODO: lifetime — arena slices pub range: bun_ast::Range, } @@ -2505,13 +2441,12 @@ impl PropertyUsage { #[inline] pub fn fill(&mut self, used: &PropertyBitset, custom_properties: &[&'static [u8]]) { self.bitset.set_union(used); - // TODO(port): lifetime — Zig stored borrowed slice; box for now. + // TODO: lifetime — box for now. self.custom_properties = custom_properties.to_vec().into_boxed_slice(); } } -// Zig: `std.bit_set.ArrayBitSet(usize, ceilPowerOfTwo(EnumFields(PropertyIdTag).len))` -// (css_parser.zig:3015). `PropertyIdTag` is a dense `repr(u16)` enum with no +// `PropertyIdTag` is a dense `repr(u16)` enum with no // explicit discriminants whose last variant is `Custom`, so the variant count // is `Custom + 1`. pub const PROPERTY_BITSET_BITS: usize = (PropertyIdTag::Custom as usize + 1).next_power_of_two(); @@ -2567,15 +2502,11 @@ pub fn fill_property_bit_set( pub struct StyleSheet { /// A list of top-level rules within the style sheet. pub rules: CssRuleList, - // PERF(port): was arena bulk-free (sources / source_map_urls / - // license_comments were ArrayList fed input.arena()) — profile if hot. pub sources: Vec>, pub source_map_urls: Vec>>, - pub license_comments: Vec<&'static [u8]>, // TODO(port): lifetime — arena - pub options: ParserOptions<'static>, // TODO(port): lifetime - // Zig: `tailwind: if (AtRule == BundlerAtRule) ?*BundlerTailwindState else u0`. - // Rust has no comptime-conditional fields; `Option>` (always `None` - // for non-bundler sheets) is the permanent shape. + pub license_comments: Vec<&'static [u8]>, // TODO: lifetime — arena + pub options: ParserOptions<'static>, // TODO: lifetime + // Always `None` for non-bundler sheets. pub tailwind: Option>, pub layer_names: Vec, @@ -2606,15 +2537,14 @@ impl StyleSheet { } // ── StyleSheet behavior (parse/minify/to_css) ──────────────────────────────── -// Method *bodies* carry borrowck-reshape notes where Zig aliased pointers. mod stylesheet_impl { use super::*; impl StyleSheet { /// Minify and transform the style sheet for the provided browser targets. /// - /// Note: `arena` is the arena that owns this stylesheet's AST - /// (Zig: `arena: Allocator`). It is threaded into `MinifyContext` so + /// Note: `arena` is the arena that owns this stylesheet's AST. + /// It is threaded into `MinifyContext` so /// downstream `deep_clone` calls allocate alongside the existing tree. pub fn minify( &mut self, @@ -2807,10 +2737,7 @@ mod stylesheet_impl { ) -> PrintResult { // TODO: this is not necessary // Make sure we always have capacity > 0: https://github.com/napi-rs/napi-rs/issues/1124. - // Zig used std.Io.Writer.Allocating; `Vec` behind `bun_io::Write` - // is the equivalent allocating destination. - // PERF(port): Zig's writer fed an allocator the caller controlled; - // this always heap-allocates — profile if hot. + // PERF: this always heap-allocates — profile if hot. let mut dest: Vec = Vec::with_capacity(1); let result = self.to_css_with_writer( arena, @@ -2835,9 +2762,7 @@ mod stylesheet_impl { import_records: Option<&mut Vec>, source_index: SrcIndex, ) -> Maybe<(StyleSheet, StylesheetExtra), Err> { - // Note: Zig instantiated `StyleSheet(DefaultAtRule).parse`; Rust - // cannot vary `Self`'s `AtRule` param against `DefaultAtRuleParser`, so - // this returns the concrete `StyleSheet`. Callers that + // Returns the concrete `StyleSheet`. Callers that // need a custom at-rule call `parse_with` directly. let mut default_at_rule_parser = DefaultAtRuleParser; StyleSheet::::parse_with( @@ -2851,7 +2776,7 @@ mod stylesheet_impl { } /// Parse a style sheet from a string. - // TODO(port): `ParserOptions<'static>` matches the `StyleSheet.options` + // TODO: `ParserOptions<'static>` matches the `StyleSheet.options` // field's `'static` erasure; re-threads to `<'bump>` alongside the rest of // the crate. pub fn parse_with>( @@ -2862,11 +2787,11 @@ mod stylesheet_impl { import_records: Option>>, source_index: SrcIndex, ) -> Maybe<(Self, StylesheetExtra), Err> { - // TODO(port): 'bump lifetime threading — every arena-backed slice the + // TODO: 'bump lifetime threading — every arena-backed slice the // parser hands back is currently detached to `'static` (matching the // crate-wide erasure on `DeclarationBlock<'static>`/`Token` payloads). - // The caller owns the arena (matching Zig's `arena: Allocator` - // parameter) so the storage outlives the returned `StyleSheet`. + // The caller owns the arena, so the storage outlives the + // returned `StyleSheet`. // TODO(refactor): re-thread the lifetime through `CssRuleList<'bump, R>` // and drop the `'static` bound on `arena`. let mut composes = ComposesMap::default(); @@ -2889,7 +2814,6 @@ mod stylesheet_impl { Some(&mut parser_extra), ); - // PERF(port): was arena bulk-free — profile if hot. let mut license_comments: Vec<&'static [u8]> = Vec::new(); let mut state = parser.state(); while let Ok(token) = parser.next_including_whitespace_and_comments() { @@ -2897,7 +2821,7 @@ mod stylesheet_impl { Token::Whitespace(_) => {} Token::Comment(comment) => { if comment.first() == Some(&b'!') { - // TODO(port): lifetime — arena slice; see erasure note. + // TODO: lifetime — arena slice; see erasure note. // SAFETY: `comment` borrows `parser.src`, which outlives // `license_comments` (consumed before `parser` drops). license_comments.push(unsafe { src_str(comment) }); @@ -2935,9 +2859,7 @@ mod stylesheet_impl { let source_map_urls: Vec>> = vec![parser.current_source_map_url().map(Box::<[u8]>::from)]; - // Spec: `.layer_names = if (comptime P == BundlerAtRuleParser) - // at_rule_parser.layer_names else .{}` (css_parser.zig:3324). Rust - // dispatches through the `CustomAtRuleParser::take_layer_names` hook + // Dispatch through the `CustomAtRuleParser::take_layer_names` hook // (default = empty; `BundlerAtRuleParser` overrides to move its list // out) so the accumulated layer ordering isn't silently dropped. let layer_names = P::take_layer_names(at_rule_parser); @@ -3003,13 +2925,10 @@ mod stylesheet_impl { out: &mut CssRuleList, new_import_records: &mut Vec, ) { - // Note: the Zig fn takes `*const @This()` but writes - // `rule.* = .ignored;` through it (Zig has no const-transitivity). - // Writing through a `*const`-derived pointer is UB in Rust, so the - // receiver is reshaped to `&mut self`. The sole caller (Tailwind + // The receiver is `&mut self`; the sole caller (Tailwind // bundling) owns the stylesheet exclusively at this point. // - // Zig used a comptime two-pass `inline for` (count, exec). Unroll. + // Two passes: count, then exec. let mut count: u32 = 0; { let mut saw_imports = false; @@ -3035,7 +2954,6 @@ mod stylesheet_impl { } } out.v.reserve(count as usize); - // PERF(port): was ensureUnusedCapacity — profile if hot. let mut saw_imports = false; for rule in self.rules.v.iter_mut() { match rule { @@ -3064,12 +2982,9 @@ mod stylesheet_impl { original_path: b"", flags: Default::default(), }); - // Note: reshaped for borrowck — Zig did - // `out.v.appendAssumeCapacity(rule.*)` (bitwise copy) then - // `rule.* = .ignored`. Rust moves the rule out via - // `mem::replace` (no `Clone` bound needed) and pushes that. + // Move the rule out via `mem::replace` (no `Clone` + // bound needed) and push that. let old = core::mem::replace(rule, CssRule::Ignored); - // PERF(port): was appendAssumeCapacity out.v.push(old); } CssRule::Unknown(u) => { @@ -3086,8 +3001,6 @@ mod stylesheet_impl { } } - // Zig compile-errors unless `AtRule == BundlerAtRule` - // (css_parser.zig:3355); the concrete impl enforces the same restriction. impl StyleSheet { pub fn contains_tailwind_directives(&self) -> bool { let mut found_import = false; @@ -3115,7 +3028,7 @@ mod stylesheet_impl { import_records: &mut Vec, source_index: SrcIndex, ) -> Maybe> { - // TODO(port): 'bump lifetime threading — `DeclarationBlock<'static>` in + // TODO: 'bump lifetime threading — `DeclarationBlock<'static>` in // `StyleAttribute` vs `Parser<'a>` here; `arena: &'static Bump` // matches the crate-wide erasure (see `parse_with`). let mut parser_extra = ParserExtra { @@ -3157,8 +3070,6 @@ mod stylesheet_impl { // ); let symbols = bun_ast::symbol::Map::init_list(Default::default()); - // Zig used std.Io.Writer.Allocating; `Vec` behind `bun_io::Write` - // is the equivalent allocating destination. let mut dest: Vec = Vec::new(); let mut printer = Printer::new( arena, @@ -3192,18 +3103,17 @@ mod stylesheet_impl { import_records: &mut Vec, source_index: SrcIndex, ) -> Maybe<(Self, StylesheetExtra), Err> { - // Note: Zig aliased `import_records` into both `BundlerAtRuleParser` - // *and* the inner `Parser` (css_parser.zig:3245), and aliased `&options` - // into the at-rule parser while also passing `options` by value (struct - // copy) to `parseWith`. Rust forbids both overlaps directly: + // `import_records` is shared by both `BundlerAtRuleParser` and the + // inner `Parser`, and `options` is both borrowed by the at-rule + // parser and passed by value: // - `import_records`: derive a single raw `NonNull` from the unique // borrow; both the at-rule parser and `Parser::new` store copies of - // that raw pointer (matching Zig's `?*Vec`). Neither holds a + // that raw pointer. Neither holds a // long-lived `&mut`, so interleaved writes from `on_import_rule` and // `add_import_record`/`state`/`reset` each create a fresh short-lived // `&mut` from the shared SharedRW provenance — sound under SB. - // - `options`: bitwise-duplicate via `ptr::read` (mirroring Zig's - // by-value struct copy) and wrap the original in `ManuallyDrop` so + // - `options`: bitwise-duplicate via `ptr::read` and wrap the + // original in `ManuallyDrop` so // only the moved copy drops — `ParserOptions` transitively owns a // `SmallList` (via `css_modules::Config::pattern`) which has a real // `Drop`, so both copies must not run their destructors. @@ -3352,7 +3262,7 @@ where pub struct ParserOptions<'a> { /// Filename to use in error messages. - pub filename: &'static [u8], // TODO(port): lifetime + pub filename: &'static [u8], // TODO: lifetime /// Whether to enable [CSS modules](https://github.com/css-modules/css-modules). pub css_modules: Option, /// The source index to assign to all parsed rules. Impacts the source map @@ -3362,7 +3272,7 @@ pub struct ParserOptions<'a> { pub error_recovery: bool, /// A list that will be appended to when a warning occurs. /// - /// Stored as a raw `NonNull` (mirrors Zig's `*Log`) so `warn(&self)` + /// Stored as a raw `NonNull` so `warn(&self)` /// can soundly write through it. Deriving `&mut Log` from a `&self`-reachable /// `&'a mut Log` (the previous representation) is UB under Stacked Borrows /// — see PORTING.md §Forbidden patterns. The caller that constructs @@ -3379,7 +3289,7 @@ impl<'a> ParserOptions<'a> { if let Some(lg) = self.logger { // SAFETY: `logger` was constructed from a unique `&'a mut Log` (see // `default`); the pointee outlives `'a` and no other borrow of the - // Log exists for the duration of parsing. Zig mutated through `*Log`. + // Log exists for the duration of parsing. let lg: &mut Log = unsafe { &mut *lg.as_ptr() }; lg.add_warning_fmt_line_col( self.filename, @@ -3479,7 +3389,6 @@ bitflags::bitflags! { const CSS_MODULES = 0b1; } } -// Note: Zig packed struct had `css_modules: bool`. Expose accessor: impl ParserOpts { #[inline] pub fn css_modules(self) -> bool { @@ -3492,8 +3401,7 @@ pub struct Parser<'a> { pub at_start_of: Option, pub stop_before: Delimiters, pub flags: ParserOpts, - /// Stored as a raw `NonNull` (mirrors Zig's `?*Vec(ImportRecord)`, - /// css_parser.zig:3808) because `BundlerAtRuleParser` holds an aliasing + /// Stored as a raw `NonNull` because `BundlerAtRuleParser` holds an aliasing /// raw pointer to the same list. Keeping a long-lived `&'a mut` here would /// be invalidated under Stacked Borrows the moment `on_import_rule` /// derives its own `&mut` from the sibling raw pointer. Each access site @@ -3571,8 +3479,7 @@ impl<'a> Parser<'a> { let import_records = unsafe { &mut *ptr.as_ptr() }; let idx = u32::try_from(import_records.len()).unwrap(); // SAFETY: `url` borrows the parser source / arena which outlives - // every `ImportRecord` produced by this parse. `bun.fs.Path` in - // the Zig original stores the same borrowed slice; the lifetime + // every `ImportRecord` produced by this parse; the lifetime // is erased to 'static (see PORTING.md §Lifetimes). let url_static: &'static [u8] = unsafe { src_str(url) }; import_records.push(ImportRecord { @@ -3701,7 +3608,6 @@ impl<'a> Parser<'a> { // capacity 1, so in the somewhat common case of only one item we don't // way overallocate. Note that we always push at least one item if // parsing succeeds. - // PERF(port): was stack-fallback let mut values: Vec = Vec::with_capacity(1); loop { @@ -3738,9 +3644,6 @@ impl<'a> Parser<'a> { result } - // Zig `tryParseImpl` is the same as `tryParse` with manual args tuple; - // collapsed into `try_parse`. - #[inline] pub fn parse_nested_block( &mut self, @@ -4317,8 +4220,7 @@ struct UnclosedBlockAtEof { impl<'a> ParserInput<'a> { /// Create a `ParserInput` borrowing `code` and an arena for unescaped - /// strings. Matches Zig `ParserInput.new` (css_parser.zig:4549) which - /// takes an `Allocator` parameter — the caller owns the arena and it must + /// strings. The caller owns the arena and it must /// outlive every `Token` produced from this input. /// /// PORTING.md §Forbidden: do not fabricate `&'a Bump` from a boxed field @@ -4574,10 +4476,7 @@ const REPLACEMENT_CHAR: u32 = 0xFFFD; const REPLACEMENT_CHAR_UNICODE: [u8; 3] = [0xEF, 0xBF, 0xBD]; /// UTF-8 encoding of U+FFFD REPLACEMENT CHARACTER, written by `serializer` /// when escaping a NUL byte (css-syntax requires U+0000 → U+FFFD; upstream -/// rust-cssparser writes `"\u{FFFD}"`). Zig called -/// `bun.strings.encodeUTF8Comptime(0xFFD)` (css_parser.zig:6747, :6937) — a -/// typo for `0xFFFD` per its own "replacement character" comment — and so -/// emitted U+0FFD (`[0xE0, 0xBF, 0xBD]`); that bug is fixed here. +/// rust-cssparser writes `"\u{FFFD}"`). const REPLACEMENT_CHAR_UTF8: &[u8] = &REPLACEMENT_CHAR_UNICODE; const MAX_ONE_B: u32 = 0x80; const MAX_TWO_B: u32 = 0x800; @@ -4587,12 +4486,11 @@ const MAX_THREE_B: u32 = 0x10000; /// /// PORTING.md §Forbidden flags this erasure. The proper fix is to thread a /// real `'a` lifetime through `Token<'a>` / `Dimension<'a>` / `CachedToken<'a>` -/// so `slice_from`/`to_slice` return `&'a [u8]` (matching Zig's plain -/// `[]const u8` borrows in css_parser.zig:5879/6461). That change is blocked +/// so `slice_from`/`to_slice` return `&'a [u8]`. That change is blocked /// on `crate::Token` (defined in `lib.rs`, not this file) gaining `<'a>` — /// once `lib.rs` is updated, delete this fn and every call site compiles with /// the honest lifetime. -// TODO(port): delete once `Token<'a>` lands in lib.rs; see verifier bug +// TODO: delete once `Token<'a>` lands in lib.rs; see verifier bug // "src_str / Tokenizer::slice_from / CopyOnWriteStr::to_slice". // SAFETY: every call site below feeds either (a) a sub-slice of `self.src` // (`&'a [u8]`) or (b) an arena-allocated `CopyOnWriteStr::to_slice()` whose @@ -4676,7 +4574,7 @@ impl<'a> Tokenizer<'a> { pub fn see_function(&mut self, name: &[u8]) { if self.var_or_env_functions == SeenStatus::LookingForThem { - // Note: Zig had `and` here (always false); preserved. + // Note: this `&&` is always false; kept as-is intentionally. if strings::eql_case_insensitive_ascii_check_length(name, b"var") && strings::eql_case_insensitive_ascii_check_length(name, b"env") { @@ -4995,7 +4893,7 @@ impl<'a> Tokenizer<'a> { } let int_value: Option = if is_integer { - // Zig: bun.intFromFloat — saturating cast. + // Saturating cast. Some(value as i32) } else { None @@ -5661,10 +5559,9 @@ fn byte_to_decimal_digit(b: u8) -> Option { } pub fn split_source_map(contents: &[u8]) -> Option<&[u8]> { - // Zig used std.unicode.Utf8Iterator; a byte scan is equivalent because the - // delimiters are all ASCII and ASCII bytes never occur inside a multi-byte - // UTF-8 sequence. Zig returned `[0..iter.i]` where `i` is *after* the - // matched codepoint — hence `i + 1`. + // A byte scan suffices: the delimiters are all ASCII and ASCII bytes never + // occur inside a multi-byte UTF-8 sequence. The returned slice ends *after* + // the matched byte — hence `i + 1`. for (i, &c) in contents.iter().enumerate() { match c { b' ' | b'\t' | FORM_FEED_BYTE | b'\r' | b'\n' => { @@ -5753,15 +5650,14 @@ impl TokenKind { // Data layout hoisted at crate root (lib.rs) so error.rs can name `Token` // without the parser hub. Behavior impls (kind/is_parse_error/to_css_generic) // live here. TODO: make strings be allocated in string pool. -// TODO(port): lifetime — every &[u8] payload borrows the arena/source. Uses +// TODO: lifetime — every &[u8] payload borrows the arena/source. Uses // `&'static [u8]` placeholder; thread `<'a>` once payload lifetimes settle. pub use crate::Token; impl Token { // blocked_on: generics::CssEql/CssHash blanket impls for Token payload set pub fn eql(lhs: &Token, rhs: &Token) -> bool { - // TODO(port): Zig used implementEql (comptime field-walk). - // Derive PartialEq once payload lifetimes settle. + // TODO: derive PartialEq once payload lifetimes settle. generic::implement_eql(lhs, rhs) } @@ -6012,12 +5908,10 @@ impl Token { // `impl Display for Token` lives at crate root (lib.rs) — minimal rendering // for error messages only. The CSS-serialization-correct form is -// `Token::to_css_generic` above; Zig's `Token.format` additionally routed -// quoted_string→serialize_string and idhash→serialize_identifier, a purely -// cosmetic difference in diagnostics output. +// `Token::to_css_generic` above. -/// Byte-writer trait for `serializer` and `to_css_generic` (replaces Zig -/// `anytype` writer). Aliased to the canonical `bun_io::Write`; the associated +/// Byte-writer trait for `serializer` and `to_css_generic`. +/// Aliased to the canonical `bun_io::Write`; the associated /// `type Error` is dropped — every `Result<(), W::Error>` becomes /// `bun_io::Result<()>`. `Vec` / `ArenaVec<'_, u8>` / `Printer` all /// implement it upstream. @@ -6059,7 +5953,6 @@ impl<'a> CopyOnWriteStr<'a> { let mut list = bun_alloc::ArenaVec::with_capacity_in(b.len() + slice.len(), arena); list.extend_from_slice(b); list.extend_from_slice(slice); - // PERF(port): was appendSliceAssumeCapacity *self = CopyOnWriteStr::Owned(list); } CopyOnWriteStr::Owned(o) => { @@ -6392,7 +6285,7 @@ pub mod serializer { dest: &mut Printer, ) -> Result<(), PrintErr> { let int_value: Option = if fract(value) == 0.0 { - Some(value as i32) // saturating like Zig bun.intFromFloat + Some(value as i32) // saturating cast } else { None }; @@ -6620,9 +6513,6 @@ pub mod to_css { local_names, symbols, ); - // Note: Zig special-cased `T == CSSString` → `CSSStringFns.toCss`; - // in Rust the `ToCss` impl on `CSSString` routes there directly, so the - // generic dispatch suffices. this.to_css(&mut printer)?; drop(printer); Ok(s) @@ -6658,7 +6548,7 @@ pub fn parse_important(input: &mut Parser) -> CssResult<()> { } pub mod signfns { - /// Spec-faithful port of `css_parser.zig:7086` — note the ±0.0 sign FLIP is + /// Note: the ±0.0 sign FLIP is /// intentional (do NOT "fix" it). Distinct from `f32::signum` and from /// `calc::std_math_sign` / `CSSNumberFns::sign`. #[inline] @@ -6888,5 +6778,3 @@ pub fn f32_length_with_5_digits(n_input: f32) -> usize { count } - -// ported from: src/css/css_parser.zig diff --git a/src/css/declaration.rs b/src/css/declaration.rs index 07518aafdf41..41b7e5ccce50 100644 --- a/src/css/declaration.rs +++ b/src/css/declaration.rs @@ -47,13 +47,12 @@ pub struct DeclarationBlock<'bump> { pub struct DebugFmt<'a, 'bump>(&'a DeclarationBlock<'bump>); -// blocked_on: Printer::new signature (Zig passes arena + Managed(u8) + -// writer + options + null + null + &symbols; the Rust ctor shape is unsettled). +// blocked_on: Printer::new signature (the ctor shape is unsettled). impl<'a, 'bump> core::fmt::Display for DebugFmt<'a, 'bump> { fn fmt(&self, writer: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { // Debug formatter: uses a throwaway local arena for the printer's - // scratch buffers (Zig threaded the parser arena). + // scratch buffers. let bump = Bump::new(); let mut arraylist: Vec = Vec::new(); let symbols = bun_ast::symbol::Map::init_list(Default::default()); @@ -105,14 +104,12 @@ impl<'bump> DeclarationBlock<'bump> { important_handler: &mut DeclarationHandler<'bump>, context: &mut css::PropertyHandlerContext, ) { - // Zig threaded `context.arena` through every append; the - // Rust `PropertyHandlerContext` dropped that field, so we recover the + // `PropertyHandlerContext` carries no arena field, so we recover the // arena from the handler's own bump-backed accumulator instead. let bump: &'bump Bump = handler.decls.bump(); - // Zig used a local generic `handle` fn with comptime field - // name + bool. Unrolled to two calls over a shared inner fn; reshaped - // for borrowck (iterate via &mut, move prop out and overwrite slot). + // Two calls over a shared inner fn; iterate via &mut, move prop out + // and overwrite the slot. #[inline] fn handle<'bump>( decls: &mut DeclarationList<'bump>, @@ -126,8 +123,7 @@ impl<'bump> DeclarationBlock<'bump> { let handled = hndlr.handle_property(prop, ctx); if !handled { - // Zig: `hndlr.decls.append(prop.*); prop.* = .{ .all = .@"revert-layer" }` - // — move the value out and overwrite the slot with a + // Move the value out and overwrite the slot with a // non-allocating placeholder so the source list's drop is a // no-op. hndlr @@ -147,9 +143,8 @@ impl<'bump> DeclarationBlock<'bump> { handler.finalize(context); important_handler.finalize(context); - // Zig swapped old lists out, deferred their deinit, then - // assigned the handler accumulators. In Rust the old bumpalo Vecs drop - // implicitly on overwrite (arena reclaims on reset). + // The old bumpalo Vecs drop implicitly on overwrite (arena reclaims + // on reset). self.important_declarations = core::mem::replace(&mut important_handler.decls, DeclarationList::new_in(bump)); self.declarations = core::mem::replace(&mut handler.decls, DeclarationList::new_in(bump)); @@ -157,7 +152,6 @@ impl<'bump> DeclarationBlock<'bump> { } /// Non-allocating placeholder used by `minify()` to overwrite moved-out slots. -/// Zig: `css.Property{ .all = .@"revert-layer" }`. #[inline(always)] fn placeholder_property() -> css::Property { css::Property::All(crate::css_properties::CSSWideKeyword::RevertLayer) @@ -256,10 +250,8 @@ impl DeclarationBlock<'static> { options.warn(&e); continue; } - // errdefer doesn't fire on `return .{ .err = ... }` — Result(T) is a tagged - // union, not an error union. Free any declarations accumulated so far. - // In Rust, `declarations`/`important_declarations` are bumpalo - // Vec and drop on early return; deepDeinit is implicit via Drop. + // `declarations`/`important_declarations` are bumpalo Vec and + // drop on this early return; freeing them is implicit via Drop. return Err(e); } } @@ -274,8 +266,7 @@ impl DeclarationBlock<'static> { // ─── hash / eql / deep_clone (gated) ────────────────────────────────────── // blocked_on: properties_generated — `Property` lacks `DeepClone`/`CssEql` // derives and `PropertyId` lacks a `hash(&mut Wyhash)` method. The bodies -// below are the real manual unrolls of Zig's comptime-reflection helpers -// (`implementEql`/`implementDeepClone`); they un-gate the moment the +// below un-gate the moment the // per-variant trait impls land in `properties_generated.rs`. impl<'bump> DeclarationBlock<'bump> { @@ -425,10 +416,9 @@ pub fn parse_declaration<'bump>( ) } -// Zig's `composes_ctx: anytype` branches on -// `comptime @TypeOf(composes_ctx) != void`. The Rust shape is a `ComposesCtx` +// Composes handling dispatches through the `ComposesCtx` // trait (defined in `css_parser.rs`); `NoComposesCtx` returns -// `DisallowEntirely` so the `void` fast-path collapses into the match's +// `DisallowEntirely` so the no-tracking fast-path collapses into the match's // no-op arm. pub fn parse_declaration_impl<'bump, C>( name: &[u8], @@ -443,8 +433,7 @@ where { let property_id = css::PropertyId::from_string(name); let mut delimiters = css::Delimiters::BANG; - // Zig: `if (property_id != .custom or property_id.custom != .custom)` — - // i.e. NOT (tag == .custom AND payload tag == .custom). + // NOT (tag == custom AND payload tag == custom). if !matches!( property_id, css::PropertyId::Custom(CustomPropertyName::Custom(_)) @@ -507,8 +496,7 @@ where /// /// Each `*Handler` is the real implementation from its leaf module (see the /// per-module status notes in properties/mod.rs for any internally-gated -/// bodies); `Direction` is the data-only `properties::text` enum. The struct -/// shape matches the Zig layout. +/// bodies); `Direction` is the data-only `properties::text` enum. pub struct DeclarationHandler<'bump> { pub background: BackgroundHandler, pub border: BorderHandler, @@ -630,5 +618,3 @@ impl<'bump> DeclarationHandler<'bump> { } } } - -// ported from: src/css/declaration.zig diff --git a/src/css/dependencies.rs b/src/css/dependencies.rs index b773fb91b3b1..dd65a35c4fd6 100644 --- a/src/css/dependencies.rs +++ b/src/css/dependencies.rs @@ -1,7 +1,6 @@ //! CSS dependency tracking — `@import` and `url()` references collected during printing. use crate::SourceLocation; -// const Location = css.Location; — shadowed by the local `Location` below in Zig too. /// Options for `analyze_dependencies` in `PrinterOptions`. pub struct DependencyOptions { @@ -127,7 +126,7 @@ impl ImportDependency { }, // Assumes the `@import "url"` form: 8 = len of `@import `, +2 for the // quotes. The `@import url(...)` form yields a slightly-off range — - // a limitation inherited from lightningcss (and the Zig port). + // a limitation inherited from lightningcss. 8, rule.url.len() + 2, ), @@ -194,5 +193,3 @@ impl SourceRange { } } } - -// ported from: src/css/dependencies.zig diff --git a/src/css/error.rs b/src/css/error.rs index f0987500fce6..37a850e2ff49 100644 --- a/src/css/error.rs +++ b/src/css/error.rs @@ -33,13 +33,11 @@ pub struct Err { impl fmt::Display for Err { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Zig: `if (@hasDecl(T, "format"))` → trait bound `T: Display` IS that check. self.kind.fmt(f) } } -// Zig: `pub const toErrorInstance = @import("../css_jsc/error_jsc.zig").toErrorInstance;` -// Deleted per PORTING.md — `to_error_instance` lives as an extension-trait method in `bun_css_jsc`. +// `to_error_instance` lives as an extension-trait method in `bun_css_jsc`. impl Err { pub fn from_parse_error(err: ParseError, filename: &[u8]) -> Err { @@ -546,5 +544,3 @@ impl fmt::Display for MinifyErrorKind { } } } - -// ported from: src/css/error.zig diff --git a/src/css/generics.rs b/src/css/generics.rs index 01fe96f301de..21aafcde8351 100644 --- a/src/css/generics.rs +++ b/src/css/generics.rs @@ -1,22 +1,20 @@ //! Generic trait-based dispatch for CSS value operations. //! -//! The Zig original (`generics.zig`) uses `@typeInfo`/`@hasDecl`/`@field` comptime -//! reflection to derive `eql`, `hash`, `deepClone`, `toCss`, `parse`, etc. across -//! every CSS value type. Per PORTING.md §Comptime reflection, that has no Rust -//! equivalent — the port defines a trait per protocol, provides blanket impls for -//! the structural cases (Option/Box/slice/Vec/Vec/SmallList/primitives), and +//! `eql`, `hash`, `deepClone`, `toCss`, `parse`, etc. are dispatched across +//! every CSS value type via a trait per protocol, with blanket impls for +//! the structural cases (Option/Box/slice/Vec/Vec/SmallList/primitives); //! per-struct/-enum impls come from the `bun_css_derive` proc-macros //! (`#[derive(ToCss, DeepClone, CssEql, CssHash)]` etc.). //! //! Free functions with the original names are kept as thin trait-method wrappers -//! so call sites in sibling files port 1:1. +//! for call sites in sibling files. use core::cmp::Ordering; use bun_alloc::Arena; // bumpalo::Bump re-export use bun_collections::VecExt; -// Zig `std.hash.Wyhash` (iterative) → `bun_wyhash::Wyhash` (the final4 variant -// matching upstream `std.hash.Wyhash`; NOT `Wyhash11`, which is a legacy v0.11 +// `bun_wyhash::Wyhash` is the final4 variant +// (NOT `Wyhash11`, which is a legacy // variant kept only for on-disk lockfile compat — different digest). // Re-exported `pub` so `#[derive(CssHash)]` (in `bun_css_derive`) can name the // hasher type as `::bun_css::generics::Wyhash` without depending on `bun_wyhash` @@ -37,25 +35,24 @@ use crate::values::rect::Rect; use crate::values::size::Size2D; use crate::{PrintErr, VendorPrefix}; -// `ArrayList(T)` in the Zig is `std.ArrayListUnmanaged(T)` fed the parser arena. -// In this AST crate that maps to `bun_alloc::ArenaVec<'bump, T>`. +// In this AST crate, lists map to `bun_alloc::ArenaVec<'bump, T>` fed the +// parser arena. pub type ArrayList<'bump, T> = bun_alloc::ArenaVec<'bump, T>; // ─────────────────────────────────────────────────────────────────────────────── // DeepClone // ─────────────────────────────────────────────────────────────────────────────── -/// Arena-aware deep clone. Equivalent of Zig's `deepClone(T, *const T, Allocator) T`. +/// Arena-aware deep clone. /// -/// Per-struct/-enum impls come from `#[derive(DeepClone)]`; -/// the Zig `implementDeepClone` body is the spec for that derive (field-wise / +/// Per-struct/-enum impls come from `#[derive(DeepClone)]` (field-wise / /// variant-wise recursion). pub trait DeepClone<'bump>: Sized { fn deep_clone(&self, bump: &'bump Arena) -> Self; } -/// `#[derive(DeepClone)]` — field-wise / variant-wise port of Zig's -/// `css.implementDeepClone`. See `src/css_derive/lib.rs` for the expansion +/// `#[derive(DeepClone)]` — field-wise / variant-wise deep clone. +/// See `src/css_derive/lib.rs` for the expansion /// rules. Re-exported here so `use crate::generics::DeepClone;` brings both /// the trait and the derive into scope (same-name trait+derive is the std /// idiom, cf. `Clone`). @@ -66,15 +63,14 @@ pub fn implement_deep_clone<'bump, T: DeepClone<'bump>>(this: &T, bump: &'bump A this.deep_clone(bump) } -// Alias: in Zig `deepClone` (structural type-dispatch entry) and -// `implementDeepClone` (field-reflection body) are distinct, but in Rust both -// collapse to `T::deep_clone` because the structural dispatch lives in the -// blanket impls below and the field-reflection lives in `#[derive(DeepClone)]`. +// Alias: structural dispatch lives in the blanket impls below and the +// field-reflection lives in `#[derive(DeepClone)]`; both collapse to +// `T::deep_clone`. // Kept as a re-export so generated code (`properties_generated.rs`) and // hand-written callers can use either name. pub use implement_deep_clone as deep_clone; -// Blanket impls covering the structural cases the Zig switch handled inline. +// Blanket impls covering the structural cases. impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for Option { #[inline] @@ -86,15 +82,13 @@ impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for Option { impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for &'bump T { #[inline] fn deep_clone(&self, bump: &'bump Arena) -> Self { - // Zig: `bun.create(arena, TT, deepClone(TT, this.*, arena))` bump.alloc((**self).deep_clone(bump)) } } impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for &'bump [T] { fn deep_clone(&self, bump: &'bump Arena) -> Self { - // Zig: alloc slice, then memcpy if simple-copy else element-wise deepClone. - // PERF(port): Zig fast-paths `isSimpleCopyType` with @memcpy — profile if hot + // PERF: element-wise deep_clone — profile if hot // (specialization would let `T: Copy` use `alloc_slice_copy`). bump.alloc_slice_fill_iter(self.iter().map(|e| e.deep_clone(bump))) } @@ -103,8 +97,7 @@ impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for &'bump [T] { impl<'bump, T: DeepClone<'bump>> DeepClone<'bump> for ArrayList<'bump, T> { #[inline] fn deep_clone(&self, bump: &'bump Arena) -> Self { - // Zig: `css.deepClone(T, arena, this)` → alloc capacity, element-wise deepClone. - // PERF(port): Zig fast-paths simple-copy types with @memcpy — profile if hot. + // PERF: element-wise deep_clone — profile if hot. let mut out = ArrayList::with_capacity_in(self.len(), bump); for item in self.iter() { out.push(item.deep_clone(bump)); @@ -191,8 +184,8 @@ pub trait CssEql { fn eql(&self, other: &Self) -> bool; } -/// `#[derive(CssEql)]` — field-wise / variant-wise port of Zig's -/// `css.implementEql`. See `src/css_derive/lib.rs` for the expansion rules. +/// `#[derive(CssEql)]` — field-wise / variant-wise equality. +/// See `src/css_derive/lib.rs` for the expansion rules. /// Re-exported here so `use crate::generics::CssEql;` brings both trait and /// derive into scope (same-name idiom, cf. `Clone`). pub use bun_css_derive::CssEql; @@ -290,7 +283,7 @@ eql_simple!(f32, f64, i32, u32, i64, u64, usize, isize, u16, bool); /// /// Exported sibling of `eql_simple!` for crate-defined types whose /// inherent `pub fn eql(&self, other) { self == other }` was a pure `PartialEq` -/// forwarder (Zig `css.implementEql(@This())` leakage). +/// forwarder. /// /// Unlike `#[derive(CssEql)]` (field-wise `.eql()` walk), this does **not** /// require every field type to itself impl `CssEql`; it bridges @@ -329,7 +322,7 @@ impl CssEql for str { impl CssEql for [T; N] { #[inline] fn eql(&self, other: &Self) -> bool { - // Zig: element-wise eql (length is `N` on both sides by type). + // Element-wise eql (length is `N` on both sides by type). for (a, b) in self.iter().zip(other.iter()) { if !a.eql(b) { return false; @@ -349,7 +342,6 @@ impl CssEql for Box { impl CssEql for VendorPrefix { #[inline] fn eql(&self, other: &Self) -> bool { - // Zig: `VendorPrefix.eql` is bitwise compare of the packed struct. *self == *other } } @@ -393,8 +385,7 @@ mod ident_eql { // Bridge inherent eql/hash/deep_clone → trait impls // // Many CSS value types carry hand-rolled inherent `eql`/`hash`/`deep_clone` -// (ported verbatim from the Zig `implementEql`/`implementHash`/ -// `implementDeepClone` bodies — usually because a field is a raw `*const [u8]` +// (usually because a field is a raw `*const [u8]` // arena slice that the derive can't see through). The `#[derive(CssEql/…)]` // expansion on *containing* types dispatches via UFCS trait paths, so those // inherent methods alone don't satisfy the bound. These thin forwarding impls @@ -865,8 +856,7 @@ mod inherent_bridge { bridge_eql_partialeq!(PropertyId); } -// Zig special-cased packed structs (`@typeInfo(T).struct.layout == .packed`) with a -// bitwise `==`. In Rust those are `bitflags!` types whose derived `PartialEq` already +// Packed bitfields are `bitflags!` types whose derived `PartialEq` already // compares contents, routed through the per-type `bridge_eql_partialeq!` calls above. // (A blanket `impl CssEql for T` would collide with the container // blanket impls under coherence, so coverage stays per-type.) @@ -882,8 +872,8 @@ pub trait CssHash { fn hash(&self, hasher: &mut Wyhash); } -/// `#[derive(CssHash)]` — field-wise / variant-wise port of Zig's -/// `css.implementHash`. See `src/css_derive/lib.rs` for the expansion rules. +/// `#[derive(CssHash)]` — field-wise / variant-wise hashing. +/// See `src/css_derive/lib.rs` for the expansion rules. /// Re-exported here so `use crate::generics::CssHash;` brings both trait and /// derive into scope. pub use bun_css_derive::CssHash; @@ -918,9 +908,8 @@ impl CssHash for () { impl CssHash for Option { #[inline] fn hash(&self, hasher: &mut Wyhash) { - // Zig `hash()` dispatcher: Some → hash inner, None → no-op (no prefix). - // The "null"/"some" prefixes are from implementHash's dead .optional arm - // (guarded by @compileError) — do NOT emit them here. + // Some → hash inner, None → no-op. Do NOT emit "null"/"some" + // prefixes here. if let Some(v) = self { v.hash(hasher); } @@ -936,7 +925,7 @@ impl CssHash for &T { impl CssHash for [T] { fn hash(&self, hasher: &mut Wyhash) { - // Zig `hash()` for `.slice` pointers iterates items only — no len prefix. + // Iterates items only — no len prefix. for item in self { item.hash(hasher); } @@ -945,8 +934,8 @@ impl CssHash for [T] { impl CssHash for [T; N] { fn hash(&self, hasher: &mut Wyhash) { - // Zig: `bun.writeAnyToHasher(hasher, list.len)` — feeds the raw bytes - // of `usize` into the hasher. `bun_core::write_any_to_hasher` exists + // Feed the raw bytes + // of the `usize` length into the hasher. `bun_core::write_any_to_hasher` exists // but is `H: Hasher`-generic and routes through `Hasher::write`, which // for `Wyhash11` calls `update` — so inlining the `usize` byte-feed // here is byte-identical and avoids the trait hop. @@ -985,7 +974,6 @@ macro_rules! hash_simple { impl CssHash for $t { #[inline] fn hash(&self, hasher: &mut Wyhash) { - // Zig: `hasher.update(std.mem.asBytes(&this))` hasher.update(&self.to_ne_bytes()); } } @@ -1025,7 +1013,6 @@ impl CssHash for Box { impl CssHash for VendorPrefix { #[inline] fn hash(&self, hasher: &mut Wyhash) { - // Zig: `hasher.update(std.mem.asBytes(&this))` on the packed-struct repr. hasher.update(&[self.as_bits()]); } } @@ -1033,8 +1020,7 @@ impl CssHash for VendorPrefix { impl CssHash for bun_ast::Loc { #[inline] fn hash(&self, hasher: &mut Wyhash) { - // Zig `implementHash` doesn't reach `Loc` (callers skip it), but - // providing a structural hash here lets `#[derive(CssHash)]` types + // Providing a structural hash here lets `#[derive(CssHash)]` types // include a `loc` field without `#[css(skip)]` if they want. hasher.update(&self.start.to_ne_bytes()); } @@ -1110,9 +1096,8 @@ impl IsCompatible for Box { impl IsCompatible for Option { #[inline] fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool { - // Zig's `isCompatible` doesn't special-case Optional, but every - // hand-written caller treats absent as compatible (no value → no - // feature gate to check). + // Every hand-written caller treats absent as compatible (no value → + // no feature gate to check). match self { Some(v) => v.is_compatible(browsers), None => true, @@ -1146,7 +1131,7 @@ impl IsCompatible for Vec { } } -// The Zig original blanket-impls over "any list container". A Rust blanket +// A blanket // `impl` conflicts with the `&T` impl above (coherence can't // prove `&T` never impls `ListContainer`), so spell out the three concrete // container types instead. @@ -1175,8 +1160,7 @@ is_compatible_container!( // ─────────────────────────────────────────────────────────────────────────────── // Parse / ParseWithOptions // ─────────────────────────────────────────────────────────────────────────────── -// Zig's `generic.parse(T, input)` / `generic.parseWithOptions(T, input, opts)` -// dispatch via `@hasDecl(T, "parse"[WithOptions])`. In Rust each leaf value +// Each leaf value // type either hand-writes an inherent `parse(&mut Parser) -> CssResult` // or derives one via `#[derive(Parse)]` / `#[derive(DefineEnumProperty)]`; the // trait below is the uniform bound that `Property::parse` and the container @@ -1194,8 +1178,8 @@ pub trait Parse: Sized { /// `T::parse_with_options(&mut Parser, &ParserOptions) -> CssResult`. /// -/// Zig falls through to `parse` when a type has no `parseWithOptions` decl. -/// Rust can't express that as a `where Self: Parse` default method +/// Falling back to `parse` when a type has no `parse_with_options` +/// can't be expressed as a `where Self: Parse` default method /// — the bound becomes part of the *method signature*, so the free /// `parse_with_options::` below would require `T: Parse` even for impls /// that override the body. Instead the fallthrough lives in @@ -1251,7 +1235,7 @@ impl ParseWithOptions for Vec { } } -// Zig `.pointer` arm (`generics.zig:273-279`): parse the pointee then heap-allocate. +// Parse the pointee then heap-allocate. impl Parse for Box { #[inline] fn parse(input: &mut Parser) -> CssResult { @@ -1357,7 +1341,7 @@ impl ToCss for &T { } } -// Zig `.pointer` arm (`generics.zig:338-341`): recurse into `*T` pointee. +// Recurse into the pointee. impl ToCss for Box { #[inline] fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { @@ -1524,11 +1508,11 @@ impl TrySign for CSSNumber { Some(CSSNumberFns::sign(*self)) } } -// Zig fell back via `@hasDecl(T, "sign")`; Rust has no decl probing, so each type +// Each type // implements `TrySign` explicitly (delegating to its inherent `sign` where one exists). pub trait TryMap: Sized { - // Zig: `comptime map_fn: *const fn(f32) f32` — generic param preserves monomorphization. + // Generic param preserves monomorphization. fn try_map(&self, map_fn: impl Fn(f32) -> f32) -> Option; } @@ -1540,7 +1524,7 @@ impl TryMap for CSSNumber { } pub trait TryOpTo: Sized { - // Zig: `comptime op_fn: *const fn(...)` — generic param preserves monomorphization. + // Generic param preserves monomorphization. // `R` is method-generic (not trait-generic) so `TryOpTo` can appear as a // supertrait bound without committing to a result type. fn try_op_to(&self, rhs: &Self, ctx: C, op_fn: impl Fn(C, f32, f32) -> R) -> Option; @@ -1561,7 +1545,7 @@ impl IsCompatible for CSSNumber { } pub trait TryOp: Sized { - // Zig: `comptime op_fn: *const fn(...)` — generic param preserves monomorphization. + // Generic param preserves monomorphization. fn try_op(&self, rhs: &Self, ctx: C, op_fn: impl Fn(C, f32, f32) -> f32) -> Option; } @@ -1631,5 +1615,3 @@ impl MulF32 for CSSNumber { self * rhs } } - -// ported from: src/css/generics.zig diff --git a/src/css/lib.rs b/src/css/lib.rs index 39f791191b71..80da3aa27ac6 100644 --- a/src/css/lib.rs +++ b/src/css/lib.rs @@ -5,14 +5,12 @@ // were translated against the crate's public surface and refer to it by name. extern crate self as bun_css; -/// Case-insensitive ASCII byte-slice dispatch — the fix for Zig's -/// `css.todo_stuff.match_ignore_ascii_case` sentinel and a drop-in port of +/// Case-insensitive ASCII byte-slice dispatch — equivalent to /// rust-cssparser's `match_ignore_ascii_case!`. /// /// Expands to an `if / else if / else` chain over /// [`bun_core::strings::eql_case_insensitive_ascii_check_length`] (length-checked, -/// ASCII-fold only, byte-wise — identical to Zig's -/// `bun.strings.eqlCaseInsensitiveASCIIICheckLength`). The whole macro is an +/// ASCII-fold only, byte-wise). The whole macro is an /// expression; arms may `return`, `break 'label`, or yield a value. /// /// Supports `|`-alternation and Rust-style `if` guards on arms; the trailing @@ -77,9 +75,9 @@ pub mod declaration; pub use context::{DeclarationContext, PropertyHandlerContext, SupportsEntry}; pub use declaration::{DeclarationBlock, DeclarationHandler, DeclarationList}; -// Path aliases the ported submodules expect at crate root (Zig's `css.*` -// namespace was flat; the Rust port re-nests under `values/`/`properties/` -// but most callers still spell `bun_css::css_values::...`). +// Path aliases the submodules expect at crate root (the crate re-nests under +// `values/`/`properties/` but most callers still spell +// `bun_css::css_values::...`). pub use properties as css_properties; pub use rules as css_rules; pub use selectors::selector; @@ -93,10 +91,10 @@ pub use css_parser::{ }; // ─── selectors/ crate-root surface ──────────────────────────────────────── -// The selector grammar references these via `bun_css::*` (Zig's flat `css.*` -// namespace). `Str` is the arena-borrowed `[]const u8` slice alias; here -// it's `*const [u8]` (matches `error.rs` / `values::ident` field shape) and -// becomes `&'bump [u8]` once the arena lifetime is plumbed. +// The selector grammar references these via `bun_css::*`. `Str` is the +// arena-borrowed byte-slice alias; here it's `*const [u8]` (matches +// `error.rs` / `values::ident` field shape) and becomes `&'bump [u8]` once +// the arena lifetime is plumbed. pub(crate) type Str = *const [u8]; /// Dereference an arena-owned [`Str`] into a slice borrow. @@ -119,23 +117,22 @@ pub(crate) unsafe fn arena_str(p: Str) -> &'static [u8] { unsafe { &*p } } pub use compat::Feature; -/// `css::ParseErrorKind` — Zig spelling. Alias of `error::ParserErrorKind`. +/// Alias of `error::ParserErrorKind`. pub use error::ParserErrorKind as ParseErrorKind; pub use error::ParserErrorKind as ErrorKind; pub use properties::custom::{TokenList, TokenListFns}; pub use values::ident::{CustomIdentFns, DashedIdentFns, IdentFns}; pub use values::string::{CssString as CSSString, CssStringFns as CSSStringFns}; -// `css::generic::*` is the Zig-spelled namespace for the protocol traits + -// reflection helpers. The Rust module is `generics`; alias both spellings so -// value/property modules can use `crate::generic::partial_cmp_f32` etc. +// `css::generic::*` is an alternate spelling of the protocol traits + +// reflection helpers in `generics`; alias both spellings so value/property +// modules can use `crate::generic::partial_cmp_f32` etc. pub use generics as generic; pub use generics::{implement_deep_clone, implement_eql, implement_hash}; // Same-name trait + derive macro re-export so `#[derive(bun_css::DeepClone)]` // (and `use bun_css::DeepClone;` at leaf sites) brings both into scope. pub use generics::{CssEql, DeepClone}; -// Keyword-enum / `union(enum)` derive macros (port of Zig's `DefineEnumProperty` -// / `DeriveParse` / `DeriveToCss` comptime fns). The `EnumProperty` *trait* is +// Keyword-enum / tagged-union derive macros. The `EnumProperty` *trait* is // re-exported above from `css_parser`; the *derive* of the same name lives in // the proc-macro crate. pub use bun_css_derive::{DefineEnumProperty, Parse, ToCss}; @@ -164,8 +161,6 @@ pub mod values_stub { pub mod color { pub use crate::values::color::*; - /// `Result(CssColor)` — Zig: `pub const ParseResult = Result(CssColor);` - /// where `Result(T) = Maybe(T, ParseError(ParserError))` (css_parser.zig:278). /// `Maybe` is now un-gated as `core::result::Result`, so this is a /// straight type alias to the real `values::color::ParseResult`. pub type CssColorParseResult = crate::values::color::ParseResult; @@ -208,7 +203,7 @@ impl core::fmt::Display for PrintErr { } impl core::error::Error for PrintErr {} -/// `PrintErr!T` return shape (Zig: `PrintErr!void`) used by every `to_css` +/// Return shape used by every `to_css` /// path. Distinct from `css_parser::PrintResult = Maybe`, /// which carries the rich `Err` — this is just the bubbled /// signal (the *kind* lives in `Printer.error_kind`). @@ -223,8 +218,7 @@ pub use css_parser::{ ParserOptions, StyleAttribute, StyleSheet, StylesheetExtra, ToCssResult, }; pub use printer::{ImportInfo, Printer, PrinterOptions, PseudoClasses}; -/// Dependent crates name this `ImportRecordHandler` (Zig had a now-removed -/// union of the same name in css_parser.zig:3783); the surviving type is +/// Dependent crates name this `ImportRecordHandler`; the surviving type is /// `printer::ImportInfo`, exposed under both names. pub type ImportRecordHandler<'a> = printer::ImportInfo<'a>; pub use values::color::{CssColor, FloatColor, LABColor, LabColor, PredefinedColor, RGBA}; @@ -280,7 +274,7 @@ impl VendorPrefix { ]; // NOTE: bitflags 2.x already generates `from_name(&str) -> Option`; - // the Zig `fromName` (panicking) is exposed as `from_name_str`. + // the panicking variant is exposed as `from_name_str`. #[inline] pub fn from_name_str(name: &str) -> VendorPrefix { match name { @@ -306,7 +300,7 @@ impl VendorPrefix { } pub fn difference_(left: Self, right: Self) -> Self { - // Zig used arithmetic subtraction on bits; preserve that. + // Arithmetic subtraction on bits, not set difference; callers depend on it. Self::from_bits_retain(left.bits().wrapping_sub(right.bits())) } @@ -441,8 +435,8 @@ pub enum Token { impl core::fmt::Display for Token { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - // Minimal rendering for error messages. The full Zig `Token.format` - // (CSS serialization) lives in `css_parser.rs` via `serializer::*`. + // Minimal rendering for error messages. Full CSS serialization + // lives in `css_parser.rs` via `serializer::*`. use bstr::BStr; match self { Token::Ident(v) diff --git a/src/css/logical.rs b/src/css/logical.rs index 4847ed448d0e..a88c79c86830 100644 --- a/src/css/logical.rs +++ b/src/css/logical.rs @@ -20,5 +20,3 @@ pub enum LogicalGroup { MinSize, MaxSize, } - -// ported from: src/css/logical.zig diff --git a/src/css/media_query.rs b/src/css/media_query.rs index 7501eaeb3f5f..28b0014fd89b 100644 --- a/src/css/media_query.rs +++ b/src/css/media_query.rs @@ -1,6 +1,4 @@ //! CSS [media queries](https://drafts.csswg.org/mediaqueries/). -//! -//! Ported from `src/css/media_query.zig`. use crate as css; use crate::css_properties::custom::EnvironmentVariable; @@ -29,12 +27,10 @@ type CSSInteger = i32; // Selector, Unknown} and its `needs_parens(&Self)` / `b" not "` contract are // structurally different and must stay hand-rolled. // -// `deep_clone` is intentionally NOT on this trait. The Zig precedent is ONE -// reflective `css.implementDeepClone` (generics.zig); the Rust equivalent is +// `deep_clone` is intentionally NOT on this trait. The single mechanism is // `#[derive(DeepClone)]` (generics.rs). The hand-expansions in callers exist // only because of derive blockers — fix the derive, not the trait. -/// Trait modeling Zig's `ValidQueryCondition` comptime interface check. /// Any type that can appear as a node in a query-condition tree. pub trait QueryCondition: Sized + ToCss { /// Leaf payload: `QueryFeature<_>` for media/container, `Property` for @@ -185,8 +181,7 @@ pub enum MediaType { } // Hand-rolled — derived PartialEq on `*const [u8]` compares -// address+len, not byte content. Spec `MediaType.eql` compares slice bytes -// (via `css.implementEql`); adjacent-@media merging (rules.zig) depends on +// address+len, not byte content. Adjacent-@media merging depends on // content equality across distinct arena offsets. impl PartialEq for MediaType { fn eq(&self, other: &Self) -> bool { @@ -253,8 +248,7 @@ pub enum MediaFeatureName { Unknown(Ident), } -// Data-only PartialEq, derived once `Ident`/`DashedIdent` gained `PartialEq` -// (Zig's `eql` was a hand-written byte compare on the ident slices). +// Data-only PartialEq, derived once `Ident`/`DashedIdent` gained `PartialEq`. impl PartialEq for MediaFeatureName { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -294,9 +288,9 @@ pub enum QueryFeature { } /// Comparison operator in a range media feature. -// Discriminants are power-of-two bitflags — Zig media_query.zig -// bitwise-ORs `@intFromEnum(start_operator) | @intFromEnum(end_operator)` to -// validate interval operator pairs. Do NOT use implicit 0..=4. +// Discriminants are power-of-two bitflags — interval-operator-pair +// validation bitwise-ORs the start/end operator discriminants. +// Do NOT use implicit 0..=4. #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::IntoStaticStr)] pub enum MediaFeatureComparison { @@ -354,7 +348,7 @@ impl core::fmt::Debug for MediaFeatureValue { } // derive(PartialEq) blocked on `Ident`/`EnvironmentVariable` lacking -// std `PartialEq`; hand-roll all arms (Zig: `css.implementEql`). +// std `PartialEq`; hand-roll all arms. impl PartialEq for MediaFeatureValue { fn eq(&self, other: &Self) -> bool { use MediaFeatureValue as V; @@ -366,8 +360,7 @@ impl PartialEq for MediaFeatureValue { (V::Resolution(a), V::Resolution(b)) => a == b, (V::Ratio(a), V::Ratio(b)) => a == b, (V::Ident(a), V::Ident(b)) => a.v() == b.v(), - // Zig: `css.implementEql` recurses into `EnvironmentVariable.eql` — - // ported via the `CssEql` derive on `EnvironmentVariable` + // `CssEql` derive on `EnvironmentVariable` // (name + indices + fallback structural equality). (V::Env(a), V::Env(b)) => { use crate::generics::CssEql as _; @@ -400,7 +393,6 @@ pub enum MediaFeatureType { } impl MediaFeatureType { - /// Zig: `MediaFeatureType.allowsRanges`. pub fn allows_ranges(self) -> bool { use MediaFeatureType as T; matches!( @@ -410,8 +402,7 @@ impl MediaFeatureType { } } -/// Trait modeling Zig's `MediaFeatureId`-shape comptime interface for the -/// generic `QueryFeature`. +/// Feature-id interface for the generic `QueryFeature`. pub trait FeatureIdTrait: Copy + PartialEq + Eq { fn value_type(&self) -> MediaFeatureType; fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr>; @@ -552,7 +543,6 @@ pub enum MediaFeatureId { } impl MediaFeatureId { - // Zig: `pub const valueType = css.DeriveValueType(@This(), ValueTypeMap).valueType;` pub(crate) fn value_type(self) -> MediaFeatureType { use MediaFeatureId::*; use MediaFeatureType as T; @@ -600,8 +590,8 @@ impl FeatureIdTrait for MediaFeatureId { MediaFeatureId::value_type(*self) } fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { - // Zig: `css.DefineEnumProperty(@This()).toCss` — emits the lowercase - // tag name. `strum::IntoStaticStr` already carries those strings. + // Emits the lowercase tag name. `strum::IntoStaticStr` already + // carries those strings. dest.write_str(<&'static str>::from(*self)) } fn to_css_with_prefix( @@ -610,8 +600,8 @@ impl FeatureIdTrait for MediaFeatureId { dest: &mut Printer, ) -> core::result::Result<(), PrintErr> { match self { - // Zig: `-webkit-{s}device-pixel-ratio` — webkit places the - // min/max prefix between the vendor prefix and the feature name. + // webkit places the min/max prefix between the vendor prefix + // and the feature name. MediaFeatureId::WebkitDevicePixelRatio => { dest.write_str("-webkit-")?; dest.write_str(prefix)?; @@ -624,8 +614,8 @@ impl FeatureIdTrait for MediaFeatureId { } } fn from_str(s: &[u8]) -> Option { - // Zig: `css.DefineEnumProperty(@This()).parse` — case-insensitive - // ASCII tag-name table. No dependency on the gated `values/` lattice. + // Case-insensitive ASCII tag-name table. No dependency on the gated + // `values/` lattice. use MediaFeatureId::*; crate::match_ignore_ascii_case! { s, { b"width" => Some(Width), @@ -760,7 +750,7 @@ impl MediaQuery { } } -/// Zig: `toCssWithParensIfNeeded` — wraps `v.to_css()` in parentheses when the +/// Wraps `v.to_css()` in parentheses when the /// caller's grammar position requires it. pub(crate) fn to_css_with_parens_if_needed( v: &T, @@ -777,7 +767,7 @@ pub(crate) fn to_css_with_parens_if_needed( Ok(()) } -/// Zig: `operationToCss` — serialize `a OP b OP c ...` with per-child parens. +/// Serialize `a OP b OP c ...` with per-child parens. pub(crate) fn operation_to_css( operator: Operator, conditions: &[C], @@ -866,7 +856,6 @@ impl QueryCondition for MediaCondition { } } fn parse_style_query(input: &mut Parser) -> Result { - // Zig: `return .{ .err = input.newErrorForNextToken() }` Err(input.new_error_for_next_token()) } fn needs_parens( @@ -950,7 +939,6 @@ impl QueryFeature { } impl MediaFeatureName { - /// Zig: `MediaFeatureName.valueType`. pub(crate) fn value_type(&self) -> MediaFeatureType { match self { MediaFeatureName::Standard(standard) => standard.value_type(), @@ -987,8 +975,6 @@ impl MediaFeatureName { /// Parses a media feature name. Returns `(name, legacy_comparator)` — /// `legacy_comparator` is `Some` when the ident carried a `min-`/`max-` /// prefix (lowered to `>=`/`<=`). - /// - /// Zig: `MediaFeatureName.parse`. pub(crate) fn parse(input: &mut Parser) -> Result<(Self, Option)> { use bun_core::strings; let ident = input.expect_ident_cloned()?; @@ -1017,13 +1003,9 @@ impl MediaFeatureName { None }; - // Zig `allocPrint("-webkit-{s}", .{name})` then - // `parse_utility.parseString(.., FeatureId.parse)` — the re-tokenize is - // only to feed `DefineEnumProperty.parse` an ident token. Here - // `FeatureIdTrait::from_str` does the same case-insensitive table lookup - // directly, so a stack buffer suffices and the temp string is freed - // immediately (Zig asserts `FeatureId` is an enum for the same reason). - // PERF: stack buffer here? + // `FeatureIdTrait::from_str` does a case-insensitive table lookup + // directly, so a stack buffer suffices for re-attaching the + // `-webkit-` prefix. let mut webkit_buf: [u8; 64] = [0; 64]; let final_name: &[u8] = if is_webkit { let len = 8 + name.len(); @@ -1053,8 +1035,8 @@ impl MediaFeatureName { impl MediaFeatureComparison { pub(crate) fn to_css(self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { match self { - // Suspect but intentional: Zig emits '-' for `Equal` (media_query.zig:1156), - // diverging from the spec `=` and from this enum's strum tag. Ported + // Suspect but intentional: emits '-' for `Equal`, diverging from + // the spec `=` and from this enum's strum tag. Preserved // byte-for-byte; revisit if upstream fixes. MediaFeatureComparison::Equal => dest.delim(b'-', true), MediaFeatureComparison::GreaterThan => dest.delim(b'>', true), @@ -1103,11 +1085,10 @@ impl MediaFeatureValue { } } - /// Zig: `addF32` — adjust by `other` for strict-inequality → min/max + /// Adjust by `other` for strict-inequality → min/max /// boundary lowering. Consumes `self`. pub(crate) fn add_f32(self, other: f32) -> MediaFeatureValue { match self { - // Zig: `len.add(arena, Length.px(other))` — calc lattice. MediaFeatureValue::Length(len) => MediaFeatureValue::Length(len.add(Length::px(other))), MediaFeatureValue::Number(num) => MediaFeatureValue::Number(num + other), MediaFeatureValue::Integer(num) => { @@ -1117,16 +1098,14 @@ impl MediaFeatureValue { MediaFeatureValue::Resolution(res) => MediaFeatureValue::Resolution(res.add_f32(other)), MediaFeatureValue::Ratio(ratio) => MediaFeatureValue::Ratio(ratio.add_f32(other)), MediaFeatureValue::Ident(id) => MediaFeatureValue::Ident(id), - // Matches Zig (media_query.zig `addF32`): env() values pass through - // unchanged — the calc-lattice add (wrapping the env() reference in a - // calc() to apply the strict-inequality boundary adjustment) is not - // implemented in either port, so range bounds on env() values lose the - // +/-0.001 nudge. + // env() values pass through unchanged — the calc-lattice add + // (wrapping the env() reference in a calc() to apply the + // strict-inequality boundary adjustment) is not implemented, so + // range bounds on env() values lose the +/-0.001 nudge. MediaFeatureValue::Env(env) => MediaFeatureValue::Env(env), } } - /// Zig: `MediaFeatureValue.valueType`. pub(crate) fn value_type(&self) -> MediaFeatureType { use MediaFeatureValue as V; match self { @@ -1141,7 +1120,6 @@ impl MediaFeatureValue { } } - /// Zig: `MediaFeatureValue.checkType`. pub(crate) fn check_type(&self, expected_type: MediaFeatureType) -> bool { let vt = self.value_type(); if expected_type == MediaFeatureType::Unknown || vt == MediaFeatureType::Unknown { @@ -1213,10 +1191,8 @@ impl MediaFeatureValue { return Ok(MediaFeatureValue::Resolution(res)); } - // Zig `input.tryParse(EnvironmentVariable.parse, .{})` left - // `options`/`depth` undefined (tryParse builds `ArgsTuple` and only - // fills index 0) — UB. Fixed here by threading the real `ParserOptions` - // down from `QueryFeature::parse` and passing `depth = 0`. + // The real `ParserOptions` are threaded down from + // `QueryFeature::parse`; `depth = 0` since this is a fresh value. if let Ok(env) = input.try_parse(|i| EnvironmentVariable::parse(i, options, 0)) { return Ok(MediaFeatureValue::Env(env)); } @@ -1226,7 +1202,7 @@ impl MediaFeatureValue { } } -/// Zig: `writeMinMax` — lower a range/interval comparator to legacy +/// Lower a range/interval comparator to legacy /// `min-`/`max-` prefixed plain feature. fn write_min_max( operator: MediaFeatureComparison, @@ -1250,8 +1226,7 @@ fn write_min_max( dest.delim(b':', false)?; - // Zig deepCloned `value` into `dest.arena` then mutated; here - // `MediaFeatureValue: Clone` so we clone-by-value. + // `MediaFeatureValue: Clone`, so clone-by-value before adjusting. let adjusted: Option = match operator { MediaFeatureComparison::GreaterThan => Some(value.clone().add_f32(0.001)), MediaFeatureComparison::LessThan => Some(value.clone().add_f32(-0.001)), @@ -1268,19 +1243,18 @@ fn write_min_max( } // ───────────────────────── deep_clone ───────────────────────── -// Arena-aware `deep_clone` — port of Zig's per-type `deepClone(arena)` -// bodies. Un-gated this round so `rules::dc::{media_list,query_feature}` can -// route through real impls instead of `#[derive(Clone)]` passthroughs. +// Arena-aware `deep_clone`. Un-gated this round so +// `rules::dc::{media_list,query_feature}` can route through real impls +// instead of `#[derive(Clone)]` passthroughs. // -// Written as **inherent** methods (not `#[derive(DeepClone)]`) to -// match the Zig hand-written bodies exactly: Zig copies `name`/`qualifier`/ -// `media_type`/`operator` fields by value (they are `Copy`/arena-slice types -// under the generics.zig "const strings" rule) and only recurses into the -// allocating payloads (`Vec`, `Box`, `MediaFeatureValue`). The derive would +// Written as **inherent** methods (not `#[derive(DeepClone)]`): +// `name`/`qualifier`/`media_type`/`operator` fields are copied by value +// (they are `Copy`/arena-slice types) and only the allocating payloads +// (`Vec`, `Box`, `MediaFeatureValue`) are recursed into. The derive would // instead add a spurious `FeatureId: DeepClone<'bump>` where-bound. impl MediaList { - /// Zig: `MediaList.deepClone` — element-wise clone of `media_queries`. + /// Element-wise clone of `media_queries`. pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { let mut media_queries = Vec::with_capacity_in(self.media_queries.len(), ArenaPtr::new(bump)); @@ -1288,13 +1262,13 @@ impl MediaList { Self { media_queries } } - /// Zig: `pub fn clone(this, arena)` — alias for `deepClone`. + /// Alias for `deep_clone`. #[inline] pub fn clone_in(&self, bump: &bun_alloc::Arena) -> Self { self.deep_clone(bump) } - /// Zig: `MediaList.cloneWithImportRecords` — `MediaList` carries no + /// `MediaList` carries no /// `ImportRecord` indices so this is just `deep_clone`. #[inline] pub fn clone_with_import_records( @@ -1305,7 +1279,7 @@ impl MediaList { self.deep_clone(bump) } - /// Zig: `pub const eql = css.implementEql(@This())` — structural eq. + /// Structural eq. #[inline] pub fn eql(&self, other: &Self) -> bool { self == other @@ -1313,7 +1287,7 @@ impl MediaList { } impl MediaQuery { - /// Zig: `MediaQuery.deepClone` — field-wise. + /// Field-wise. pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { Self { qualifier: self.qualifier, @@ -1324,8 +1298,7 @@ impl MediaQuery { } impl MediaType { - /// Zig: `css.implementDeepClone` — `Custom([]const u8)` is an arena-owned - /// slice (identity copy under the generics.zig "const strings" rule). + /// `Custom` is an arena-owned slice (identity copy). #[inline] pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { *self @@ -1333,14 +1306,13 @@ impl MediaType { } impl MediaCondition { - /// Zig: `MediaCondition.deepClone` — variant-wise recursion. + /// Variant-wise recursion. pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { let alloc = ArenaPtr::new(bump); match self { MediaCondition::Feature(f) => { MediaCondition::Feature(Box::new_in(f.deep_clone(bump), alloc)) } - // Zig: `bun.create(arena, MediaCondition, c.deepClone(arena))` MediaCondition::Not(c) => MediaCondition::Not(Box::new_in(c.deep_clone(bump), alloc)), MediaCondition::Operation { operator, @@ -1358,7 +1330,7 @@ impl MediaCondition { } impl MediaFeatureName { - /// Zig: struct-copy (`name = this.plain.name`). All payloads are `Copy` / + /// All payloads are `Copy` / /// arena-slice idents; `derive(Clone)` is the faithful deep clone. #[inline] pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { @@ -1367,7 +1339,7 @@ impl MediaFeatureName { } impl QueryFeature { - /// Zig: `QueryFeature.deepClone` — variant-wise; `name`/`operator` are + /// Variant-wise; `name`/`operator` are /// value-copied, `MediaFeatureValue` recurses. pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { match self { @@ -1405,14 +1377,13 @@ impl QueryFeature { } impl MediaFeatureValue { - /// Zig: `MediaFeatureValue.deepClone` — variant-wise. + /// Variant-wise. pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { use MediaFeatureValue as V; match self { - // Zig: `l.deepClone(arena)` — real `values::length::Length` - // owns a calc tree. The local `value_shims::Length` stand-in is a - // unit struct, so `Clone` is faithful until the calc lattice - // un-gates and the shim is replaced. + // The real `values::length::Length` owns a calc tree. The local + // `value_shims::Length` stand-in is a unit struct, so `Clone` is + // faithful until the calc lattice un-gates and the shim is replaced. V::Length(l) => V::Length(l.clone()), V::Number(n) => V::Number(*n), V::Integer(i) => V::Integer(*i), @@ -1420,7 +1391,7 @@ impl MediaFeatureValue { V::Resolution(r) => V::Resolution(*r), V::Ratio(r) => V::Ratio(*r), V::Ident(i) => V::Ident(i.deep_clone(bump)), - // Zig: `e.deepClone(arena)` — `EnvironmentVariable` carries + // `EnvironmentVariable` carries // `Vec` + `Option`; route through its // `#[derive(DeepClone)]` impl. V::Env(e) => { @@ -1478,7 +1449,6 @@ impl MediaList { match input.next() { Ok(tok) => { if !matches!(tok, css::Token::Comma) { - // Zig: bun.Output.panic(...) — see media_query.zig:54. unreachable!( "expected a comma after parsing a MediaQuery — bug in CSS parser" ); @@ -1494,7 +1464,6 @@ impl MediaList { impl MediaQuery { pub fn parse(input: &mut Parser, options: &css::ParserOptions) -> Result { - // Zig: `Fn.tryParseFn` returning `(?Qualifier, ?MediaType)`. let (qualifier, explicit_media_type) = input .try_parse(|i| -> Result<(Option, Option)> { let qualifier = i.try_parse(Qualifier::parse).ok(); @@ -1651,8 +1620,6 @@ fn parse_paren_block( flags: QueryConditionFlags, options: &css::ParserOptions, ) -> Result { - // Zig: `Closure { flags }.parseNestedBlockFn` — collapsed to a closure - // capturing `flags`/`options` by copy/reborrow. input.parse_nested_block(|i| { if let Ok(inner) = i.try_parse(|i2| parse_query_condition_with_options::(i2, flags, options)) @@ -1666,8 +1633,6 @@ fn parse_paren_block( impl QueryFeature { /// Parse a media/container feature inside `(` `)`. /// - /// Zig: `QueryFeature.parse` (media_query.zig:945). - /// /// Forwarder kept for callers that don't yet thread `ParserOptions` /// (e.g. `rules::container::ContainerCondition::parse_feature`). #[inline] @@ -1695,7 +1660,6 @@ impl QueryFeature { } } - /// Zig: `QueryFeature.parseNameFirst`. pub(crate) fn parse_name_first( input: &mut Parser, options: &css::ParserOptions, @@ -1731,18 +1695,15 @@ impl QueryFeature { } } - /// Zig: `QueryFeature.parseValueFirst`. pub(crate) fn parse_value_first( input: &mut Parser, options: &css::ParserOptions, ) -> Result { // We need to find the feature name first so we know the type. let start = input.state(); - // Zig loops `MediaFeatureName.parse` then checks - // `isExhausted()` — but `expectIdent` does not advance on error, so - // the literal Zig body would spin on a non-ident token. The intent - // (matching lightningcss) is to *skip* tokens until the name is - // found; advance one token per failed attempt. + // Skip tokens (matching lightningcss) until the name is found; + // advance one token per failed attempt so a non-ident token cannot + // cause an infinite loop. let name: MediaFeatureName = loop { if let Ok((name, legacy_op)) = input.try_parse(MediaFeatureName::::parse) { if legacy_op.is_some() { @@ -1776,7 +1737,7 @@ impl QueryFeature { let end_operator = end_operator_.unwrap(); // Start and end operators must be matching. // Discriminants are bitflags (1/2/4/8/16) — see the - // comment on `MediaFeatureComparison`. Zig bitwise-ORs them. + // comment on `MediaFeatureComparison`. const GT: u8 = MediaFeatureComparison::GreaterThan as u8; const GTE: u8 = MediaFeatureComparison::GreaterThanEqual as u8; const LT: u8 = MediaFeatureComparison::LessThan as u8; @@ -1820,7 +1781,7 @@ impl QueryFeature { /// Consumes an operation or a colon, or returns an error. /// -/// Zig: `consumeOperationOrColon` (media_query.zig:1103). Returns `Ok(None)` +/// Returns `Ok(None)` /// when a colon was consumed (and `allow_colon`); `Ok(Some(op))` for `<`/`>`/`=`. fn consume_operation_or_colon( input: &mut Parser, @@ -1854,5 +1815,3 @@ fn consume_operation_or_colon( _ => Err(location.new_unexpected_token_error(css::Token::Delim(first_delim))), } } - -// ported from: src/css/media_query.zig diff --git a/src/css/prefixes.rs b/src/css/prefixes.rs index 2fce358a90f1..9e9d39324d8f 100644 --- a/src/css/prefixes.rs +++ b/src/css/prefixes.rs @@ -1,4 +1,4 @@ -// Ported from src/css/prefixes.zig (autogenerated by build-prefixes.js). +// Autogenerated by build-prefixes.js. // DO NOT EDIT BY HAND — regenerate via `build-prefixes.js --rs` when the // caniuse tables change. diff --git a/src/css/printer.rs b/src/css/printer.rs index 93302f34653f..42bc1815367a 100644 --- a/src/css/printer.rs +++ b/src/css/printer.rs @@ -15,7 +15,7 @@ use css_values::ident::DashedIdent; pub use css::Error; -// Byte-oriented writer (writeAll/writeByte/print equivalents); Zig used *std.Io.Writer. +// Byte-oriented writer (writeAll/writeByte/print equivalents). use bun_io::Write; /// Options that control how CSS is serialized to a string. @@ -220,10 +220,8 @@ impl<'a> Printer<'a> { self.lookup_symbol(ident.as_ref().unwrap()) } - // Zig checked vtable identity against std.Io.Writer.Allocating and recovered the - // backing buffer length via `container_of`; in Rust the trait exposes `written_len()` - // directly (Vec / MutableString / counting sinks override it, others panic — same - // contract as Zig's `@panic("css: got bad writer type")` fallthrough). + // The `Write` trait exposes `written_len()` directly + // (Vec / MutableString / counting sinks override it, others panic). #[inline] fn get_written_amt(writer: &dyn Write) -> usize { writer.written_len() @@ -340,7 +338,6 @@ impl<'a> Printer<'a> { column: 1, }, symbols, - // defaults for fields not set by Zig's `.{}` initializer indent_amt: 0, line: 0, col: 0, @@ -356,9 +353,8 @@ impl<'a> Printer<'a> { } /// Construct a `Printer` that writes into an in-memory `Vec` buffer - /// using default `PrinterOptions`. Mirrors the Zig pattern of pairing - /// `std.Io.Writer.Allocating` with `Printer.new(..., PrinterOptions.default(), ...)` - /// for sub-serialization (e.g. `PseudoClass::toCss`, `Selector` debug fmt). + /// using default `PrinterOptions`. Used for sub-serialization + /// (e.g. `PseudoClass::toCss`, `Selector` debug fmt). pub fn new_buffered( arena: &'a Bump, dest: &'a mut Vec, @@ -393,7 +389,7 @@ impl<'a> Printer<'a> { // Reshaped for borrowck — copied (a, b) out before re-borrowing &mut self let a = a.to_vec(); let b = b.to_vec(); - // PERF(port): two small heap copies above; Zig borrowed directly. Profile if it shows up on a hot path. + // PERF: two small heap copies above; profile if it shows up on a hot path. self.write_str(&a)?; self.write_str(&b)?; return Ok(()); @@ -596,10 +592,8 @@ impl<'a> Printer<'a> { pub fn write_ident(&mut self, ident: &'a [u8], handle_css_module: bool) -> PrintResult<()> { if handle_css_module { if self.css_module.is_some() { - // Borrowck reshape — Zig captured `&mut self` inside the closure - // while `css_module` (a field of self) was simultaneously borrowed. We instead - // copy the `'a`-lifetime references out of `css_module` up front so the - // closure can hold the sole `&mut self`. + // Copy the `'a`-lifetime references out of `css_module` up front so + // the closure can hold the sole `&mut self`. let source_index = self.loc.source_index as usize; let arena = self.arena; let (config, hash, source): (&'a css::css_modules::Config, &'a [u8], &'a [u8]) = { @@ -844,7 +838,7 @@ impl<'a> Printer<'a> { let ctx = css::StyleContext { selectors, parent }; - // `&ctx` is stack-local but the field type is `&'a StyleContext<'a>`; like Zig, + // `&ctx` is stack-local but the field type is `&'a StyleContext<'a>`; // soundness relies on restoring `parent` before this frame returns. // SAFETY: ctx outlives the call to func; self.ctx is restored to `parent` before return. // Inner-lifetime variance cast via raw pointer (`StyleContext<'x>` and @@ -899,5 +893,3 @@ impl<'a> Printer<'a> { // bun.ast.Symbol.Map — lives in bun_logger. type SymbolMap = bun_ast::symbol::Map; - -// ported from: src/css/printer.zig diff --git a/src/css/properties/align.rs b/src/css/properties/align.rs index 30ffee5e7e1b..2c5618c7086c 100644 --- a/src/css/properties/align.rs +++ b/src/css/properties/align.rs @@ -18,8 +18,6 @@ use crate::css_properties::flex::{ /// A value for the [align-content](https://www.w3.org/TR/css-align-3/#propdef-align-content) property. #[derive(Clone, PartialEq, Eq)] -// Zig: `css.DeriveParse(@This()).parse` / `css.DeriveToCss(@This()).toCss` — -// comptime-reflection generators ported as proc-macro derives. #[derive(css::Parse, css::ToCss)] pub enum AlignContent { /// Default alignment. @@ -32,10 +30,9 @@ pub enum AlignContent { ContentPosition(AlignContentContentPosition), } -// Zig: anonymous payload struct carrying `pub fn __generateToCss() void {}` — -// the marker telling `DeriveToCss` to auto-generate the field-sequence printer. -// In Rust the equivalent is `#[derive(css::ToCss)]` on the lifted named-field -// struct (see `css_derive::expand_derive_to_css` struct branch); the enum arm's +// `#[derive(css::ToCss)]` + `#[css(generate_to_css)]` on the lifted +// named-field struct auto-generates the field-sequence printer (see +// `css_derive::expand_derive_to_css` struct branch); the enum arm's // `__inner.to_css(dest)` then resolves to this generated inherent. #[derive(Clone, PartialEq, Eq, css::ToCss)] #[css(generate_to_css)] @@ -211,7 +208,6 @@ impl JustifyContent { /// A value for the [align-self](https://www.w3.org/TR/css-align-3/#align-self-property) property. #[derive(Clone, PartialEq, Eq)] -// Zig: `css.DeriveParse` / `css.DeriveToCss` #[derive(css::Parse, css::ToCss)] pub enum AlignSelf { /// Automatic alignment. @@ -226,7 +222,7 @@ pub enum AlignSelf { SelfPosition(AlignSelfSelfPosition), } -// Zig: `__generateToCss` marker — see `AlignContentContentPosition` note. +// See `AlignContentContentPosition` note. #[derive(Clone, PartialEq, Eq, css::ToCss)] #[css(generate_to_css)] pub struct AlignSelfSelfPosition { @@ -381,7 +377,6 @@ impl JustifySelf { /// A value for the [align-items](https://www.w3.org/TR/css-align-3/#align-items-property) property. #[derive(Clone, PartialEq, Eq)] -// Zig: `css.DeriveParse` / `css.DeriveToCss` #[derive(css::Parse, css::ToCss)] pub enum AlignItems { /// Default alignment. @@ -394,7 +389,7 @@ pub enum AlignItems { SelfPosition(AlignItemsSelfPosition), } -// Zig: `__generateToCss` marker — see `AlignContentContentPosition` note. +// See `AlignContentContentPosition` note. #[derive(Clone, PartialEq, Eq, css::ToCss)] #[css(generate_to_css)] pub struct AlignItemsSelfPosition { @@ -605,7 +600,6 @@ impl LegacyJustify { /// A [gap](https://www.w3.org/TR/css-align-3/#column-row-gap) value, as used in the /// `column-gap` and `row-gap` properties. #[derive(Clone, PartialEq)] -// Zig: `css.DeriveParse` / `css.DeriveToCss` #[derive(css::Parse, css::ToCss)] pub enum GapValue { /// Equal to `1em` for multi-column containers, and zero otherwise. @@ -964,11 +958,10 @@ pub struct AlignHandler { pub has_any: bool, } -// ─── helper macros (Zig used `comptime prop: []const u8` + `@field` / `@unionInit`) ─── +// ─── helper macros ─── // -// The Zig source threads field names as comptime strings into helper fns and uses -// @field/@unionInit for reflection. Rust cannot pass field names as values, so these -// are macro_rules! that expand at each call site. +// Field names cannot be passed as values, so these are macro_rules! that +// expand at each call site. macro_rules! handle_property_maybe_flush { ($this:expr, $dest:expr, $context:expr, $field:ident, $val:expr, $vp:expr) => {{ @@ -1044,9 +1037,6 @@ macro_rules! flush_legacy_property { prefixes_2009.insert(VendorPrefix::MOZ); } if !prefixes_2009.is_empty() { - // Zig branched on `T == BoxOrdinalGroup` to bypass from_standard. - // Never true at any callsite in this file; noted in case the - // macro is reused elsewhere. let s = <$ty2009>::from_standard(val); if let Some(a) = s { $dest.push(Property::$variant2009((a, prefixes_2009))); @@ -1144,7 +1134,7 @@ macro_rules! flush_shorthand_helper { ) => {{ if let Some((align, align_prefix)) = &mut *$align_val { if let Some(justify) = &mut *$justify_val { - // Zig: intersection = align_prefix & align_prefix (justify has no prefix) + // justify has no prefix, so the intersection is just align_prefix let intersection = *align_prefix; if intersection.contains(VendorPrefix::NONE) { *align_prefix = $this.flush_prefixes_helper($context, $align_feature); @@ -1313,8 +1303,8 @@ impl AlignHandler { Property::Unparsed(val) => { if is_align_property(&val.property_id) { self.flush(dest, context); - // Zig pushed `property.deepClone(context.arena)`. `Property` has no - // blanket `Clone` yet; reconstruct from the matched payload (same as flex.rs). + // `Property` has no blanket `Clone` yet; reconstruct from + // the matched payload (same as flex.rs). let bump = dest.bump(); dest.push(Property::Unparsed(val.deep_clone(bump))); } else { @@ -1452,7 +1442,6 @@ impl AlignHandler { } /// Gets prefixes for standard properties. - // PERF(port): was comptime monomorphization (`comptime feature: Feature`) — profile if hot. fn flush_prefixes_helper( &self, context: &PropertyHandlerContext<'_>, @@ -1490,5 +1479,3 @@ fn is_align_property(property_id: &PropertyId) -> bool { | PropertyId::Gap ) } - -// ported from: src/css/properties/align.zig diff --git a/src/css/properties/animation.rs b/src/css/properties/animation.rs index 61a6e13fd749..e3c5f3d627b4 100644 --- a/src/css/properties/animation.rs +++ b/src/css/properties/animation.rs @@ -39,9 +39,8 @@ pub struct Animation { impl Animation { // PropertyFieldMap omitted: `PropertyIdTag::Animation*` variants are not yet - // generated (animation longhands are unparsed-only for now), and the Zig table - // was unread comptime metadata. Re-add the field→PropertyIdTag table once the - // variants land. + // generated (animation longhands are unparsed-only for now). Re-add the + // field→PropertyIdTag table once the variants land. pub const VENDOR_PREFIX_MAP: &'static [(&'static str, bool)] = &[ ("name", true), @@ -137,9 +136,8 @@ impl Animation { } pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // reshaped `inline .ident, .string => |name|` — Zig's inline - // switch monomorphized over two payload types; Rust extracts the inner - // string slice up front instead. + // Extract the inner string slice up front so both the ident and + // string variants share one code path. let name_str: Option<&[u8]> = match &self.name { AnimationName::None => None, AnimationName::Ident(ident) => Some(ident.v()), @@ -238,7 +236,7 @@ pub enum AnimationName { impl AnimationName { // hand-written (not `#[derive]`) because `CSSString` is a raw // `*const [u8]` arena pointer — generics blanket impls cover `&[u8]` but - // not raw slices. Mirrors Zig `css.implementEql/Hash/DeepClone`. + // not raw slices. pub fn eql(&self, other: &Self) -> bool { match (self, other) { (AnimationName::None, AnimationName::None) => true, @@ -280,8 +278,7 @@ impl AnimationName { } pub fn parse(input: &mut Parser) -> css::Result { - // ported from src/css/properties/animation.zig — `none` keyword, - // then ``, else ``. + // `none` keyword, then ``, else ``. if input .try_parse(|i| i.expect_ident_matching(b"none")) .is_ok() @@ -474,15 +471,10 @@ pub enum AnimationTimeline { } impl AnimationTimeline { - // Port of `css.DeriveParse(@This()).parse` — void variants (`auto`, `none`) - // declared first → tried first via ident match; payloads follow in - // declaration order (`DashedIdent`, `ScrollTimeline`, `ViewTimeline`). - // Upstream `ScrollTimeline` / `ViewTimeline` carry no `parse`, so the Zig - // `DeriveParse` instantiation is dead code (`generic.parseFor` would - // `@compileError` if compiled — `Animation` is unreferenced in - // properties_generated.zig). We stop at `DashedIdent` here; if scroll()/ - // view() ever become live they need real function-syntax parsing, not the - // derived field-sequence fallback. + // Void variants (`auto`, `none`) are tried first via ident match; + // payloads follow in declaration order. We stop at `DashedIdent`; if + // scroll()/view() ever become live they need real function-syntax + // parsing, not a derived field-sequence fallback. pub fn parse(input: &mut Parser) -> css::Result { let state = input.state(); if let Ok(ident) = input.expect_ident() { @@ -499,16 +491,12 @@ impl AnimationTimeline { DashedIdent::parse(input).map(AnimationTimeline::DashedIdent) } - // Port of `css.DeriveToCss(@This()).toCss`. pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { match self { AnimationTimeline::Auto => dest.write_str(b"auto"), AnimationTimeline::None => dest.write_str(b"none"), AnimationTimeline::DashedIdent(d) => d.to_css(dest), - // Upstream Zig `ScrollTimeline` / `ViewTimeline` have no `toCss`; - // `DeriveToCss` would delegate to `generic.toCss` → `T.toCss` and - // `@compileError` if this arm were ever instantiated. Mirror that: - // these variants are currently unconstructible via `parse()`, and + // These variants are currently unconstructible via `parse()`, and // emitting bare space-separated fields here would be wrong CSS // (spec syntax is `scroll(...)` / `view(...)`). AnimationTimeline::Scroll(_) | AnimationTimeline::View(_) => { @@ -654,5 +642,3 @@ pub enum TimelineRangeName { /// Represents the range during which the principal box crosses the start border edge. ExitCrossing, } - -// ported from: src/css/properties/animation.zig diff --git a/src/css/properties/background.rs b/src/css/properties/background.rs index a0a96c83ab74..c96ff47abddc 100644 --- a/src/css/properties/background.rs +++ b/src/css/properties/background.rs @@ -36,8 +36,6 @@ pub struct Background { } impl Background { - // Zig `deinit` was a no-op (all allocations in CSS parser are in arena) — Drop handles it. - pub(crate) fn parse(input: &mut Parser) -> css::Result { let mut color: Option = None; let mut position: Option = None; @@ -597,18 +595,16 @@ pub struct BackgroundHandler { pub attachments: Option>, pub origins: Option>, pub clips: Option<(SmallList, VendorPrefix)>, - // TODO(perf): arena Vec — Zig is `ArrayListUnmanaged(Property)` fed `context.arena` - // (CSS arena). Should be `bun_alloc::ArenaVec<'bump, Property>`; threading `'bump` - // through BackgroundHandler would avoid the heap alloc. + // TODO(perf): should be `bun_alloc::ArenaVec<'bump, Property>`; threading + // `'bump` through BackgroundHandler would avoid the heap alloc. pub decls: Vec, pub flushed_properties: BackgroundProperty, pub has_any: bool, } -// The Zig uses comptime field-name strings + @field for `flushHelper` / -// `initSmallListHelper` / `push`. Rust cannot index struct fields by string at runtime; -// these helpers are expanded into small per-field macros below. A derive macro -// could replace them. +// Struct fields cannot be indexed by string at runtime; the `flushHelper` / +// `initSmallListHelper` / `push` helpers are expanded into small per-field +// macros below. A derive macro could replace them. macro_rules! init_small_list_helper { ($this:expr, $field:ident, $length:expr) => {{ @@ -698,8 +694,8 @@ impl BackgroundHandler { Property::BackgroundClip(x) => { let val: &SmallList = &x.0; let vendor_prefix: VendorPrefix = x.1; - // Reshaped for borrowck: Zig held &mut into self.clips - // across self.flush(). Compute the predicate first, then dispatch. + // Compute the predicate first, then dispatch (avoids holding + // &mut self.clips across self.flush()). let needs_flush = if let Some((clips, vp)) = &self.clips { vendor_prefix != *vp && !SmallList::eql(val, clips) } else { @@ -851,7 +847,6 @@ impl BackgroundHandler { let mut maybe_origins: Option> = self.origins.take(); let mut maybe_clips: Option<(SmallList, VendorPrefix)> = self.clips.take(); - // Zig had `defer { ... deinit }` here — Drop handles cleanup at scope exit. if let ( Some(color), @@ -904,8 +899,7 @@ impl BackgroundHandler { }; let mut backgrounds: SmallList = SmallList::init_capacity(len); - // Reshaped for borrowck: Zig zipped 8 slices by value; here we - // index by `i` and clone each element to avoid 8 simultaneous borrows. + // Index by `i` and clone each element to avoid 8 simultaneous borrows. for i in 0..(len as usize) { backgrounds.append_assume_capacity(Background { color: if i == (len as usize) - 1 { @@ -929,21 +923,17 @@ impl BackgroundHandler { }, }); } - // Zig: defer { clearRetainingCapacity on each list } — values were moved - // by-value into `backgrounds` above, so clearing prevents double-free. - // In Rust we cloned, so the originals will Drop normally; no explicit clear - // needed for correctness. Leaving as-is. - // PERF(port): was arena bulk-free / move-then-clear — profile if hot. + // The elements were cloned into `backgrounds` above, so the + // originals Drop normally; no explicit clear needed. + // PERF: profile the clones if hot. if self.flushed_properties.is_empty() { let mut fallbacks = crate::small_list::get_fallbacks(&mut backgrounds, arena, &context.targets); // Pop in reverse then re-reverse via a temp Vec to - // preserve order while consuming the list. Shape kept from - // the Zig port, where the fallback list was an arena - // SmallList with no owning iterator; `fallbacks` is a plain - // Vec here, so a direct `for fb in fallbacks` would also - // work. + // preserve order while consuming the list. `fallbacks` is + // a plain Vec, so a direct `for fb in fallbacks` would + // also work. let mut tmp: Vec> = Vec::with_capacity(fallbacks.len()); while let Some(fb) = fallbacks.pop() { @@ -1005,10 +995,8 @@ impl BackgroundHandler { let mut fallbacks = crate::small_list::get_fallbacks(&mut images, arena, &context.targets); // Pop in reverse then re-reverse via a temp Vec to preserve - // order while consuming the list. Shape kept from the Zig - // port, where the fallback list was an arena SmallList with no - // owning iterator; `fallbacks` is a plain Vec here, so a - // direct `for fb in fallbacks` would also work. + // order while consuming the list. `fallbacks` is a plain Vec, + // so a direct `for fb in fallbacks` would also work. let mut tmp: Vec> = Vec::with_capacity(fallbacks.len()); while let Some(fb) = fallbacks.pop() { tmp.push(fb); @@ -1048,7 +1036,6 @@ impl BackgroundHandler { y: y.clone(), }); } - // Zig: clearRetainingCapacity on xs/ys after moving values out — Drop handles it. push_property!( self, dest, @@ -1136,7 +1123,7 @@ impl BackgroundHandler { } fn reset(&mut self) { - // Zig deinit'd each field then set to null — Drop on assignment handles both. + // Drop on assignment frees each field's old value. self.color = None; self.images = None; self.x_positions = None; @@ -1162,8 +1149,7 @@ impl BackgroundHandler { let arena = dest.bump(); for decl in self.decls.drain(..) { - // Zig was `appendSlice` (bitwise copy of arena-backed - // values). `Property` is not `Clone` here, so move out via drain. + // `Property` is not `Clone` here, so move out via drain. let _ = arena; dest.push(decl); } @@ -1173,8 +1159,8 @@ impl BackgroundHandler { } } -// `Background` participates in `SmallList::get_fallbacks` via the duck-typed -// `ImageFallback` protocol (Zig dispatched on `@hasDecl(T, "getImage")`). +// `Background` participates in `SmallList::get_fallbacks` via the +// `ImageFallback` trait. impl crate::small_list::ImageFallback for Background { #[inline] fn get_image(&self) -> &Image { @@ -1210,5 +1196,3 @@ fn is_background_property(property_id: PropertyId) -> bool { | PropertyId::Background ) } - -// ported from: src/css/properties/background.zig diff --git a/src/css/properties/border.rs b/src/css/properties/border.rs index 4d296f3a6434..91efc57db399 100644 --- a/src/css/properties/border.rs +++ b/src/css/properties/border.rs @@ -148,7 +148,6 @@ where { fn get_fallbacks(&mut self, arena: &Bump, targets: &Targets) -> SmallList { let fallbacks = self.color.get_fallbacks(arena, targets); - // PERF(port): was arena bulk-free (fallbacks.deinit) — profile if it shows up on a hot path let mut out: SmallList = SmallList::init_capacity(fallbacks.len()); for color in fallbacks.slice() { out.append_assume_capacity(Self { @@ -166,11 +165,9 @@ where } /// Deep-clone into a `GenericBorder` with a different const-generic - /// discriminant `Q`. The fields are identical regardless of `P`; this is - /// the Rust equivalent of Zig coercing one anonymous struct literal into - /// multiple `Border*` aliases. Needed when one logical value must be - /// emitted as two distinct physical `Property` variants (e.g. - /// inline-start → BorderLeft + BorderRight). + /// discriminant `Q`. The fields are identical regardless of `P`. Needed + /// when one logical value must be emitted as two distinct physical + /// `Property` variants (e.g. inline-start → BorderLeft + BorderRight). pub(crate) fn clone_as(&self, arena: &Bump) -> GenericBorder { let cloned = self.deep_clone(arena); GenericBorder { @@ -252,10 +249,9 @@ impl BorderSideWidth { crate::css_eql_partialeq!(BorderSideWidth); // ────────────────────────────────────────────────────────────────────────── -// ImplFallbacks (Zig: `pub fn ImplFallbacks(comptime T: type) type`) +// impl_fallbacks // ────────────────────────────────────────────────────────────────────────── -// Zig used `inline for (std.meta.fields(T))` reflection; we expand the field -// list at macro invocation instead. All fields are `CssColor`. +// The field list is expanded at macro invocation. All fields are `CssColor`. // Hoisted here because `macro_rules!` is order-sensitive. macro_rules! impl_fallbacks { ($T:ty; $($field:ident),+) => { @@ -357,15 +353,13 @@ macro_rules! define_size_shorthand { } impl $name { - // Zig: `css.DefineShorthand(@This(), PropertyIdTag.$shorthand_id)` - // consumed this table via comptime reflection; in Rust the table is - // kept as data for a future shorthand derive to consume. + // The table is kept as data for a future shorthand derive to consume. pub const PROPERTY_FIELD_MAP: &[(&str, PropertyIdTag)] = &[ ("start", PropertyIdTag::$start_id), ("end", PropertyIdTag::$end_id), ]; } - // Zig `css.DefineSizeShorthand(@This(), V)` — parse/to_css via `Size2D`. + // parse/to_css via `Size2D`. // Shared impl macro lives in `properties/mod.rs`. impl_size_shorthand!($name, $inner, start, end); }; @@ -448,7 +442,6 @@ impl BorderShorthand { } fn reset(&mut self, _arena: &Bump) { - // PERF(port): was arena bulk-free via bun.clear — profile if it shows up on a hot path self.width = None; self.style = None; self.color = None; @@ -459,8 +452,7 @@ impl BorderShorthand { } /// Generic over the `P` const param so the same `BorderShorthand` data - /// can populate any of `BorderTop`/`BorderLeft`/.../`Border` (Zig used a - /// single anonymous struct literal that coerced to each alias). + /// can populate any of `BorderTop`/`BorderLeft`/.../`Border`. fn to_border(&self, arena: &Bump) -> GenericBorder { GenericBorder { width: css::generic::deep_clone(&self.width, arena).unwrap(), @@ -471,7 +463,7 @@ impl BorderShorthand { } // ────────────────────────────────────────────────────────────────────────── -// BorderProperty bitflags (Zig: `packed struct(u32)` of all-bool fields) +// BorderProperty bitflags // ────────────────────────────────────────────────────────────────────────── bitflags::bitflags! { @@ -554,8 +546,7 @@ bitflags::bitflags! { // blocked_on: PropertyIdTag variant name verification (PascalCase mapping) impl BorderProperty { pub(crate) fn try_from_property_id(property_id: PropertyIdTag) -> Option { - // Zig used `inline for` over PropertyIdTag fields + @hasDecl. Expanded - // here to an explicit match over every PropertyIdTag whose name starts + // An explicit match over every PropertyIdTag whose name starts // with "border" and has a matching const above — keep in sync when new // Border* PropertyIdTag variants are added. use PropertyIdTag as P; @@ -635,8 +626,7 @@ mod border_handler_body { use super::*; use crate::generics::{CssEql, DeepClone}; // ────────────────────────────────────────────────────────────────────────── - // FlushContext + flush_category! (Zig: nested struct with inline fns and - // extensive comptime string-dispatch) + // FlushContext + flush_category! // ────────────────────────────────────────────────────────────────────────── // hoisted above `impl BorderHandler` — macro_rules! is order- // sensitive and the flush_category!() callsites in `flush()` need these. @@ -658,8 +648,8 @@ mod border_handler_body { } struct FlushContext<'a, 'bump, 'ctx> { - // Zig stored `self: *BorderHandler`; we only need flushed_properties - // here because the per-side BorderShorthand pointers are passed separately. + // Only flushed_properties is needed here because the per-side + // BorderShorthand pointers are passed separately. flushed_properties: &'a mut BorderProperty, dest: &'a mut DeclarationList<'bump>, ctx: &'a mut PropertyHandlerContext<'ctx>, @@ -715,8 +705,7 @@ mod border_handler_body { } // `f.push(p, val)` - // Zig's `@field(BorderProperty, p)` keyed both Property and BorderProperty - // off one kebab string. Here `$p` is the PascalCase Property/PropertyIdTag variant; + // `$p` is the PascalCase Property/PropertyIdTag variant; // the bitflags const is derived via try_from_property_id so a single ident suffices. macro_rules! fc_push { ($f:expr, $p:ident, $val:expr) => {{ @@ -747,8 +736,7 @@ mod border_handler_body { }}; } - // `f.prop(prop_name, val)` — comptime string dispatch over prop_name. - // In Rust we dispatch on the Property variant ident. + // Dispatch on the Property variant ident. macro_rules! fc_prop { // border-inline-start* ($f:expr, BorderInlineStart, $val:expr) => {{ @@ -915,9 +903,8 @@ mod border_handler_body { }; } - // `flushCategory(...)` — was a fn with comptime string params + nested `State` - // struct of inline fns. In Rust we expand it as a macro so the `comptime` prop - // names remain compile-time idents and the nested closures become local macros. + // `flush_category!` is a macro so the prop names remain + // compile-time idents and the nested closures become local macros. macro_rules! flush_category { ( $f:expr, @@ -1239,7 +1226,7 @@ mod border_handler_body { // arena is recovered via `dest.bump()` (DeclarationList = bumpalo::Vec). let arena = dest.bump(); - // Helper macros — Zig used local comptime closures with @field string access. + // Helper macros. macro_rules! flush_helper { ($key:ident, $prop:ident, $val:expr, $category:expr) => {{ @@ -1522,9 +1509,8 @@ mod border_handler_body { let logical_shorthand_supported = !context.should_compile_logical(Feature::LogicalBorderShorthand); - // reshaped for borrowck — Zig stored `self: *BorderHandler` in - // FlushContext and accessed self.border_* through it. We instead take - // independent &mut borrows of each shorthand, plus &mut self.flushed_properties. + // Take independent &mut borrows of each shorthand, plus + // &mut self.flushed_properties, instead of one &mut self. let arena = dest.bump(); let mut flctx = FlushContext { flushed_properties: &mut self.flushed_properties, @@ -1626,12 +1612,12 @@ mod border_handler_body { macro_rules! prop { ($id:ident) => {{ - let _ = &dest; // autofix (matches Zig: `_ = d;`) + let _ = &dest; // autofix let mut upppppppppp = unparsed.with_property_id(arena, PropertyId::$id); context.add_unparsed_fallbacks(arena, &mut upppppppppp); self.flushed_properties .insert(BorderProperty::try_from_property_id(PropertyIdTag::$id).unwrap()); - // Zig did NOT push to dest here (likely a bug upstream) — preserved. + // Intentionally not pushed to dest (preserves existing behavior; likely a bug). }}; } @@ -1739,6 +1725,4 @@ mod border_handler_body { | P::Border ) } - - // ported from: src/css/properties/border.zig } // mod border_handler_body diff --git a/src/css/properties/border_image.rs b/src/css/properties/border_image.rs index 310c7445e100..a1bfe1d1a415 100644 --- a/src/css/properties/border_image.rs +++ b/src/css/properties/border_image.rs @@ -34,9 +34,7 @@ pub struct BorderImage { } impl BorderImage { - // Zig PropertyFieldMap / VendorPrefixMap were comptime anonymous-struct - // tables consumed via @field reflection by the shorthand codegen; recorded - // here for a future shorthand trait/derive to consume. + // Recorded here for a future shorthand trait/derive to consume. // PropertyFieldMap: // source -> PropertyIdTag::BorderImageSource // slice -> PropertyIdTag::BorderImageSlice @@ -46,12 +44,9 @@ impl BorderImage { // VendorPrefixMap: all fields = true pub(crate) fn parse(input: &mut css::Parser) -> Result { - // Zig passed `{}` ctx + a no-op callback struct; collapsed to a closure. Self::parse_with_callback(input, |_: &mut css::Parser| false) } - // Zig signature was (input, ctx: anytype, comptime callback: anytype) - // where callback(ctx, input) -> bool. Collapsed ctx into the closure capture. pub(crate) fn parse_with_callback( input: &mut css::Parser, mut callback: impl FnMut(&mut css::Parser) -> bool, @@ -203,7 +198,7 @@ impl BorderImage { // `defer fallbacks.deinit(arena)` dropped — SmallList drops at scope exit. let mut res = SmallList::::init_capacity(fallbacks.len()); for fallback in fallbacks { - // Matches Zig: `fallback` is moved into the cloned shorthand. + // `fallback` is moved into the cloned shorthand. let mut clone = self.deep_clone(arena); clone.source = fallback; res.append(clone); @@ -212,8 +207,7 @@ impl BorderImage { } pub(crate) fn deep_clone(&self, arena: &Arena) -> Self { - // Zig css.implementDeepClone iterated @typeInfo fields. Expanded - // explicitly here — keep in sync with the BorderImage field list. + // Keep in sync with the BorderImage field list. BorderImage { source: self.source.deep_clone(arena), slice: self.slice.deep_clone(arena), @@ -304,9 +298,8 @@ pub enum BorderImageSideWidth { } impl BorderImageSideWidth { - // `css.DeriveParse(@This()).parse` / `css.DeriveToCss(@This()).toCss` - // were comptime-reflected derives. Hand-expanded — declaration order matches - // Zig (Number → LengthPercentage → keyword `auto`). + // Hand-expanded — tried in declaration order + // (Number → LengthPercentage → keyword `auto`). pub(crate) fn parse(input: &mut css::Parser) -> Result { if let Ok(n) = input.try_parse(CSSNumberFns::parse) { return Ok(BorderImageSideWidth::Number(n)); @@ -454,11 +447,7 @@ impl BorderImageProperty { pub(crate) const BORDER_IMAGE: BorderImageProperty = BorderImageProperty::all(); - // bitflags provides `is_empty()` already; Zig `isEmpty` maps to it. - pub(crate) fn try_from_property_id(property_id: PropertyIdTag) -> Option { - // Zig used `inline for` over struct fields + @field to match - // "border-image-" ++ field.name. Unrolled explicitly here. match property_id { PropertyIdTag::BorderImageSource => Some(BorderImageProperty::SOURCE), PropertyIdTag::BorderImageSlice => Some(BorderImageProperty::SLICE), @@ -492,8 +481,7 @@ impl BorderImageHandler { ) -> bool { let arena = dest.bump(); - // Zig defined `flushHelper`/`propertyHelper` as local struct fns - // using @field for comptime field access. Ported as macro_rules! to keep the + // `flushHelper`/`propertyHelper` are macro_rules! to keep the // per-field name dispatch without reflection. macro_rules! flush_helper { ($self:expr, $d:expr, $ctx:expr, $name:ident, $val:expr) => { @@ -568,7 +556,6 @@ impl BorderImageHandler { .unwrap(), ); dest.push(Property::Unparsed(unparsed_clone)); - // PERF(port): was bun.handleOom(dest.append(arena, ...)) } else { return false; } @@ -725,5 +712,3 @@ pub(crate) fn is_border_image_property(property_id: PropertyIdTag) -> bool { } crate::css_eql_partialeq!(BorderImageSideWidth); - -// ported from: src/css/properties/border_image.zig diff --git a/src/css/properties/border_radius.rs b/src/css/properties/border_radius.rs index 35f9e07b3b18..ad6187f4e0c4 100644 --- a/src/css/properties/border_radius.rs +++ b/src/css/properties/border_radius.rs @@ -13,7 +13,7 @@ use bun_alloc::ArenaVecExt as _; /// A value for the [border-radius](https://www.w3.org/TR/css-backgrounds-3/#border-radius) property. // `Size2D` carries no `Clone`/`PartialEq` derives (it exposes -// inherent `deep_clone`/`eql` instead, matching the Zig protocol surface), so +// inherent `deep_clone`/`eql` instead), so // `BorderRadius` can't `#[derive]` them either. The handler below uses // `Size2D::deep_clone`/`Size2D::eql` directly. pub struct BorderRadius { @@ -44,9 +44,7 @@ impl BorderRadius { && Size2D::eql(&self.bottom_left, &other.bottom_left) } - // Zig: `css.DefineShorthand(@This(), css.PropertyIdTag.@"border-radius", PropertyFieldMap)` - // consumed these anonymous-struct decl literals via comptime reflection. Kept - // as assoc consts for a future shorthand trait/derive to consume. + // Kept as assoc consts for a future shorthand trait/derive to consume. pub const PROPERTY_FIELD_MAP: [(&'static str, &'static str); 4] = [ ("top_left", "border-top-left-radius"), ("top_right", "border-top-right-radius"), @@ -67,7 +65,7 @@ impl BorderRadius { // errdefer-style cleanup of `widths` is implicit via Drop on the `?` path. Rect::::parse_with(input, LengthPercentage::parse)? } else { - // Zig `widths.deepClone(arena)` — `LengthPercentage` is + // `LengthPercentage` is // `Clone`-via-derive (no arena indirection), so per-field `.clone()` is exact. Rect { top: widths.top.clone(), @@ -98,8 +96,7 @@ impl BorderRadius { } pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // Zig built `Rect(*const LengthPercentage)` and reused - // `Rect.toCss`. `Rect::<&T>::to_css` would need `&T: ToCss + PartialEq`; + // `Rect::<&T>::to_css` would need `&T: ToCss + PartialEq`; // inline the 4-side serialization to avoid that bound (logic is identical // to `values::rect::Rect::to_css`). #[inline] @@ -152,8 +149,8 @@ impl BorderRadius { Ok(()) } - // deepClone → #[derive(Clone)] (was css.implementDeepClone comptime field iteration) - // eql → #[derive(PartialEq)] (was css.implementEql comptime field iteration) + // deep_clone comes from #[derive(Clone)]. + // eql comes from #[derive(PartialEq)]. } #[derive(Default)] @@ -166,14 +163,13 @@ pub struct BorderRadiusHandler { pub start_end: Option, pub end_end: Option, pub end_start: Option, - // Zig: `= .physical`. derive(Default) is sound here because + // derive(Default) is sound here because // PropertyCategory::default() == Physical (see src/css/logical.rs). pub category: PropertyCategory, pub has_any: bool, } -// The Zig helpers take `comptime prop: []const u8` and use `@field` / `@unionInit` for -// token-level field/variant access. Rust has no field-by-name reflection, so these are +// There is no field-by-name reflection, so these helpers are // macro_rules! that paste the field ident and the corresponding Property variant ident. // `Size2D::is_compatible` is bounded on `T: values::protocol::IsCompatible`, @@ -233,7 +229,7 @@ macro_rules! logical_property_helper { $self.flush($d, $ctx); } - // Zig stored `property.deepClone(arena)`. `Property` itself + // `Property` itself // has no blanket `Clone`; callers pass an already-deep_clone'd `Property`. $self.$prop = Some($val); $self.category = PropertyCategory::Logical; @@ -561,5 +557,3 @@ pub fn is_logical_border_radius_property(property_id: PropertyIdTag) -> bool { | PropertyIdTag::BorderEndStartRadius ) } - -// ported from: src/css/properties/border_radius.zig diff --git a/src/css/properties/box_shadow.rs b/src/css/properties/box_shadow.rs index e9203dadf924..9a4a221ad333 100644 --- a/src/css/properties/box_shadow.rs +++ b/src/css/properties/box_shadow.rs @@ -201,9 +201,9 @@ impl BoxShadowHandler { self.flush(dest, context); } - // Reshaped for borrowck: Zig held simultaneous &mut into - // self.box_shadows across self.flush(). Compute the predicate first, - // then either flush+replace or update in place. + // Compute the predicate first, then either flush+replace or + // update in place (avoids holding &mut self.box_shadows + // across self.flush()). let needs_flush = if let Some(bxs) = &self.box_shadows { !SmallList::eql(&bxs.0, box_shadows) && !bxs.1.contains(prefix) } else { @@ -270,10 +270,6 @@ impl BoxShadowHandler { fallbacks.insert(shadow.color.get_necessary_fallbacks(&context.targets)); } - // Zig used `initCapacity(len)` + `setLen(len)` + per-index field - // writes via `inline for std.meta.fields(BoxShadow)` skipping `color`. That - // pattern would observe partially-uninit `BoxShadow` values in Rust, so we - // build each fully-formed `BoxShadow` and `append`. Behavior is identical. macro_rules! build_color_fallback { ($conv:ident) => {{ let mut out: SmallList = @@ -324,5 +320,3 @@ impl BoxShadowHandler { self.flushed = true; } } - -// ported from: src/css/properties/box_shadow.zig diff --git a/src/css/properties/css_modules.rs b/src/css/properties/css_modules.rs index a157c84c2258..00a451be8925 100644 --- a/src/css/properties/css_modules.rs +++ b/src/css/properties/css_modules.rs @@ -190,5 +190,3 @@ impl Specifier { } } } - -// ported from: src/css/properties/css_modules.zig diff --git a/src/css/properties/custom.rs b/src/css/properties/custom.rs index 9f2e5e88652c..0c25d607e863 100644 --- a/src/css/properties/custom.rs +++ b/src/css/properties/custom.rs @@ -1,6 +1,4 @@ //! CSS custom properties / `var()` / `env()` / unparsed token lists. -//! -//! Ported from `src/css/properties/custom.zig`. // // `TokenList::{parse, parse_into, parse_with_options, to_css, to_css_raw}`, // `UnresolvedColor::{parse, to_css}`, `Variable::{parse, to_css}`, @@ -119,7 +117,6 @@ mod ext { }; if dest.minify && !is_internal { - // PERF(port): was std.Io.Writer.Allocating with dest.arena — using Vec; profile if hot. let mut buf: Vec = Vec::new(); // PERF(alloc) we could use stack fallback here? let _ = Token::UnquotedUrl(url).to_css_generic(&mut buf); @@ -159,7 +156,7 @@ mod ext { /// Forwarder to `DashedIdentReference::to_css` (now un-gated in /// `values/ident.rs`). `CssModule::reference_dashed` is real; the - /// CSS-Modules `dashed_idents` remapping path (ident.zig:44-52) is wired. + /// CSS-Modules `dashed_idents` remapping path is wired. #[inline] pub(super) fn dashed_ident_ref_to_css( this: &DashedIdentReference, @@ -273,7 +270,6 @@ impl CssEql for Token { impl CssHash for Token { fn hash(&self, hasher: &mut Wyhash) { use Token::*; - // Zig `implementHash`: tag prefix + payload bytes. // `Token::kind() as u32` gives a stable per-variant discriminant. hasher.update(&(self.kind() as u32).to_ne_bytes()); match self { @@ -302,16 +298,14 @@ impl<'bump> DeepClone<'bump> for Token { #[inline] fn deep_clone(&self, _bump: &'bump Arena) -> Self { // All `&'static [u8]` payloads borrow the parser source/arena (`'static` - // is a placeholder) — identity copy is correct (matches generics.zig - // "const strings" fast-path). `Num`/`Dimension` are POD. + // is a placeholder) — identity copy is correct. `Num`/`Dimension` are POD. self.clone() } } -// PERF(port): the token vecs here are plain global-alloc `Vec`; -// the Zig original was arena-backed. Thread `&'bump Bump` if profiling shows it. +// PERF: the token vecs here are plain global-alloc `Vec`; +// Thread `&'bump Bump` if profiling shows it. -/// Zig: `pub fn Result(comptime T: type) type` → `Maybe(T, ParseError(ParserError))`. pub use css_parser::CssResult as Result; /// PERF: nullable optimization @@ -481,8 +475,7 @@ impl TokenList { if tokens[tokens.len() - 1].is_whitespace() { end -= 1; } - // Zig does `insertSlice(0, slice)` (shallow memcpy) then `tokens.deinit()` - // (frees only the backing array). `drain` moves the elements out without deep-cloning. + // `drain` moves the elements out without deep-cloning. let newlist: Vec = tokens.drain(start..end).collect(); return Ok(TokenList { v: newlist }); } @@ -751,7 +744,6 @@ impl TokenList { let mut res = css::SmallList::::default(); if fallbacks.contains(ColorFallbackKind::P3) { - // PERF(port): was assume_capacity res.append(( ColorFallbackKind::P3.supports_condition(), self.get_fallback(bump, ColorFallbackKind::P3), @@ -759,7 +751,6 @@ impl TokenList { } if fallbacks.contains(ColorFallbackKind::LAB) { - // PERF(port): was assume_capacity res.append(( ColorFallbackKind::LAB.supports_condition(), self.get_fallback(bump, ColorFallbackKind::LAB), @@ -984,7 +975,6 @@ impl UnresolvedColor { }) }), b"light-dark" => return input.parse_nested_block(|input2| { - // errdefer doesn't fire on `return .{ .err = ... }` in Zig — but in Rust, // `?` drops `light` automatically on the error path. let light = input2.parse_until_before(Delimiters::COMMA, |i| { TokenListFns::parse(i, options, depth + 1) @@ -1015,9 +1005,8 @@ impl UnresolvedColor { // `ComponentParser::parse_relative` is generic over `C: LightDarkOwned` so the // `from light-dark(...)` relative-color path can rebuild a `light-dark()` of -// whatever output type the caller is producing. Zig duck-types this via -// `lightDarkOwned` on both `CssColor` and `UnresolvedColor`; in Rust the trait -// lives in `values::color` and we wire `UnresolvedColor` into it here. +// whatever output type the caller is producing. The trait lives in +// `values::color` and we wire `UnresolvedColor` into it here. impl css_values::color::LightDarkOwned for UnresolvedColor { #[inline] fn light_dark_owned(light: Self, dark: Self) -> Self { @@ -1190,9 +1179,7 @@ impl EnvironmentVariableName { } /// A UA-defined environment variable name. -// Zig `css.DefineEnumProperty(@This())` provides eql/hash/parse/ -// to_css/deep_clone via comptime reflection over @tagName. Replaced by an -// `EnumProperty` impl below (kebab-case match) — same protocol surface. +// The `EnumProperty` impl below provides parse/to_css via a kebab-case match. #[derive(Clone, Copy, PartialEq, Eq, strum::IntoStaticStr, CssHash)] pub enum UAEnvironmentVariable { /// The safe area inset from the top of the viewport. @@ -1335,8 +1322,7 @@ impl TokenOrValue { // `TokenList` payload, and `media_query::MediaFeatureValue` derives // `Debug + Clone` over an `EnvironmentVariable` payload. The leaf value types // (`Url`, `CustomIdent`, …) don't all `#[derive(Clone)]` yet, so hand-roll -// the structural clone here. Zig had no `Clone` distinction — -// shallow struct copy was implicit; arena-slice payloads (`*const [u8]`) are +// the structural clone here. Arena-slice payloads (`*const [u8]`) are // `Copy`, and the only owning fields are `Vec` / `Vec`. impl Clone for TokenList { @@ -1551,7 +1537,7 @@ impl CustomPropertyName { pub fn to_css(&self, dest: &mut Printer) -> PrintResult<()> { match self { CustomPropertyName::Custom(custom) => { - // Spec custom.zig:1496-1501 → DashedIdent.toCss → dest.writeDashedIdent(ident, true), + // DashedIdent.toCss → dest.writeDashedIdent(ident, true), // which applies CSS-Modules dashed-ident renaming. dest.write_dashed_ident(custom, true) } @@ -1626,5 +1612,3 @@ pub(crate) fn try_parse_color_token( None } - -// ported from: src/css/properties/custom.zig diff --git a/src/css/properties/display.rs b/src/css/properties/display.rs index a187c15e76a0..5b80df0a3473 100644 --- a/src/css/properties/display.rs +++ b/src/css/properties/display.rs @@ -11,8 +11,8 @@ pub enum Display { Pair(DisplayPair), } -// Zig `DeriveParse`/`DeriveToCss` for a 2-payload union(enum) tries each -// payload's `parse` in declaration order; `toCss` dispatches to the active payload. +// Tries each payload's `parse` in declaration order; `to_css` dispatches to +// the active payload. impl Display { pub(crate) fn parse(input: &mut Parser) -> css::Result { if let Ok(kw) = input.try_parse(DisplayKeyword::parse) { @@ -130,7 +130,6 @@ impl DisplayPair { let location = input.current_source_location(); let ident = input.expect_ident_cloned()?; - // Zig used `bun.ComptimeStringMap(..).getASCIIICaseInsensitive`. // 8 keys → if-chain over `eql_case_insensitive_ascii::` (phf values // would have to be const-eval, and `VendorPrefix` bitflags are not). use bun_core::eql_case_insensitive_ascii as eq; @@ -242,7 +241,6 @@ impl DisplayInside { let location = input.current_source_location(); let ident = input.expect_ident_cloned()?; - // Zig used `bun.ComptimeStringMap(..).getASCIIICaseInsensitive`. // 10 keys → if-chain over `eql_case_insensitive_ascii::`. use bun_core::eql_case_insensitive_ascii as eq; Ok(if eq(ident, b"flow", true) { @@ -292,5 +290,3 @@ impl DisplayInside { } } } - -// ported from: src/css/properties/display.zig diff --git a/src/css/properties/effects.rs b/src/css/properties/effects.rs index c3d71c41cec0..8b137891791f 100644 --- a/src/css/properties/effects.rs +++ b/src/css/properties/effects.rs @@ -1 +1 @@ -// ported from: src/css/properties/effects.zig + diff --git a/src/css/properties/flex.rs b/src/css/properties/flex.rs index cff9a292f5a3..b3c7c9dbf1e9 100644 --- a/src/css/properties/flex.rs +++ b/src/css/properties/flex.rs @@ -8,7 +8,6 @@ use css::css_values::length::{LengthPercentage, LengthPercentageOrAuto}; use css::css_values::number::{CSSInteger, CSSNumber, CSSNumberFns}; use css::prefixes::Feature as PrefixFeature; use css::{PrintErr, Printer, VendorPrefix}; -// Zig: `const isFlex2009 = css.prefixes.Feature.isFlex2009;` use css::prefixes::is_flex_2009; /// A value for the [flex-direction](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#propdef-flex-direction) property. @@ -59,8 +58,7 @@ pub struct FlexFlow { pub wrap: FlexWrap, } -// Zig: `css.DefineShorthand(@This(), css.PropertyIdTag.@"flex-flow", PropertyFieldMap)` -// consumed comptime shorthand metadata; recorded here for a future shorthand derive: +// Shorthand metadata recorded here for a future shorthand derive: // PropertyFieldMap = { direction: PropertyIdTag::FlexDirection, wrap: PropertyIdTag::FlexWrap } // VendorPrefixMap = { direction: true, wrap: true } @@ -121,7 +119,7 @@ pub struct Flex { pub basis: LengthPercentageOrAuto, } -// Zig: `css.DefineShorthand(@This(), css.PropertyIdTag.flex, PropertyFieldMap)` — see FlexFlow note. +// Shorthand metadata recorded here for a future shorthand derive (see FlexFlow note): // PropertyFieldMap = { grow: PropertyIdTag::FlexGrow, shrink: PropertyIdTag::FlexShrink, basis: PropertyIdTag::FlexBasis } // VendorPrefixMap = { grow: true, shrink: true, basis: true } @@ -537,8 +535,6 @@ impl FlexHandler { dest: &mut css::DeclarationList, context: &mut css::PropertyHandlerContext, ) -> bool { - // Zig used local closures with `@field(self, prop)` comptime reflection; - // ported as macro_rules! token-pasting on field idents. macro_rules! maybe_flush { ($prop:ident, $val:expr, $vp:expr) => {{ // If two vendor prefixes for the same property have different @@ -556,9 +552,8 @@ impl FlexHandler { maybe_flush!($prop, $val, $vp); // Otherwise, update the value and add the prefix - // Zig threaded `context.arena` into `css.generic.deepClone`; - // every payload here is `Clone` (Copy enums / f32 / i32 / LengthPercentageOrAuto), - // so `.clone()` is the faithful equivalent. + // Every payload here is `Clone` (Copy enums / f32 / i32 / + // LengthPercentageOrAuto), so `.clone()` suffices. if let Some(field) = &mut self.$prop { field.0 = ($val).clone(); field.1.insert(*$vp); @@ -643,8 +638,7 @@ impl FlexHandler { Property::Unparsed(val) => { if Self::is_flex_property(&val.property_id) { self.flush(dest, context); - // Zig pushed `property.deepClone(context.arena)`. `Property` - // has no blanket `deep_clone` yet; reconstruct from the matched payload. + // `Property` has no blanket `deep_clone` yet; reconstruct from the matched payload. let bump = dest.bump(); dest.push(Property::Unparsed(val.deep_clone(bump))); } else { @@ -692,8 +686,6 @@ impl FlexHandler { let mut order = self.order.take(); let mut flex_order = self.flex_order.take(); - // Zig `legacyProperty` / `singleProperty` use `@unionInit(Property, name, ...)` - // (comptime token-pasting); ported as macro_rules! taking the Property variant ident. macro_rules! legacy_property { ($variant:ident, $key:expr) => {{ if let Some(value) = $key { @@ -702,8 +694,7 @@ impl FlexHandler { if !prefix.is_empty() { dest.push(Property::$variant((val, prefix))); } else { - // css.generic.eql(comptime T: type, lhs: *const T, rhs: *const T) - // css.generic.deinit(@TypeOf(val), &val, ctx.arena); + // No prefix: the value is dropped here. } } }}; @@ -742,7 +733,6 @@ impl FlexHandler { } if let (Some(dir_val), Some(wrap_val)) = (&mut direction, &mut wrap) { - // reshaped for borrowck — Zig took simultaneous &mut into both Options. let dir: &FlexDirection = &dir_val.0; let dir_prefix: &mut VendorPrefix = &mut dir_val.1; let wrapinner: &FlexWrap = &wrap_val.0; @@ -797,7 +787,6 @@ impl FlexHandler { // prop_2012 = Some, prop_2009 = BoxOrdinalGroup special case ($variant:ident, $key:expr, prop_2012 = $p2012:ident, prop_2009 = (BoxOrdinalGroup, $v2009:ident), feature = $feature:ident) => {{ single_property!(@inner $variant, $key, $feature, |val, _prefix, prefixes_2009: VendorPrefix| { - // Zig: if T == BoxOrdinalGroup -> Some(val as i32) let s: Option = Some(val); if let Some(v) = s { dest.push(Property::$v2009((v, prefixes_2009))); @@ -848,11 +837,10 @@ impl FlexHandler { } }}; } - // single_property! encodes Zig's comptime `prop_2009`/`prop_2012` branches. The Zig - // version gates the entire 2009 block at comptime on `prop_2009 != null`; here the - // `prop_2009 = None` arms pass a no-op closure instead. That block has no side effects - // other than invoking the body (it only reads `prefix`/`targets` into locals), so - // running it with a no-op body is behaviorally identical to skipping it. + // The `prop_2009 = None` arms pass a no-op closure for the 2009 block. + // That block has no side effects other than invoking the body (it only + // reads `prefix`/`targets` into locals), so running it with a no-op + // body is behaviorally identical to skipping it. single_property!( FlexDirection, @@ -969,5 +957,3 @@ impl FlexHandler { ) } } - -// ported from: src/css/properties/flex.zig diff --git a/src/css/properties/font.rs b/src/css/properties/font.rs index cfb8ed4716e7..8669864f637b 100644 --- a/src/css/properties/font.rs +++ b/src/css/properties/font.rs @@ -1,6 +1,4 @@ //! CSS font properties. -//! -//! Ported from `src/css/properties/font.zig`. // // The data types (FontWeight / AbsoluteFontWeight / FontSize / // AbsoluteFontSize / RelativeFontSize / @@ -49,9 +47,8 @@ pub enum FontWeight { } impl FontWeight { - // Zig `css.DeriveParse(@This()).parse` for a union(enum) with one - // payload variant + 2 keyword variants tries the payload first, then matches - // the remaining keywords against `expect_ident`. + // Tries the payload variant first, then matches the remaining keywords + // against `expect_ident`. pub(crate) fn parse(input: &mut css::Parser) -> CssResult { if let Ok(v) = input.try_parse(AbsoluteFontWeight::parse) { return Ok(FontWeight::Absolute(v)); @@ -103,8 +100,7 @@ pub enum AbsoluteFontWeight { } impl AbsoluteFontWeight { - // Zig `css.DeriveParse(@This()).parse` — payload (`CSSNumber`) first, - // then keyword variants. + // Payload (`CSSNumber`) first, then keyword variants. pub(crate) fn parse(input: &mut css::Parser) -> CssResult { if let Ok(n) = input.try_parse(CSSNumberFns::parse) { return Ok(AbsoluteFontWeight::Weight(n)); @@ -235,8 +231,7 @@ pub enum FontStretch { } impl FontStretch { - // Zig `css.DeriveParse(@This()).parse` — two payload variants - // tried in declaration order. + // Two payload variants tried in declaration order. pub(crate) fn parse(input: &mut css::Parser) -> CssResult { if let Ok(kw) = input.try_parse(FontStretchKeyword::parse) { return Ok(FontStretch::Keyword(kw)); @@ -335,8 +330,8 @@ pub enum FontFamily { // Arena-backed slice: the pointer targets bytes owned by the parser arena and is // only valid while that arena is alive (becomes `&'bump [u8]` once the 'bump // lifetime is threaded through these types). With `*const [u8]`, derived - // PartialEq/Eq/Hash would compare by pointer; Zig's custom HashContext - // hashes/compares by content (Wyhash over bytes) — manual impls below. + // PartialEq/Eq/Hash would compare by pointer; the manual impls below + // hash/compare by content (Wyhash over bytes). FamilyName(*const [u8]), } @@ -433,9 +428,8 @@ impl FontFamily { // `CssEql`/`DeepClone` via `bridge_clone_partialeq!` in `generics.rs`. } -// Zig's `css.implementEql` / `css.implementHash` walked fields by -// reflection and compared/hashed `[]const u8` by *content*. With `*const [u8]` -// in Rust, derived `PartialEq`/`Hash` would compare pointers, so hand-roll. +// With `*const [u8]`, derived `PartialEq`/`Hash` would compare pointers; +// hand-roll to compare/hash the bytes by *content*. impl PartialEq for FontFamily { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -452,9 +446,9 @@ impl Eq for FontFamily {} impl core::hash::Hash for FontFamily { fn hash(&self, state: &mut H) { - // Zig `css.implementHash` hashes the active tag then the - // payload bytes. With `*const [u8]` a derived Hash would hash the - // pointer address, breaking FontFamilyHashMap dedupe semantics. + // Hash the active tag then the payload bytes. With `*const [u8]` a + // derived Hash would hash the pointer address, breaking + // FontFamilyHashMap dedupe semantics. core::mem::discriminant(self).hash(state); match self { FontFamily::Generic(g) => g.hash(state), @@ -468,8 +462,8 @@ impl core::hash::Hash for FontFamily { impl Clone for FontFamily { fn clone(&self) -> Self { - // shallow — arena slice pointers are `Copy`; matches Zig's - // implicit struct copy. `deepClone` would re-alloc the slice in 'bump. + // shallow — arena slice pointers are `Copy`. + // `deepClone` would re-alloc the slice in 'bump. match self { FontFamily::Generic(g) => FontFamily::Generic(*g), FontFamily::FamilyName(n) => FontFamily::FamilyName(*n), @@ -647,8 +641,7 @@ pub enum LineHeight { } impl LineHeight { - // Zig `css.DeriveParse(@This()).parse` — keyword variant first - // (`normal`), then payload variants in declaration order. + // Keyword variant first (`normal`), then payload variants in declaration order. pub(crate) fn parse(input: &mut css::Parser) -> CssResult { if input .try_parse(|p| p.expect_ident_matching(b"normal")) @@ -686,10 +679,8 @@ impl LineHeight { } /// A value for the [font](https://www.w3.org/TR/css-fonts-4/#font-prop) shorthand property. -// Zig's `eql`/`deepClone` were reflection-based (`css.implementEql` -// / `css.implementDeepClone`); the field-wise `#[derive(DeepClone, CssEql)]` -// is the Rust equivalent — every field type carries the trait via the -// blankets/bridges in `generics.rs`. +// Field-wise `#[derive(DeepClone, CssEql)]` — every field type carries the +// trait via the blankets/bridges in `generics.rs`. #[derive(DeepClone, CssEql)] pub struct Font { /// The font family. @@ -746,7 +737,7 @@ impl Font { } if variant_caps.is_some() { - // Zig has `if (variant_caps != null)` here — preserved verbatim (likely upstream bug; should be `== null`) + // Intentionally checks `is_some()` to match upstream lightningcss (likely a bug there; should be `is_none()`) if let Ok(value) = input.try_parse(FontVariantCaps::parse_css2) { variant_caps = Some(value); count += 1; @@ -776,8 +767,6 @@ impl Font { None }; - // Zig `Vec(FontFamily).parse` parsed a comma-separated - // list and packed it; route through `parse_comma_separated` + move. let family = input .parse_comma_separated(FontFamily::parse) .map(Vec::::move_from_list)?; @@ -878,8 +867,7 @@ impl FontProperty { pub(crate) fn try_from_property_id( property_id: crate::properties::PropertyIdTag, ) -> Option { - // Zig used `inline for` over std.meta.fields + @field; expanded by hand — - // keep in sync when new Font* PropertyIdTag variants are added. + // Keep in sync when new Font* PropertyIdTag variants are added. use crate::properties::PropertyIdTag; match property_id { PropertyIdTag::FontFamily => Some(FontProperty::FONT_FAMILY), @@ -923,8 +911,8 @@ impl FontHandler { // arena is recovered via `dest.bump()` (DeclarationList = bumpalo::Vec). let arena = dest.bump(); - // Zig used `comptime prop: []const u8` + @field for property_helper / flush_helper / - // push; expanded as macro_rules! over (handler_field, Property variant, FontProperty flag). + // macro_rules! over (handler_field, Property variant, FontProperty flag) + // for property_helper / flush_helper / push. macro_rules! flush_helper { ($this:expr, $field:ident, $val:expr) => {{ if $this.$field.is_some() @@ -980,7 +968,6 @@ impl FontHandler { self.flush(dest, context); self.flushed_properties .insert(FontProperty::try_from_property_id(val.property_id.tag()).unwrap()); - // PERF(port): was dest.append(context.arena, property.*) on arena dest.push(property.deep_clone(arena)); } else { return false; @@ -1012,7 +999,6 @@ impl FontHandler { macro_rules! push_prop { (Font, $val:expr) => {{ - // PERF(port): was dest.append(ctx.arena, ..) on arena-backed list decls.push(Property::Font($val)); self.flushed_properties.insert(FontProperty::FONT); }}; @@ -1048,12 +1034,10 @@ impl FontHandler { if let Some(f) = family.as_mut() { if f.len() > 1 { // Dedupe - // PERF(port): was std.heap.stackFallback(664, default_allocator) — profile if it shows up on a hot path let mut seen: FontFamilyHashMap<()> = Default::default(); let mut i: usize = 0; while i < f.len() { - // Zig: `seen.getOrPut` — single-lookup entry API. use bun_collections::array_hash_map::MapEntry; match seen.entry(f.at(i).clone()) { MapEntry::Occupied(_) => { @@ -1132,8 +1116,8 @@ impl FontHandler { } } -// Zig SYSTEM_UI was `const FontFamily = .{ .generic = .system_ui }`; matched against -// the Generic variant directly here instead of a const (FontFamily holds a raw pointer). +// Matched against the Generic variant directly instead of a const +// (FontFamily holds a raw pointer). fn is_system_ui(f: &FontFamily) -> bool { matches!(f, FontFamily::Generic(GenericFontFamily::SystemUi)) } @@ -1163,11 +1147,10 @@ fn compatible_font_family( } if let Some(families) = family.as_mut() { - // Zig (font.zig:1029-1035) iterates `families.sliceConst()` - // by value while inserting into `families` mid-loop, then `break`s. - // In Rust the immutable slice borrow would alias the &mut needed for - // `insert` (and `insert` may reallocate, invalidating the iterator). - // Reshape: capture the system-ui index first, drop the borrow, then + // Iterating the slice while inserting into `families` mid-loop would + // alias the &mut needed for `insert` (and `insert` may reallocate, + // invalidating the iterator). + // Capture the system-ui index first, drop the borrow, then // perform the inserts using the captured index. if let Some(i) = families.slice_const().iter().position(is_system_ui) { for (j, name) in DEFAULT_SYSTEM_FONTS.iter().enumerate() { @@ -1197,5 +1180,3 @@ fn is_font_property(property_id: &crate::properties::PropertyId) -> bool { | PropertyId::Font ) } - -// ported from: src/css/properties/font.zig diff --git a/src/css/properties/generate_properties.ts b/src/css/properties/generate_properties.ts index 5bda2f67bb50..19fc43ebfc44 100644 --- a/src/css/properties/generate_properties.ts +++ b/src/css/properties/generate_properties.ts @@ -1529,14 +1529,14 @@ generateCode({ // cursor: { // ty: "Cursor", // }, - // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.zig :) + // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.rs :) // "caret-color": { // ty: "ColorOrAuto", // }, // "caret-shape": { // ty: "CaretShape", // }, - // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.zig :) + // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.rs :) // caret: { // ty: "Caret", // shorthand: true, @@ -1573,7 +1573,7 @@ generateCode({ conditional: { css_modules: true }, parse_dont_make_unparsed: true, }, - // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.zig :) + // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.rs :) // fill: { // ty: "SVGPaint", // }, @@ -1583,7 +1583,7 @@ generateCode({ // "fill-opacity": { // ty: "AlphaValue", // }, - // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.zig :) + // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.rs :) // stroke: { // ty: "SVGPaint", // }, @@ -1752,12 +1752,12 @@ generateCode({ unprefixed: false, }, - // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.zig :) + // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.rs :) // filter: { // ty: "FilterList", // valid_prefixes: ["webkit"], // }, - // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.zig :) + // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.rs :) // "backdrop-filter": { // ty: "FilterList", // valid_prefixes: ["webkit"], diff --git a/src/css/properties/grid.rs b/src/css/properties/grid.rs index 38f2430d4902..b761b969986c 100644 --- a/src/css/properties/grid.rs +++ b/src/css/properties/grid.rs @@ -299,8 +299,7 @@ impl TrackRepeat { .unwrap_or_else(|_| CustomIdentList::default()); line_names.push(line_name); - // The Zig original referenced the outer `input` here (likely a - // bug); use the nested parser `i`. + // Use the nested parser `i`, not the outer `input`. if let Ok(track_size) = i.try_parse(TrackSize::parse) { // TODO: error handling track_sizes.push(track_size); @@ -386,8 +385,7 @@ fn parse_line_names(input: &mut Parser) -> css::Result { input.parse_nested_block(|i: &mut Parser| -> css::Result { let mut values = CustomIdentList::default(); - // The Zig original referenced the outer `input` here (likely a bug); - // use the nested parser `i`. + // Use the nested parser `i`, not the outer `input`. while let Ok(ident) = i.try_parse(CustomIdent::parse) { values.append(ident); } @@ -411,8 +409,8 @@ pub enum RepeatCount { } impl RepeatCount { - // Hand-expanded from Zig's `css.DeriveParse(@This()).parse` in declaration - // order (Number → keyword `auto-fill` → keyword `auto-fit`). + // Variants tried in declaration order (Number → keyword `auto-fill` → + // keyword `auto-fit`). pub fn parse(input: &mut Parser) -> css::Result { if let Ok(n) = input.try_parse(CSSIntegerFns::parse) { return Ok(RepeatCount::Number(n)); @@ -428,7 +426,6 @@ impl RepeatCount { Err(location.new_unexpected_token_error(css::Token::Ident(ident))) } - // Hand-expanded from Zig's `css.DeriveToCss(@This()).toCss`. pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { match self { RepeatCount::Number(n) => CSSIntegerFns::to_css(*n, dest), @@ -449,7 +446,7 @@ pub enum GridTemplateAreas { columns: u32, /// A flattened list of grid area names. /// Unnamed areas specified by the `.` token are represented as null. - // TODO(port): arena-owned slice lifetime — Zig `?[]const u8` in CSS arena + // TODO: arena-owned slice lifetime — should be `Option<&'bump [u8]>` areas: SmallList, 1>, }, } @@ -523,12 +520,11 @@ impl GridTemplateAreas { column += 1; if strings::starts_with_char(rest, b'.') { - // TODO(port): the Zig original falls through here without `continue` — - // likely a bug (per upstream lightningcss, a `.` null-cell token should - // push None and continue; as written, any string containing `.` fails - // the name-codepoint check below and the whole value fails to parse). - // Mirroring the observable Zig control flow for now (Zig dead-advances - // `string = rest[idx..]`; this block is empty — same outcome). + // TODO: this intentionally falls through without `continue`, + // which is likely a bug (per upstream lightningcss, a `.` + // null-cell token should push None and continue; as written, + // any string containing `.` fails the name-codepoint check + // below and the whole value fails to parse). } let starts_with_name_codepoint = 'brk: { @@ -551,7 +547,7 @@ impl GridTemplateAreas { rest.len() }; let token = &rest[..token_len]; - // TODO(port): arena-owned slice — Zig stores borrowed slice into SmallList; using raw ptr here + // TODO: arena-owned slice — should store a borrowed slice into SmallList; using raw ptr here let _ = bump; tokens.append(Some(std::ptr::from_ref::<[u8]>(token))); string = &rest[token_len..]; @@ -572,5 +568,3 @@ fn is_name_codepoint(c: u8) -> bool { } crate::css_eql_partialeq!(TrackSize, RepeatCount); - -// ported from: src/css/properties/grid.zig diff --git a/src/css/properties/list.rs b/src/css/properties/list.rs index bbc9f7cc9f01..8b137891791f 100644 --- a/src/css/properties/list.rs +++ b/src/css/properties/list.rs @@ -1 +1 @@ -// ported from: src/css/properties/list.zig + diff --git a/src/css/properties/margin_padding.rs b/src/css/properties/margin_padding.rs index c15de5051926..44369ee5eddf 100644 --- a/src/css/properties/margin_padding.rs +++ b/src/css/properties/margin_padding.rs @@ -6,8 +6,7 @@ use crate::properties::{Property, PropertyId, PropertyIdTag}; use crate::{DeclarationList, PropertyHandlerContext}; use bun_alloc::ArenaVecExt as _; -// `RectShorthand`/`SizeShorthand` mirror Zig's `css.DefineRectShorthand` / -// `css.DefineSizeShorthand` comptime mixins. The marker traits stay (some +// The `RectShorthand`/`SizeShorthand` marker traits stay (some // callers name `::Value`). The rect-shorthand structs // below are stamped out by `define_rect_shorthand!` (struct + PROPERTY_FIELD_MAP // + deep_clone/eql + parse/to_css + RectShorthand impl); the size-shorthand @@ -71,11 +70,8 @@ impl_size_shorthand!( // Shorthand value types // ────────────────────────────────────────────────────────────────────────── // -// Zig used `css.DefineRectShorthand(@This(), V)` / `css.DefineSizeShorthand(@This(), V)` -// as comptime mixins that inject `parse` + `toCss`. In Rust those become trait -// impls (`RectShorthand` / `SizeShorthand`) that provide default `parse`/`to_css`. -// The trait comes first (PORTING.md §Comptime reflection); a `#[derive]` could -// replace the manual impls. +// Trait impls (`RectShorthand` / `SizeShorthand`) provide default +// `parse`/`to_css`. A `#[derive]` could replace the manual impls. // // `implementDeepClone` / `implementEql` are field-wise reflection helpers → // `#[derive(Clone, PartialEq)]`; the `DeepClone`/`CssEql` trait impls are @@ -361,27 +357,19 @@ pub type ScrollMarginHandler = SizeHandler; pub type InsetHandler = SizeHandler; // ────────────────────────────────────────────────────────────────────────── -// NewSizeHandler — Zig `fn(comptime ...) type { return struct { ... } }` +// SizeHandler // ────────────────────────────────────────────────────────────────────────── // -// The Zig generator took 11 `comptime PropertyIdTag` parameters, a -// `comptime PropertyCategory`, and an optional `{feature, shorthand_feature}` -// pair, and used `@field` / `@tagName` / `@unionInit` to project in/out of -// the `Property` tagged union by tag name at compile time. +// The per-variant projection in/out of the `Property` tagged union lives in +// a `SizeHandlerSpec` trait. The generic body (`handle_property` / `flush` / +// helpers) calls through `S::*`. Each concrete handler is a zero-sized +// marker type implementing the spec. // -// Rust cannot reflect on enum variants by `PropertyIdTag` value, so the -// per-variant projection is moved into a `SizeHandlerSpec` trait. The -// generic body (`handle_property` / `flush` / helpers) is preserved 1:1 and -// calls through `S::*`. Each concrete handler is a zero-sized marker type -// implementing the spec. -// -// A `macro_rules! size_handler_spec!` could generate the four -// `SizeHandlerSpec` impls from the same 13-argument table the Zig used, -// eliminating the per-spec extract/construct boilerplate. Left explicit for -// reviewability. +// A macro could generate the four `SizeHandlerSpec` impls from a single +// argument table, eliminating the per-spec extract/construct boilerplate. +// Left explicit for reviewability. -/// Selector for the four physical slots on `SizeHandler` (Zig used a -/// `comptime field: []const u8` and `@field(this, field)`). +/// Selector for the four physical slots on `SizeHandler`. #[derive(Copy, Clone)] enum PhysicalSlot { Top, @@ -400,12 +388,8 @@ enum LogicalSlot { } /// Compile-time configuration for one `SizeHandler` instantiation. -/// -/// Replaces the 13 `comptime` parameters of Zig's `NewSizeHandler` and the -/// `@field(property, @tagName(X_prop))` / `@unionInit(Property, @tagName(X_prop), v)` -/// reflection it performed. pub trait SizeHandlerSpec { - // ---- comptime tag parameters ---- + // ---- tag parameters ---- const TOP: PropertyIdTag; const BOTTOM: PropertyIdTag; const LEFT: PropertyIdTag; @@ -425,22 +409,22 @@ pub trait SizeHandlerSpec { const LEFT_ID: PropertyId; const RIGHT_ID: PropertyId; const SHORTHAND_CATEGORY: PropertyCategory; - /// `shorthand_extra.?.feature` — `None` ⇔ Zig passed `null`. + /// Optional prefix feature for the shorthand. const FEATURE: Option; /// `shorthand_extra.?.shorthand_feature`. const SHORTHAND_FEATURE: Option; - // ---- value-type bindings (Zig: `X_prop.valueType()`) ---- + // ---- value-type bindings ---- // In every instantiation in this file the longhand value type is // `LengthPercentageOrAuto`, so the generic body below uses that // concretely. If a future spec needs a different `valueType()`, lift it // to an associated type here. - /// Zig: `shorthand_prop.valueType()` (the 4-field rect struct). + /// The 4-field rect struct. type Shorthand; - /// Zig: `block_shorthand.valueType()` (the 2-field block struct). + /// The 2-field block struct. type BlockShorthand; - /// Zig: `inline_shorthand.valueType()` (the 2-field inline struct). + /// The 2-field inline struct. type InlineShorthand; // ---- @field / @unionInit replacements ---- @@ -485,7 +469,7 @@ pub trait SizeHandlerSpec { inline_end: LengthPercentageOrAuto, ) -> Property; - // Field accessors on the shorthand value structs (Zig: `val.block_start` etc.). + // Field accessors on the shorthand value structs. fn shorthand_top(v: &Self::Shorthand) -> &LengthPercentageOrAuto; fn shorthand_right(v: &Self::Shorthand) -> &LengthPercentageOrAuto; fn shorthand_bottom(v: &Self::Shorthand) -> &LengthPercentageOrAuto; @@ -497,8 +481,6 @@ pub trait SizeHandlerSpec { } /// Generic margin/padding/inset/scroll-* handler. -/// -/// Zig: the anonymous `return struct { ... }` inside `NewSizeHandler`. pub struct SizeHandler { pub top: Option, pub bottom: Option, @@ -574,10 +556,10 @@ impl SizeHandler { dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) -> bool { - // Zig: `switch (@as(PropertyIdTag, property.*))` — the *raw* union - // discriminant, ported as `Property::variant_tag()`. The `.unparsed` - // arm needs the inner `property_id` to decide whether the unparsed - // value belongs to this handler, so it stays a structural match. + // Match on the *raw* union discriminant (`Property::variant_tag()`). + // The `Unparsed` arm needs the inner `property_id` to decide whether + // the unparsed value belongs to this handler, so it stays a + // structural match. if let Property::Unparsed(unparsed) = property { let id = unparsed.property_id.tag(); if id == S::TOP @@ -674,8 +656,7 @@ impl SizeHandler { dest, context, ); - // Zig stored `property.deepClone(arena)`; reconstruct - // via the spec's `make_X(extract_X)` pair (same observable shape). + // Reconstruct via the spec's `make_X(extract_X)` pair. self.logical_property_helper( LogicalSlot::BlockStart, S::make_block_start(S::extract_block_start(property).clone()), @@ -830,12 +811,11 @@ impl SizeHandler { self.flush(dest, context); } - // Reshaped — Zig's single `flushHelper` (generic over `comptime field: []const u8` - // via `@field(this, field)`) is split into `flush_helper_physical` + `flush_helper_logical` - // because the physical slots hold `Option` and the logical slots hold - // `Option`; Rust cannot express `@field` over heterogeneous Option payloads generically. + // The flush helper is split into `flush_helper_physical` + `flush_helper_logical` + // because the physical slots hold `Option` and the + // logical slots hold `Option`. - /// Zig `flushHelper` for the four physical slots (`top`/`bottom`/`left`/`right`). + /// Flush helper for the four physical slots (`top`/`bottom`/`left`/`right`). fn flush_helper_physical( &mut self, field: PhysicalSlot, @@ -844,7 +824,6 @@ impl SizeHandler { dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) { - // PERF(port): `category` was comptime monomorphization — profile if hot. // If the category changes betweet logical and physical, // or if the value contains syntax that isn't supported across all targets, // preserve the previous value as a fallback. @@ -857,7 +836,7 @@ impl SizeHandler { } } - /// Zig `flushHelper` for the four logical slots (`block_start`/.../`inline_end`). + /// Flush helper for the four logical slots (`block_start`/.../`inline_end`). fn flush_helper_logical( &mut self, field: LogicalSlot, @@ -866,7 +845,6 @@ impl SizeHandler { dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) { - // PERF(port): `category` was comptime monomorphization — profile if hot. // If the category changes betweet logical and physical, // or if the value contains syntax that isn't supported across all targets, // preserve the previous value as a fallback. @@ -887,7 +865,6 @@ impl SizeHandler { dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) { - // PERF(port): `category` was comptime monomorphization — profile if hot. self.flush_helper_physical(field, val, category, dest, context); *self.physical_slot(field) = Some(val.clone()); self.category = category; @@ -908,8 +885,7 @@ impl SizeHandler { self.flush(dest, context); } - // Zig: `if (@field(this, field)) |*p| p.deinit(context.arena);` - // Drop handles deinit; assigning over the Option drops the old value. + // Assigning over the Option drops the old value. *self.logical_slot(field) = Some(val); self.category = PropertyCategory::Logical; self.has_any = true; @@ -998,8 +974,7 @@ impl SizeHandler { context, ); } else if inline_start.is_some() || inline_end.is_some() { - // Zig: `inline_start.? == @field(Property, @tagName(inline_start_prop))` - // — raw union-tag equality, which is `false` for `.unparsed`. + // Raw union-tag equality, which is `false` for `Unparsed`. let start_matches = inline_start .as_ref() .map(|p| p.variant_tag() == S::INLINE_START) @@ -1077,7 +1052,7 @@ impl SizeHandler { // _ = this; // autofix let bump = dest.bump(); if let Some(v_) = val.as_ref() { - // Zig: `@as(css.PropertyIdTag, _v.*) == logical` — raw discriminant. + // Raw discriminant comparison. if v_.variant_tag() == logical { let v = extract_logical(v_); context.add_logical_rule(make_ltr(v.clone()), make_rtl(v.clone())); @@ -1111,8 +1086,7 @@ impl SizeHandler { LogicalSidePair::Inline => (S::INLINE_START, S::INLINE_END), }; - // Zig: `@as(PropertyIdTag, start.*.?) == start_prop` — raw - // discriminant. `variant_tag()` keeps `Unparsed` distinct so an + // Raw discriminant comparison. `variant_tag()` keeps `Unparsed` distinct so an // unparsed longhand falls through to the else branch and is appended // as-is, instead of hitting `unreachable!()` in `extract_*`. if start @@ -1125,9 +1099,7 @@ impl SizeHandler { .unwrap_or(false) && shorthand_supported { - // Zig built `value: ValueType` field-by-field then `@unionInit`. - // The Zig also `@compileError`ed if the value type had >2 fields; - // that invariant is upheld structurally by `make_*_shorthand`. + // The ≤2-field invariant is upheld structurally by `make_*_shorthand`. let start_v = match pair { LogicalSidePair::Block => S::extract_block_start(start.as_ref().unwrap()).clone(), LogicalSidePair::Inline => S::extract_inline_start(start.as_ref().unwrap()).clone(), @@ -1165,11 +1137,10 @@ impl SizeHandler { let _ = context; let bump = dest.bump(); if let Some(v) = val.as_ref() { - // Zig: `@as(css.PropertyIdTag, v.*) == logical` — raw discriminant. + // Raw discriminant comparison. if v.variant_tag() == logical { - // Zig moved the payload (`@field(v, @tagName(logical))`) by value. - // Reshaped for borrowck — clone instead of moving out - // of `&Property`; `LengthPercentageOrAuto` is small. + // Clone instead of moving out of `&Property`; + // `LengthPercentageOrAuto` is small. dest.push(make_physical(extract_logical(v).clone())); } else if let Property::Unparsed(u) = v { dest.push(Property::Unparsed(u.with_property_id(bump, physical))); @@ -1188,11 +1159,9 @@ enum LogicalSidePair { // Spec instantiations // ────────────────────────────────────────────────────────────────────────── // -// The `extract_*` / `make_*` / `shorthand_*` bodies are pure -// `@field` / `@unionInit` token-pasting in Zig (`NewSizeHandler`). -// `size_handler_spec_projections!` expands them from the 11 `Property` -// variant idents + 3 shorthand value-type idents that the Zig -// `NewSizeHandler(...)` call sites passed positionally. +// `size_handler_spec_projections!` expands the `extract_*` / `make_*` / +// `shorthand_*` bodies from the 11 `Property` variant idents + 3 shorthand +// value-type idents. macro_rules! size_handler_spec_projections { ( @@ -1496,7 +1465,5 @@ impl SizeHandlerSpec for InsetSpec { ); } -// NOTE: Zig also defined `ScrollPadding{,Block,Inline}` value types above but -// did NOT instantiate a `ScrollPaddingHandler` — matching that here. - -// ported from: src/css/properties/margin_padding.zig +// NOTE: `ScrollPadding{,Block,Inline}` value types are defined above but no +// `ScrollPaddingHandler` is instantiated. diff --git a/src/css/properties/masking.rs b/src/css/properties/masking.rs index cc9a89a25569..47b56d9bd74c 100644 --- a/src/css/properties/masking.rs +++ b/src/css/properties/masking.rs @@ -73,8 +73,7 @@ pub enum BasicShape { } /// An [`inset()`](https://www.w3.org/TR/css-shapes-1/#funcdef-inset) rectangle shape. -// Zig declares this `const` (file-private) but it's reachable via `pub enum BasicShape::Inset`, -// so Rust requires `pub` here — Zig has no private-in-public lint. +// Reachable via `pub enum BasicShape::Inset`, so it must be `pub`. pub struct InsetRect { /// The rectangle. pub rect: Rect, @@ -107,8 +106,7 @@ pub struct Polygon { /// The points of each vertex of the polygon. // If Polygon ever becomes arena-fed this must become (§Allocators: AST crates are arena-fed) // `bun_alloc::ArenaVec<'bump, Point>` and Polygon/BasicShape/ClipPath gain `<'bump>`. - // No construction site exists in src/css/*.zig today, so provenance is unconfirmed; keeping - // plain Vec until the arena story is verified. + // Keeping plain Vec until the arena story is verified. pub points: Vec, } @@ -152,7 +150,6 @@ pub enum MaskMode { #[derive(Debug, Clone, Copy, PartialEq, Eq, css::Parse, css::ToCss)] pub enum MaskClip { /// A geometry box. - // Zig: @"geometry-box" GeometryBox(GeometryBox), /// The painted content is not clipped. #[css(name = "no-clip")] @@ -213,8 +210,7 @@ pub struct Mask { } impl Mask { - // Zig's PropertyFieldMap was an anon-struct const consumed by comptime - // reflection in shorthand handlers; represented here as an assoc const slice. + // Shorthand field→property map, consumed by shorthand handlers. pub const PROPERTY_FIELD_MAP: &'static [(&'static str, PropertyIdTag)] = &[ ("image", PropertyIdTag::MaskImage), ("position", PropertyIdTag::MaskPosition), @@ -226,8 +222,7 @@ impl Mask { ("mode", PropertyIdTag::MaskMode), ]; - // Zig's VendorPrefixMap was an anon-struct const of bools consumed by - // comptime reflection; represented here as a field-name slice. + // Field names that carry a vendor prefix. pub const VENDOR_PREFIX_MAP: &'static [&'static str] = &["image", "position", "size", "repeat", "clip", "origin"]; @@ -423,7 +418,7 @@ impl MaskBorder { }); if border_image.is_ok() || mode.is_some() { - // PERF(port): Zig used `comptime BorderImage.default()` — could const-eval the default + // PERF: could const-eval the default let bi = border_image.unwrap_or_else(|_| BorderImage::default()); Ok(MaskBorder { source: bi.source, @@ -519,8 +514,6 @@ pub enum WebKitMaskSourceType { // blocked_on: PropertyId::WebKitMaskComposite variant name (codegen spelling is `WebKitMaskComposite`) pub fn get_webkit_mask_property(property_id: &PropertyId) -> Option { - // Zig spells these variants kebab-case (@"mask-border-source" etc.); the - // Rust PropertyId enum uses the PascalCase spellings below. match property_id { PropertyId::MaskBorderSource => Some(PropertyId::MaskBoxImageSource(VendorPrefix::WEBKIT)), PropertyId::MaskBorderSlice => Some(PropertyId::MaskBoxImageSlice(VendorPrefix::WEBKIT)), @@ -533,5 +526,3 @@ pub fn get_webkit_mask_property(property_id: &PropertyId) -> Option _ => None, } } - -// ported from: src/css/properties/masking.zig diff --git a/src/css/properties/mod.rs b/src/css/properties/mod.rs index f0f27045a89c..b0013bfafa1b 100644 --- a/src/css/properties/mod.rs +++ b/src/css/properties/mod.rs @@ -1,6 +1,4 @@ //! CSS property definitions. -//! -//! Ported from `src/css/properties/properties.zig`. #![warn(unused_must_use)] @@ -15,9 +13,7 @@ // `Property` payloads that name `css_values::*` resolve directly. // ─── Rect / Size shorthand impl + define macros ──────────────────────────── -// Shared by `border.rs` and `margin_padding.rs`. These are the Rust port of -// Zig's `css.DefineRectShorthand` / `css.DefineSizeShorthand` comptime mixins -// (src/css/css_parser.zig:502 / :532). +// Shared by `border.rs` and `margin_padding.rs`. // // `impl_rect_shorthand!` / `impl_size_shorthand!` stamp out the inherent // `parse`/`to_css` pair (and the `generic::{Parse,ToCss}` forwarders) for a @@ -83,11 +79,7 @@ macro_rules! define_rect_shorthand { } impl $name { - // Zig applied `css.DefineShorthand(@This(), PropertyIdTag::, - // PropertyFieldMap)` here, but every method it defines - // (`fromLonghands`/`longhands`/`longhand`) bodies out to - // `@compileError(todo_stuff.depth)` (css_parser.zig:307ff) — it is not - // callable. `PROPERTY_FIELD_MAP` preserves the map data it consumed. + // `PROPERTY_FIELD_MAP` records the shorthand field→property map. pub const PROPERTY_FIELD_MAP: &[(&str, $crate::properties::PropertyIdTag)] = &[ ("top", $crate::properties::PropertyIdTag::$top_id), @@ -99,7 +91,7 @@ macro_rules! define_rect_shorthand { impl $crate::properties::margin_padding::RectShorthand for $name { type Value = $inner; } - // Zig `css.DefineRectShorthand(@This(), V)` — parse/to_css via `Rect`. + // parse/to_css via `Rect`. impl_rect_shorthand!($name, $inner); }; } @@ -131,7 +123,6 @@ macro_rules! impl_size_shorthand { } // ─── Submodule declarations ──────────────────────────────────────────────── -// (Zig: `pub const X = @import("./X.zig");`) // pub mod align; // `animation`: un-gated — real AnimationName / Animation / AnimationIterationCount / @@ -165,7 +156,7 @@ pub mod font; pub mod grid; // `list`: un-gated — real ListStyleType / CounterStyle / Symbols / Symbol // live in `list.rs`. PredefinedCounterStyle / SymbolsType / ListStylePosition / -// ListStyle / MarkerSide are uninhabited (Zig source is `@compileError`). +// ListStyle / MarkerSide are uninhabited. pub mod list; pub mod margin_padding; pub mod masking; @@ -206,8 +197,7 @@ pub use self::custom::CustomPropertyName; pub use self::properties_generated::{Property, PropertyId, PropertyIdTag}; /// A [CSS-wide keyword](https://drafts.csswg.org/css-cascade-5/#defaulting-keywords). -// Zig: `css.DefineEnumProperty(@This())` provides eql/hash/parse/toCss/deepClone via -// comptime reflection over @tagName. The Rust derive emits `EnumProperty` + +// The `DefineEnumProperty` derive emits `EnumProperty` + // `From for &'static str` + inherent `parse`/`to_css`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, crate::DefineEnumProperty)] pub enum CSSWideKeyword { @@ -404,12 +394,3 @@ mod generic_registrations { } } pub(crate) use generic_registrations::GenericBorderImpl; - -// ─── Dead code (not ported) ──────────────────────────────────────────────── -// The original Zig file contains ~1800 lines of commented-out code (lines 60–1876) -// implementing the old `DefineProperties(...)` comptime-reflection approach that -// predates `properties_generated.zig`. It is dead reference material and is -// intentionally omitted here. See `src/css/properties/properties.zig` for the -// historical block; the live definitions come from `properties_generated`. - -// ported from: src/css/properties/properties.zig diff --git a/src/css/properties/outline.rs b/src/css/properties/outline.rs index 9f42cdc663af..746cb7af0c17 100644 --- a/src/css/properties/outline.rs +++ b/src/css/properties/outline.rs @@ -4,9 +4,6 @@ use super::border::{GenericBorder, LineStyle}; pub(crate) type Outline = GenericBorder; /// A value for the [outline-style](https://drafts.csswg.org/css-ui/#outline-style) property. -// `DeriveParse`/`DeriveToCss` in Zig are comptime-reflection helpers that iterate variants -// to implement the domain protocol — in Rust the protocol is a trait and we derive it. -// `implementEql`/`implementDeepClone` are field-iteration eq/clone → `#[derive(PartialEq, Clone)]`. #[derive(Clone, PartialEq, Eq, crate::Parse, crate::ToCss)] pub enum OutlineStyle { /// The `auto` keyword. @@ -20,5 +17,3 @@ impl Default for OutlineStyle { OutlineStyle::LineStyle(LineStyle::None) } } - -// ported from: src/css/properties/outline.zig diff --git a/src/css/properties/overflow.rs b/src/css/properties/overflow.rs index dd03bc2f6c35..5fa4e16ced03 100644 --- a/src/css/properties/overflow.rs +++ b/src/css/properties/overflow.rs @@ -51,5 +51,3 @@ pub enum TextOverflow { /// Overflowing text is truncated with an ellipsis. Ellipsis, } - -// ported from: src/css/properties/overflow.zig diff --git a/src/css/properties/position.rs b/src/css/properties/position.rs index 944b2bc6d9ec..6fb463a75172 100644 --- a/src/css/properties/position.rs +++ b/src/css/properties/position.rs @@ -28,8 +28,7 @@ enum PositionKeyword { } fn lookup_keyword(ident: &[u8]) -> Option { - // ≤8 entries → plain match per PORTING.md (Zig: `bun.ComptimeEnumMap` + - // `getASCIIICaseInsensitive`). + // ≤8 entries → plain match. use bun_core::eql_case_insensitive_ascii_check_length as eq; Some(if eq(ident, b"static") { PositionKeyword::Static @@ -80,5 +79,3 @@ impl Position { } } } - -// ported from: src/css/properties/position.zig diff --git a/src/css/properties/prefix_handler.rs b/src/css/properties/prefix_handler.rs index fe64c146d969..77b8be3291be 100644 --- a/src/css/properties/prefix_handler.rs +++ b/src/css/properties/prefix_handler.rs @@ -9,9 +9,7 @@ use bun_alloc::ArenaVecExt as _; pub struct FallbackHandler { pub color: Option, pub text_shadow: Option, - // The remaining fallback fields are not implemented yet. This mirrors the Zig - // source (prefix_handler.zig), which has the same fields commented out; add them - // here when they are added there. + // The remaining fallback fields are not implemented yet. // filter: Option, // backdrop_filter: Option, // fill: Option, @@ -27,20 +25,15 @@ impl FallbackHandler { dest: &mut css::DeclarationList, context: &mut css::PropertyHandlerContext, ) -> bool { - // The Zig source does `inline for (std.meta.fields(FallbackHandler))` and uses - // `@field` / `@unionInit` keyed on the field name. Rust has no field reflection, - // so we expand each (field, Property variant, has_vendor_prefix) pair via macro. + // Each (field, Property variant, has_vendor_prefix) pair is expanded via macro. let arena = dest.bump(); - // Zig's `inline for` over `std.meta.fields(FallbackHandler)` dispatched - // each (field, Property variant) pair via a single generic body using `@field` / - // `@unionInit` + `css.generic.{deepClone,isCompatible,hasGetFallbacks}`. Rust has - // no field reflection and the generic-trait surface (`DeepClone`/`IsCompatible`/ - // `get_fallbacks` on `SmallList`) is still partially gated, so we - // expand each pair via a macro that takes per-type closures for those three ops. - // This keeps the *control flow* identical while letting each payload type use its - // own inherent methods until the trait lattice un-gates. + // The generic-trait surface (`DeepClone`/`IsCompatible`/`get_fallbacks` + // on `SmallList`) is still partially gated, so each + // (field, Property variant) pair is expanded via a macro that takes + // per-type closures for those three ops. This lets each payload type + // use its own inherent methods until the trait lattice un-gates. macro_rules! handle_unprefixed { ( $self_field:ident, @@ -53,7 +46,7 @@ impl FallbackHandler { let mut val = ($dc)(payload, arena); if $self_field.is_none() { - // Zig only consulted `has_fallbacks` in the vendor-prefixed branch. + // `has_fallbacks` is only consulted in the vendor-prefixed branch. ($fb)(&mut val, arena, &context.targets, dest); } @@ -66,7 +59,7 @@ impl FallbackHandler { } else if let Some(index) = *$self_field { dest[index] = Property::$Variant(val); } else { - // val dropped — Rust Drop handles cleanup (Zig: val.deinit(context.arena)) + // val dropped — Drop handles cleanup drop(val); } @@ -157,10 +150,7 @@ impl FallbackHandler { _dest: &mut css::DeclarationList, _context: &mut css::PropertyHandlerContext, ) { - // Zig: inline for (std.meta.fields(FallbackHandler)) |f| @field(this, f.name) = null; self.color = None; self.text_shadow = None; } } - -// ported from: src/css/properties/prefix_handler.zig diff --git a/src/css/properties/properties_generated.rs b/src/css/properties/properties_generated.rs index 745eee8b5844..11121aae21fa 100644 --- a/src/css/properties/properties_generated.rs +++ b/src/css/properties/properties_generated.rs @@ -1,11 +1,9 @@ -// Hand-maintained Rust port, originally derived from -// src/css/properties/properties_generated.zig. Note: generate_properties.ts -// (`bun run css-properties`) regenerates only the .zig reference file, never -// this .rs file — edits here are safe and will not be clobbered. +// Hand-maintained. Note: generate_properties.ts (`bun run css-properties`) +// does not write this file — edits here are safe and will not be clobbered. // // Type paths below resolve against `super::*` (the leaf property // modules — currently data-only stub bodies in `mod.rs`) and -// `crate::css_values`, matching the Zig prelude in properties_generated.zig. +// `crate::css_values`. #![allow(non_camel_case_types)] @@ -43,7 +41,7 @@ use super::transform; use super::transition; use super::ui; -/// Discriminant-only tag for [`Property`] / [`PropertyId`] (Zig: `enum(u16)`). +/// Discriminant-only tag for [`Property`] / [`PropertyId`]. #[repr(u16)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum PropertyIdTag { @@ -300,7 +298,7 @@ pub enum PropertyIdTag { impl PropertyIdTag { /// Whether the corresponding `Property` payload carries a `VendorPrefix` - /// (i.e. is a `(T, VendorPrefix)` tuple in the Zig union). + /// (i.e. is a `(T, VendorPrefix)` tuple). pub const fn has_vendor_prefix(self) -> bool { matches!( self, @@ -425,7 +423,7 @@ impl PropertyIdTag { }) } - /// Kebab-case CSS property name (Zig: `@tagName(PropertyIdTag)`). + /// Kebab-case CSS property name. pub const fn name(self) -> &'static [u8] { match self { PropertyIdTag::BackgroundColor => b"background-color", @@ -685,7 +683,7 @@ impl PropertyIdTag { /// prefix it was parsed with. Variants without payload are unprefixed. // // Do NOT `#[derive(PartialEq, Eq)]` here — the spec-correct -// equality (Zig `PropertyId.eql`, properties_generated.zig:9195) ignores the +// equality ignores the // `Custom(CustomPropertyName)` payload and is hand-written below. A derived // impl would (a) conflict (E0119) and (b) diverge by comparing custom-name // bytes. @@ -942,15 +940,14 @@ pub enum PropertyId { Custom(CustomPropertyName), } -// Zig `PropertyId.eql()` (properties_generated.zig:9195) compares the -// tag, then *only* compares the payload when its type is `VendorPrefix` — for -// `.custom` (whose payload is `CustomPropertyName`) and all unit/void variants -// it returns `true` on tag match alone. A derived `PartialEq` would compare the -// `CustomPropertyName` bytes, diverging from the spec (observable in -// `rules/style.zig:isDuplicate`). `prefix()` already returns the `VendorPrefix` -// payload for the 65 prefixed variants and `VendorPrefix::empty()` for every -// other variant (including `Custom`/`All`/`Unparsed`), so `tag` + `prefix` -// equality is exactly the Zig semantics. +// `PropertyId` equality compares the tag, then *only* compares the payload +// when its type is `VendorPrefix` — for `Custom` (whose payload is +// `CustomPropertyName`) and all unit variants it returns `true` on tag match +// alone. A derived `PartialEq` would compare the `CustomPropertyName` bytes, +// diverging from the duplicate-detection semantics in `rules/style.rs`. +// `prefix()` already returns the `VendorPrefix` payload for the 65 prefixed +// variants and `VendorPrefix::empty()` for every other variant (including +// `Custom`/`All`/`Unparsed`), so `tag` + `prefix` equality is exactly that. impl PartialEq for PropertyId { #[inline] fn eq(&self, other: &Self) -> bool { @@ -960,8 +957,8 @@ impl PartialEq for PropertyId { impl Eq for PropertyId {} -// Zig `PropertyId.hash()` (properties_generated.zig:9209) hashes only -// the tag discriminant so PropertyId can key a hash map consistent with `eql`. +// Hash only the tag discriminant so PropertyId can key a hash map +// consistent with `eq`. impl core::hash::Hash for PropertyId { #[inline] fn hash(&self, h: &mut H) { @@ -1225,9 +1222,6 @@ impl PropertyId { } /// Returns the property name, without any vendor prefixes. - /// - /// Mirrors Zig `PropertyId.name()` (properties_generated.zig:7674), which is - /// literally `if (.custom) return custom.asStr(); return @tagName(this.*);`. pub fn name(&self) -> &[u8] { match self { PropertyId::Custom(c) => c.as_str(), @@ -1349,8 +1343,8 @@ impl PropertyId { /// isn't allowed for that property. pub fn from_name_and_prefix(name: &[u8], pre: VendorPrefix) -> Option { use bun_core::strings; - // Zig used a comptime perfect-hash map; the linear scan here is - // correct but slower — a phf / match-on-len would be an optimization. + // PERF: the linear scan here is correct but slow — a phf / + // match-on-len would be an optimization. if strings::eql_case_insensitive_ascii_check_length(name, b"background-color") { let allowed: VendorPrefix = VendorPrefix::NONE; if allowed.intersects(pre) { @@ -6196,21 +6190,13 @@ impl Property { /// Returns the given longhand property for a shorthand. /// - /// In Zig (`properties_generated.zig:7087-7160`) each arm - /// dispatches to `v.longhand(property_id)` where the per-type `longhand` - /// is provided by `DefineShorthand`, whose body is - /// `@compileError(todo_stuff.depth)`. Zig only instantiates - /// `Property.longhand` when referenced (it isn't), so the @compileError - /// never fires. Rust type-checks eagerly, so the per-arm dispatch is - /// routed through a no-op `lh!` that mirrors the Zig fallthrough - /// (`return null`) until the `DefineShorthand` derive is ported. There - /// are no callers. - // blocked_on: shorthand_handler_port — leaf shorthand types lack `.longhand()` (Zig body is `@compileError(todo_stuff.depth)`, .zig:7087-7160) + /// Per-type `longhand` is not implemented yet, so the per-arm dispatch is + /// routed through a no-op `lh!` (`return None`) until the + /// `DefineShorthand` derive exists. There are no callers. + // blocked_on: shorthand_handler_port — leaf shorthand types lack `.longhand()` pub fn longhand(&self, property_id: &PropertyId) -> Option { #[inline(always)] fn lh(_v: &T, _id: &PropertyId) -> Option { - // Per-type `v.longhand(property_id)` is - // `@compileError(todo_stuff.depth)` in Zig and never instantiated. // Trip in debug so callers can't accidentally rely on the // always-`None` placeholder before `DefineShorthand::longhand` // is ported. @@ -6305,7 +6291,7 @@ impl Property { } } - // blocked_on: leaf_value_traits — un-gate once every payload type impls `generics::DeepClone` (.zig:6307-6558) + // blocked_on: leaf_value_traits — un-gate once every payload type impls `generics::DeepClone` pub fn deep_clone(&self, arena: &bun_alloc::Arena) -> Property { match self { Property::BackgroundColor(v) => { @@ -6882,7 +6868,7 @@ impl Property { } } - // blocked_on: leaf_value_traits — un-gate once every payload type impls `generics::CssEql` (.zig:7162-7415) + // blocked_on: leaf_value_traits — un-gate once every payload type impls `generics::CssEql` pub fn eql(&self, other: &Property) -> bool { match (self, other) { (Property::BackgroundColor(a), Property::BackgroundColor(b)) => css::generic::eql(a, b), @@ -7414,14 +7400,13 @@ impl Property { } // `declaration::placeholder_property()` (the moved-out slot -// sentinel in `DeclarationBlock::minify`) is `Property{ .all = .revert-layer }` -// in Zig. Expose it via `Default` so the un-gated stub branch in -// `declaration.rs` keeps compiling against the real enum. +// sentinel in `DeclarationBlock::minify`) is +// `Property::All(CSSWideKeyword::RevertLayer)`. Expose it via `Default` so +// the un-gated stub branch in `declaration.rs` keeps compiling against the +// real enum. impl Default for Property { #[inline] fn default() -> Self { Property::All(CSSWideKeyword::RevertLayer) } } - -// ported from: src/css/properties/properties_generated.zig diff --git a/src/css/properties/properties_impl.rs b/src/css/properties/properties_impl.rs index f835462aeb30..f3c05519931a 100644 --- a/src/css/properties/properties_impl.rs +++ b/src/css/properties/properties_impl.rs @@ -15,8 +15,8 @@ impl Property { /// an `Unparsed` declaration always returns `PropertyIdTag::Unparsed`, and /// a `Custom` declaration always returns `PropertyIdTag::Custom`. /// - /// This mirrors Zig's `@as(PropertyIdTag, property.*)` (a raw union-tag - /// coercion). Handlers that switch on the discriminant to project a parsed + /// This is a raw discriminant lookup. + /// Handlers that switch on the discriminant to project a parsed /// payload — e.g. `SizeHandler` in `margin_padding.rs` — must use this so /// an unparsed `margin-top: var(--x)` does not route into the parsed /// `MarginTop` arm and panic in `extract_top`. @@ -32,9 +32,8 @@ impl Property { } } -/// Ordered single-bit prefix flags for the `inline for (VendorPrefix.FIELDS)` -/// Zig idiom. The crate-root `VendorPrefix::FIELDS` is a -/// `&'static [VendorPrefix]` with the same values in the same declaration +/// Ordered single-bit prefix flags. The crate-root `VendorPrefix::FIELDS` is +/// a `&'static [VendorPrefix]` with the same values in the same declaration /// order (webkit, moz, ms, o, none); kept duplicated here as a fixed-size /// array for the to_css loops. pub(super) const PREFIX_FLAGS: [VendorPrefix; 5] = [ @@ -52,7 +51,7 @@ pub(super) mod property_id_mixin { let name = this.name(); let prefix_value = this.prefix().or_none(); - // `PREFIX_FLAGS` mirrors Zig's `VendorPrefix.FIELDS` — same set, same + // `PREFIX_FLAGS` matches `VendorPrefix::FIELDS` — same set, same // order; serialization order of prefixed names depends on it. dest.write_comma_separated( PREFIX_FLAGS @@ -119,5 +118,3 @@ pub(super) mod property_mixin { ) } } - -// ported from: src/css/properties/properties_impl.zig diff --git a/src/css/properties/shape.rs b/src/css/properties/shape.rs index f8aa42c6cba2..e184b566492c 100644 --- a/src/css/properties/shape.rs +++ b/src/css/properties/shape.rs @@ -4,9 +4,7 @@ pub use crate::css_parser as css; /// determine the interior of a `polygon()` shape. /// /// See [Polygon](Polygon). -// Zig source is `css.DefineEnumProperty(@compileError(css.todo_stuff.depth))` — -// a placeholder that compile-errors on use. Mirrored as an uninhabited enum -// until the CSS shapes module is actually implemented. +// Uninhabited placeholder until the CSS shapes module is actually implemented. pub enum FillRule {} /// A CSS [``](https://www.w3.org/TR/css-color-4/#typedef-alpha-value), @@ -16,5 +14,3 @@ pub enum FillRule {} pub struct AlphaValue { pub v: f32, } - -// ported from: src/css/properties/shape.zig diff --git a/src/css/properties/size.rs b/src/css/properties/size.rs index 4dc92e7d093a..e11669a08e3d 100644 --- a/src/css/properties/size.rs +++ b/src/css/properties/size.rs @@ -25,9 +25,8 @@ pub enum BoxSizing { /// Include the padding and border (but not the margin) in the width and height. BorderBox, } -// Zig: css::DefineEnumProperty(@This()) — provided eql/hash/parse/toCss/deepClone via -// comptime reflection over @tagName. Hand-written here (only two variants) so the inherent -// `parse`/`to_css` participate in `impl_parse_tocss_via_inherent!` without a derive-coherence clash. +// Hand-written (only two variants) so the inherent `parse`/`to_css` participate in +// `impl_parse_tocss_via_inherent!` without a derive-coherence clash. impl BoxSizing { pub(crate) fn parse(input: &mut css::Parser) -> css::Result { let location = input.current_source_location(); @@ -70,9 +69,8 @@ pub enum Size { } /// Case-insensitive keyword dispatch for `Size`/`MaxSize` parse bodies. -/// Zig used `bun.ComptimeStringMap(..).getASCIIICaseInsensitive` — -/// expanded as an `if`-chain over `eql_case_insensitive_ascii::` (≤14 keys; -/// per PORTING.md a phf table is overkill at this size). +/// An `if`-chain over `eql_case_insensitive_ascii::` (≤14 keys; +/// a phf table is overkill at this size). macro_rules! size_ident_match { ($ident:expr, { $($lit:literal => $val:expr,)+ } else $err:expr) => {{ let __ident: &[u8] = $ident; @@ -184,8 +182,7 @@ impl Size { } pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // Zig: css.implementDeepClone (comptime field-walk). `Size` carries only - // `LengthPercentage`/`VendorPrefix` payloads, both `Clone`-via-derive. + // `Size` carries only `LengthPercentage`/`VendorPrefix` payloads, both `Clone`-via-derive. self.clone() } @@ -321,8 +318,7 @@ impl MaxSize { } pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // Zig: css.implementDeepClone (comptime field-walk). `MaxSize` carries only - // `LengthPercentage`/`VendorPrefix` payloads, both `Clone`-via-derive. + // `MaxSize` carries only `LengthPercentage`/`VendorPrefix` payloads, both `Clone`-via-derive. self.clone() } @@ -374,7 +370,7 @@ impl AspectRatio { } pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // Zig: css.implementDeepClone — `Ratio` is two `f32`s; #[derive(Clone)] is exact. + // `Ratio` is two `f32`s; #[derive(Clone)] is exact. *self } @@ -409,8 +405,6 @@ bitflags::bitflags! { impl SizeProperty { pub(crate) fn try_from_property_id_tag(property_id: PropertyIdTag) -> Option { - // Zig used `inline for (std.meta.fields(@This()))` to compare each - // bitfield name against PropertyIdTag's @tagName. Expanded explicitly here. match property_id { PropertyIdTag::Width => Some(SizeProperty::WIDTH), PropertyIdTag::Height => Some(SizeProperty::HEIGHT), @@ -452,13 +446,7 @@ pub struct SizeHandler { // arena is recovered via `dest.bump()`. use css::compat::Feature; -// ─── helper macros (Zig used `inline fn` + `comptime []const u8` field names + @field/@unionInit) ─── -// -// The following four macros replace Zig's `propertyHelper`, `logicalUnparsedHelper`, -// `flushPrefixHelper`, `flushPropertyHelper`, `flushLogicalHelper`. The Zig code passes field -// names as comptime strings and uses @field/@unionInit/@tagName to splice them into struct/enum -// accesses. Rust has no equivalent reflection — macro_rules! is the closest 1:1 mapping. -// PERF(port): was comptime monomorphization. +// ─── helper macros ─── macro_rules! property_helper { ($this:expr, $field:ident, $ty:ty, $value:expr, $category:expr, $dest:expr, $context:expr) => {{ @@ -486,8 +474,7 @@ macro_rules! logical_unparsed_helper { $this.flushed_properties.insert( SizeProperty::try_from_property_id_tag($unparsed.property_id.tag()).unwrap(), ); - // Zig pushed `property.deepClone(arena)`; the matched - // payload is `Unparsed`, so reconstruct directly. + // The matched payload is `Unparsed`, so reconstruct directly. $dest.push(Property::Unparsed($unparsed.deep_clone(bump))); } else { $dest.push(Property::Unparsed( @@ -505,7 +492,7 @@ macro_rules! flush_prefix_helper { .targets .prefixes(VendorPrefix::NONE, css::prefixes::Feature::$feature) .difference(VendorPrefix::NONE); - // Zig: `inline for (css.VendorPrefix.FIELDS)` — iterate set bits. + // Iterate set bits. for prefix in prefixes.iter() { $dest.push(Property::$prop_variant($size_ty::$size_variant(prefix))); } @@ -966,4 +953,3 @@ impl SizeHandler { } } -// ported from: src/css/properties/size.zig diff --git a/src/css/properties/svg.rs b/src/css/properties/svg.rs index ad3c0402af0d..e69de29bb2d1 100644 --- a/src/css/properties/svg.rs +++ b/src/css/properties/svg.rs @@ -1 +0,0 @@ -// ported from: src/css/properties/svg.zig diff --git a/src/css/properties/text.rs b/src/css/properties/text.rs index 7ca3ba236e0c..4cbe089e34b4 100644 --- a/src/css/properties/text.rs +++ b/src/css/properties/text.rs @@ -126,15 +126,11 @@ impl TextShadow { && self.spread.is_compatible(browsers) } - // Zig: `pub fn eql` via `css.implementEql(@This(), ...)` — field-wise equality. - // Ported as `#[derive(PartialEq)]` above; callers use `==`. - pub(crate) fn deep_clone(&self, alloc: &bun_alloc::Arena) -> Self { - // Zig used reflection-based `css.implementDeepClone`. Fields deep-clone - // via `#[derive(Clone)]` (`CssColor`/`Length` `Box`-carrying variants - // clone deeply onto the global heap), so a plain Clone is equivalent; - // arena param retained for signature compatibility with the CSS - // deep_clone protocol. + // Fields deep-clone via `#[derive(Clone)]` (`CssColor`/`Length` + // `Box`-carrying variants clone deeply onto the global heap), so a + // plain Clone is equivalent; arena param retained for signature + // compatibility with the CSS deep_clone protocol. let _ = alloc; self.clone() } @@ -151,7 +147,6 @@ impl css::generics::IsCompatible for TextShadow { } /// A value for the [direction](https://drafts.csswg.org/css-writing-modes-3/#direction) property. -// Zig wires eql/hash/parse/toCss/deepClone via `css.DefineEnumProperty(@This())`. #[derive( Debug, Clone, Copy, PartialEq, Eq, Hash, crate::DefineEnumProperty, crate::generics::CssHash, )] @@ -162,4 +157,3 @@ pub enum Direction { Rtl, } -// ported from: src/css/properties/text.zig diff --git a/src/css/properties/transform.rs b/src/css/properties/transform.rs index 9945125b0433..d7b25b5b0a9e 100644 --- a/src/css/properties/transform.rs +++ b/src/css/properties/transform.rs @@ -28,7 +28,7 @@ impl TransformList { pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { // `Transform`/`TransformList` deep-clone via `#[derive(Clone)]`: payloads // with heap variants (`LengthPercentage`/`Length` `Calc(Box<..>)`) clone - // deeply onto the global heap, matching Zig's `css.implementDeepClone`. + // deeply onto the global heap. self.clone() } } @@ -64,10 +64,7 @@ impl TransformList { // TODO: Re-enable with a better solution // See: https://github.com/parcel-bundler/lightningcss/issues/288 - // Zig's minify branch built a sub-`Printer` writing into a temp - // buffer then `dest.writeStr(base)` — observably identical to writing - // directly into `dest` while `dest.minify` is set (the original - // lightningcss size-comparison was already disabled upstream). Collapsed. + // (The original lightningcss size-comparison was already disabled upstream.) self.to_css_base(dest) } @@ -240,8 +237,7 @@ impl Transform { let y = NumberOrPercentage::parse(i)?; Ok(Transform::Scale { x, y }) } else { - // `NumberOrPercentage` is POD; `clone()` matches Zig's - // `x.deepClone(arena)` exactly. + // `NumberOrPercentage` is POD. let y = x.clone(); Ok(Transform::Scale { x, y }) } @@ -575,7 +571,7 @@ impl Transform { pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { // All payload types deep-clone via `#[derive(Clone)]` — `Calc` variants // of `LengthPercentage`/`Length` are `Box`ed and clone deeply onto the - // global heap — matching Zig's `css.implementDeepClone`. + // global heap. self.clone() } } @@ -744,8 +740,7 @@ impl Translate { } pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self { - // `LengthPercentage` is `Clone`-via-derive; a plain clone matches - // Zig's `css.implementDeepClone`. + // `LengthPercentage` is `Clone`-via-derive; a plain clone suffices. self.clone() } } @@ -985,7 +980,6 @@ impl TransformHandler { context: &mut PropertyHandlerContext, ) -> bool { let bump = dest.bump(); - // Zig used a local fn with `comptime field: []const u8` + `@field(self, field)`. // Rust cannot index struct fields by string at runtime; use a macro to paste the ident. macro_rules! individual_property { ($field:ident, $val:expr) => {{ @@ -1005,8 +999,8 @@ impl TransformHandler { // If two vendor prefixes for the same property have different // values, we need to flush what we have immediately to preserve order. - // Reshaped for borrowck — Zig held &self.transform across - // self.flush(); compute the predicate first, then act. + // Compute the predicate first, then act, so no borrow of + // self.transform is held across self.flush(). let needs_flush = if let Some(current) = &self.transform { current.0 != *transform_val && !current.1.contains(vp) } else { @@ -1047,8 +1041,7 @@ impl TransformHandler { prefixes::Feature::Transform, )) } else { - // Zig pushed `property.deepClone(arena)`; the matched - // payload is `Unparsed`, so reconstruct directly. + // The matched payload is `Unparsed`, so reconstruct directly. Property::Unparsed(unparsed.deep_clone(bump)) }; dest.push(prop); @@ -1101,4 +1094,3 @@ impl TransformHandler { } } -// ported from: src/css/properties/transform.zig diff --git a/src/css/properties/transition.rs b/src/css/properties/transition.rs index 9c21020aebc4..9b72668b0f53 100644 --- a/src/css/properties/transition.rs +++ b/src/css/properties/transition.rs @@ -33,13 +33,11 @@ pub struct Transition { impl Transition { pub(crate) fn eql(&self, rhs: &Self) -> bool { - // Zig: css.implementEql(@This(), lhs, rhs) — field-by-field reflection. self == rhs } pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // Zig: css.implementDeepClone(@This(), this, arena) — field-by-field - // reflection. All four fields deep-clone via `#[derive(Clone)]` with no + // All four fields deep-clone via `#[derive(Clone)]` with no // arena indirections (any heap-carrying variants of `PropertyId`/ // `EasingFunction` clone deeply onto the global heap), so Clone is exact. self.clone() @@ -120,11 +118,9 @@ pub struct TransitionHandler { pub has_any: bool, } -// Zig's `property`/`maybeFlush` took `comptime prop: []const u8` and used -// `@field(this, prop)` + `val: anytype` for comptime field dispatch. Rust has no -// `@field`, and passing both `&mut self` and `&mut self.` to a generic fn -// trips borrowck (because `flush` needs `&mut self`). Macros expand at the call -// site exactly like the Zig comptime dispatch did. +// Passing both `&mut self` and `&mut self.` to a generic fn trips +// borrowck (because `flush` needs `&mut self`), so macros expand the field +// dispatch at the call site instead. macro_rules! handler_maybe_flush { ($this:expr, $dest:expr, $context:expr, $field:ident, $val:expr, $vp:expr) => {{ // If two vendor prefixes for the same property have different @@ -483,12 +479,8 @@ mod transition_handler_body { }; let prefix_to_iter = property_id.prefix().or_none(); // Expand vendor prefixes into multiple transitions. - // Zig used `inline for (VendorPrefix.FIELDS)` over packed-struct - // bool fields; with bitflags, iterate the individual flag bits. - // PERF(port): was comptime-unrolled inline-for — profile if it shows up on a hot path. - // Zig moved `transition` into the first matching iteration and - // deep-cloned for the rest; cloning for every iteration is - // observably identical (one extra clone of a small value). + // Cloning for every iteration costs at most one extra clone of a + // small value. for &prefix_flag in VendorPrefix::FIELDS { if prefix_to_iter.contains(prefix_flag) { let mut t = transition.deep_clone(arena); @@ -824,5 +816,4 @@ mod transition_handler_body { ) } - // ported from: src/css/properties/transition.zig } // mod transition_handler_body diff --git a/src/css/properties/ui.rs b/src/css/properties/ui.rs index 6501fe1d7805..f41664d74e3f 100644 --- a/src/css/properties/ui.rs +++ b/src/css/properties/ui.rs @@ -18,7 +18,6 @@ bitflags::bitflags! { const DARK = 1 << 1; /// Forbids the user agent from overriding the color scheme for the element. const ONLY = 1 << 2; - // Zig: __unused: u5 = 0 (padding — bitflags handles this implicitly) } } @@ -83,8 +82,7 @@ impl ColorScheme { } } -// Zig: `const Map = bun.ComptimeEnumMap(enum { normal, only, light, dark });` -// ≤8 entries → plain match on bytes (per PORTING.md). +// ≤8 entries → plain match on bytes. #[derive(Clone, Copy)] enum ColorSchemeKeyword { Normal, @@ -104,15 +102,13 @@ fn color_scheme_map_get(ident: &[u8]) -> Option { } /// A value for the [resize](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#resize) property. -// Zig source is `css.DefineEnumProperty(@compileError(css.todo_stuff.depth))` -// — intentionally unimplemented upstream, so this stays a unit placeholder. +// Intentionally unimplemented upstream, so this stays a unit placeholder. pub struct Resize; #[derive(Default)] pub struct ColorSchemeHandler; -// `define_var` needs no arena because `TokenList.v` is a std -// `Vec` (unlike Zig, which threaded `context.arena` through). +// `define_var` needs no arena because `TokenList.v` is a std `Vec`. impl ColorSchemeHandler { pub(crate) fn handle_property( &mut self, @@ -146,8 +142,7 @@ impl ColorSchemeHandler { dest.push(define_var(b"--buncss-dark", css::Token::Ident(b"initial"))); } } - // Zig pushed `property.deepClone(arena)`; ColorScheme is - // `Copy` (bitflags u8), so reconstruct the variant directly. + // ColorScheme is `Copy` (bitflags u8), so reconstruct the variant directly. dest.push(Property::ColorScheme(color_scheme)); true } @@ -166,7 +161,7 @@ impl ColorSchemeHandler { fn define_var(name: &'static [u8], value: css::Token) -> Property { // `name` is `&'static [u8]` because all call sites pass byte-string literals. // `TokenList.v` is `Vec` (std Vec — see custom.rs:320), so no arena - // threading is needed here despite Zig's `ArrayList(TokenOrValue)`. + // threading is needed here. Property::Custom(css::css_properties::custom::CustomProperty { name: css::css_properties::custom::CustomPropertyName::Custom(DashedIdent { v: name }), value: css::TokenList { @@ -175,4 +170,3 @@ fn define_var(name: &'static [u8], value: css::Token) -> Property { }) } -// ported from: src/css/properties/ui.zig diff --git a/src/css/rules/container.rs b/src/css/rules/container.rs index bc3a12292f5c..cda6e2fbd34b 100644 --- a/src/css/rules/container.rs +++ b/src/css/rules/container.rs @@ -65,11 +65,9 @@ pub enum ContainerSizeFeatureId { // `QueryFeature` requires `FeatureId: FeatureIdTrait` at the type // level, so this impl must be present for `ContainerSizeFeature` to resolve. -// `value_type` inlines the Zig `DeriveValueType` reflection; `to_css`/`from_str` -// delegate to `enum_property_util` (driven by the `EnumProperty` derive). +// `to_css`/`from_str` delegate to `enum_property_util` (driven by the +// `EnumProperty` derive). impl crate::media_query::FeatureIdTrait for ContainerSizeFeatureId { - // Zig: pub const valueType = css.DeriveValueType(@This(), ValueTypeMap).valueType; - // `DeriveValueType` is comptime reflection over `ValueTypeMap`; expanded inline. fn value_type(&self) -> MediaFeatureType { match self { Self::Width => MediaFeatureType::Length, @@ -103,8 +101,7 @@ pub enum StyleQuery { /// The operator for the conditions. operator: Operator, /// The conditions for the operator. - // PERF(port): was ArrayListUnmanaged fed input.arena() (parser arena); - // could use bun_alloc::ArenaVec<'bump, _> instead of global Vec — profile if hot. + // PERF: could use bun_alloc::ArenaVec<'bump, _> instead of global Vec — profile if hot. conditions: Vec, }, } @@ -151,8 +148,8 @@ impl QueryCondition for StyleQuery { let property_id = crate::properties::PropertyId::parse(input)?; input.expect_colon()?; input.skip_whitespace(); - // Zig threaded `(input.arena(), null)` here; the arena gets re-threaded - // as part of the crate-wide `'bump` lifetime work (see css_parser.rs). + // The arena gets re-threaded as part of the crate-wide `'bump` + // lifetime work (see css_parser.rs). let opts = css::ParserOptions::default(None); let feature = StyleQuery::Feature(Box::new(Property::parse(property_id, input, &opts)?)); let _ = input.try_parse(css::css_parser::parse_important); @@ -168,7 +165,6 @@ impl QueryCondition for StyleQuery { } } fn parse_style_query(input: &mut css::Parser) -> css::Result { - // Zig: `return .{ .err = input.newErrorForNextToken() }` Err(input.new_error_for_next_token()) } fn needs_parens(&self, parent_operator: Option, _targets: &css::Targets) -> bool { @@ -208,8 +204,7 @@ pub enum ContainerCondition { /// The operator for the conditions. operator: Operator, /// The conditions for the operator. - // PERF(port): was ArrayListUnmanaged fed input.arena() (parser arena); - // could use bun_alloc::ArenaVec<'bump, _> instead of global Vec — profile if hot. + // PERF: could use bun_alloc::ArenaVec<'bump, _> instead of global Vec — profile if hot. conditions: Vec, }, /// A style query. @@ -280,7 +275,6 @@ impl QueryCondition for ContainerCondition { } fn parse_style_query(input: &mut css::Parser) -> css::Result { use crate::media_query::QueryConditionFlags; - // Zig defined a local `Fns` struct with two callbacks; in Rust pass closures. input.parse_nested_block(|i| { if let Ok(res) = i.try_parse(|i2| { media_query::parse_query_condition::(i2, QueryConditionFlags::ALLOW_OR) @@ -355,7 +349,6 @@ impl ContainerRule { // Don't downlevel range syntax in container queries. let exclude = dest.targets.exclude; - // Zig: bun.bits.insert(css.targets.Features, &dest.targets.exclude, .media_queries); dest.targets.exclude.insert(css::Features::MEDIA_QUERIES); self.condition.to_css(dest)?; dest.targets.exclude = exclude; @@ -381,4 +374,3 @@ impl ContainerRule { } } -// ported from: src/css/rules/container.zig diff --git a/src/css/rules/counter_style.rs b/src/css/rules/counter_style.rs index 99fb0b5b6186..342dbfaa02d7 100644 --- a/src/css/rules/counter_style.rs +++ b/src/css/rules/counter_style.rs @@ -35,4 +35,3 @@ impl CounterStyleRule { } } -// ported from: src/css/rules/counter_style.zig diff --git a/src/css/rules/custom_media.rs b/src/css/rules/custom_media.rs index 07b63c6902cc..7007be420feb 100644 --- a/src/css/rules/custom_media.rs +++ b/src/css/rules/custom_media.rs @@ -35,4 +35,3 @@ impl CustomMediaRule { } } -// ported from: src/css/rules/custom_media.zig diff --git a/src/css/rules/document.rs b/src/css/rules/document.rs index 9ecafc0dc40a..e7d90fcab1f8 100644 --- a/src/css/rules/document.rs +++ b/src/css/rules/document.rs @@ -36,4 +36,3 @@ impl MozDocumentRule { } } -// ported from: src/css/rules/document.zig diff --git a/src/css/rules/font_face.rs b/src/css/rules/font_face.rs index ea6460115fe6..ef779bbda5d3 100644 --- a/src/css/rules/font_face.rs +++ b/src/css/rules/font_face.rs @@ -40,7 +40,6 @@ pub enum FontFaceProperty { impl FontFaceProperty { pub(crate) fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // Local helpers mirroring the Zig `Helpers.writeProperty` with `comptime multi: bool`. macro_rules! write_property_single { ($d:expr, $prop:expr, $value:expr) => {{ $d.write_str($prop)?; @@ -89,7 +88,6 @@ impl FontFaceProperty { } pub(crate) fn deep_clone(&self, arena: &bun_alloc::Arena) -> Self { - // Zig `css.implementDeepClone` field-walk, hand-expanded. match self { FontFaceProperty::Source(v) => { FontFaceProperty::Source(v.iter().map(|s| s.deep_clone(arena)).collect()) @@ -228,8 +226,7 @@ impl UnicodeRange { fn parse_tokens(input: &mut css::Parser) -> css::Result<()> { let tok = input.next_including_whitespace()?.clone(); - // Matches Zig `parseTokens` (font_face.zig): tag-only matches on - // `.dimension`/`.number` — payloads are never inspected. + // Tag-only matches on `Dimension`/`Number` — payloads are never inspected. match tok { css::Token::Dimension { .. } => return Self::parse_question_marks(input), css::Token::Number { .. } => { @@ -280,7 +277,6 @@ impl UnicodeRange { } } - // Zig `css.Maybe(UnicodeRange, void)` carries no error payload → `Option`. fn parse_concatenated(text_: &[u8]) -> Option { let mut text = if !text_.is_empty() && text_[0] == b'+' { &text_[1..] @@ -335,8 +331,8 @@ impl UnicodeRange { } fn consume_hex(text: &mut &[u8]) -> (u32, usize) { - // Cap at 8: caller validates `<= 6` post-hoc; the unbounded Zig original - // panic-overflows u32 in debug on >8 hex chars (malformed input). + // Cap at 8: caller validates `<= 6` post-hoc; an unbounded parse + // would overflow u32 on >8 hex chars (malformed input). let (value, n) = bun_core::fmt::parse_hex_prefix(text, 8); *text = &text[n..]; (value, n) @@ -502,7 +498,6 @@ impl Source { match input.try_parse(UrlSource::parse) { Ok(url) => return Ok(Source::Url(url)), Err(e) => { - // Zig: `e.kind == .basic and e.kind.basic == .at_rule_body_invalid` if matches!( e.kind, css::ParseErrorKind::basic(css::BasicParseErrorKind::at_rule_body_invalid) @@ -701,11 +696,6 @@ impl FontFaceRule { pub(crate) struct FontFaceDeclarationParser; -// Zig modeled `AtRuleParser` / `QualifiedRuleParser` / -// `DeclarationParser` / `RuleBodyItemParser` as nested namespaces with -// associated consts + fns. In Rust these are trait impls on -// `FontFaceDeclarationParser`. -// // blocked_on: css::{AtRuleParser,QualifiedRuleParser,DeclarationParser, // RuleBodyItemParser} trait signatures, properties::font::* + // properties::custom::CustomProperty, Size2D::parse, Parser surface, @@ -833,4 +823,3 @@ const _: () = { } }; -// ported from: src/css/rules/font_face.zig diff --git a/src/css/rules/font_palette_values.rs b/src/css/rules/font_palette_values.rs index 510f5df01738..b23337792b6a 100644 --- a/src/css/rules/font_palette_values.rs +++ b/src/css/rules/font_palette_values.rs @@ -53,7 +53,6 @@ impl FontPaletteValuesRule { while let Some(result) = parser.next() { if let Ok(decl) = result { properties.push(decl); - // PERF(port): was `append(input.arena(), decl) catch unreachable` } } @@ -231,9 +230,6 @@ impl BasePalette { pub(crate) struct FontPaletteValuesDeclarationParser {} -// Zig models these as nested namespace structs (`DeclarationParser`, -// `RuleBodyItemParser`, `AtRuleParser`, `QualifiedRuleParser`) duck-typed by -// `RuleBodyParser`. In Rust these are trait impls. const _: () = { use crate::css_properties::custom::{CustomProperty, CustomPropertyName}; use crate::css_properties::font::FontFamily; @@ -279,7 +275,6 @@ const _: () = { }} input.reset(&state); - // PERF(port): Zig passed `input.arena()` + `null` here. let opts = ParserOptions::default(None); let custom = CustomProperty::parse(CustomPropertyName::from_str(name), input, &opts)?; Ok(FontPaletteValuesProperty::Custom(custom)) @@ -349,4 +344,3 @@ const _: () = { } }; -// ported from: src/css/rules/font_palette_values.zig diff --git a/src/css/rules/import.rs b/src/css/rules/import.rs index b7750095c423..652cc1930a79 100644 --- a/src/css/rules/import.rs +++ b/src/css/rules/import.rs @@ -8,10 +8,8 @@ use crate::{PrintErr, Printer}; use bun_alloc::Arena; use bun_ast::ImportRecord; -/// Named replacement for the Zig anonymous `struct { v: ?LayerName }` used in -/// both `ImportConditions.layer` and `ImportRule.layer`. The two Zig anonymous -/// structs are layout-identical (the code `@ptrCast`s between the parents), so -/// we use a single Rust type for both. +/// Layer slot used in both `ImportConditions.layer` and `ImportRule.layer`. +/// The two uses are layout-identical, so a single type serves both. #[repr(C)] #[derive(Default)] pub struct Layer { @@ -164,7 +162,7 @@ impl ImportConditions { #[repr(C)] pub struct ImportRule { /// The url to import. - // TODO(port): arena lifetime — `&'bump [u8]` once crate-wide thread lands. + // TODO: arena lifetime — `&'bump [u8]` once crate-wide thread lands. pub url: &'static [u8], /// An optional cascade layer name, or `None` for an anonymous layer. @@ -227,7 +225,7 @@ impl ImportRule { pub fn conditions(&self) -> &ImportConditions { // SAFETY: ImportConditions is #[repr(C)] with fields {layer, supports, media} // laid out identically to the {layer, supports, media} field run of ImportRule - // (also #[repr(C)]). The Zig code relies on this same layout pun via @ptrCast. + // (also #[repr(C)]). // The pointer is derived from `self` (not `&self.layer`) so its provenance // covers all three fields — going through a field reference would narrow // provenance to just `layer` and make sibling-field reads UB under SB. @@ -282,9 +280,8 @@ impl ImportRule { } pub fn deep_clone(&self, bump: &Arena) -> Self { - // `css.implementDeepClone` field-walk. `url: &'static [u8]` - // is an arena-owned slice → identity copy (generics.zig "const - // strings" rule); `media` routes through `dc::media_list` until + // `url: &'static [u8]` is an arena-owned slice → identity copy; + // `media` routes through `dc::media_list` until // `MediaList` gains an arena-aware `deep_clone`. Self { url: self.url, @@ -320,7 +317,6 @@ impl ImportRule { dest.serialize_string(placeholder)?; if let Some(deps) = &mut dest.dependencies { - // PERF(port): was `catch unreachable` (alloc cannot fail under arena) deps.push(css::Dependency::Import(d)); } } else { @@ -373,4 +369,3 @@ const _: () = { // silence unused-import warnings on the gated bodies' deps -// ported from: src/css/rules/import.zig diff --git a/src/css/rules/keyframes.rs b/src/css/rules/keyframes.rs index 01cf23f8f45d..5515ee0c9754 100644 --- a/src/css/rules/keyframes.rs +++ b/src/css/rules/keyframes.rs @@ -13,8 +13,7 @@ use super::ArrayList; // ────────────────────────────────────────────────────────────────────────── /// ` = | ` -// Zig threaded the parser-input lifetime; this stores -// `&'static [u8]` per the rules/mod.rs `CssRule` lifetime-erasure +// Stores `&'static [u8]` per the rules/mod.rs `CssRule` lifetime-erasure // note (mod.rs:37-41). // TODO(refactor): re-thread `'bump` here. pub enum KeyframesName { @@ -24,14 +23,12 @@ pub enum KeyframesName { Custom(&'static [u8]), } -// Zig: `pub fn HashMap(comptime V: type) type { return std.ArrayHashMapUnmanaged(...) }` -// → a generic type alias keyed by `KeyframesName` with the custom hash/eq below. +// A generic type alias keyed by `KeyframesName` with the custom hash/eq below. pub type KeyframesNameHashMap = bun_collections::ArrayHashMap; impl Hash for KeyframesName { fn hash(&self, state: &mut H) { - // Matches Zig: hash only the underlying string bytes; variant tag does NOT - // participate (Zig's `hash` switches and calls `hashString` on the slice). + // Hash only the underlying string bytes; the variant tag does NOT participate. match self { KeyframesName::Ident(ident) => state.write(ident.v()), KeyframesName::Custom(s) => state.write(s), @@ -94,8 +91,7 @@ impl KeyframesName { impl KeyframesName { pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // `Custom(&'static [u8])` is an arena-owned slice → identity copy - // (generics.zig "const strings"). + // `Custom(&'static [u8])` is an arena-owned slice → identity copy. match self { Self::Ident(i) => Self::Ident(i.deep_clone(bump)), Self::Custom(s) => Self::Custom(s), @@ -176,13 +172,11 @@ impl KeyframeSelector { } // ─── KeyframeSelector parse ─────────────────────────────────────────────── -// blocked_on: css::derive_parse (DeriveParse comptime macro replacement). +// blocked_on: css::derive_parse (DeriveParse). impl KeyframeSelector { - // Zig: `pub const parse = css.DeriveParse(@This()).parse;` - // `DeriveParse` is a comptime type-generator producing `parse` from - // variant introspection. Expanded by hand here: try the tuple variant - // (`Percentage`) first, then fall back to keyword idents (`from`/`to`). + // Try the tuple variant (`Percentage`) first, then fall back to keyword + // idents (`from`/`to`). pub(crate) fn parse(input: &mut css::Parser) -> css::Result { if let Ok(p) = input.try_parse(Percentage::parse) { return Ok(KeyframeSelector::Percentage(p)); @@ -253,9 +247,8 @@ impl KeyframesRule { let mut first_rule = true; - // Zig: `inline for (.{ "webkit", "moz", "ms", "o", "none" }) |prefix_name|` with - // `@field(this.vendor_prefix, prefix_name)`. VendorPrefix is a packed-bool struct - // (→ `bitflags!`), so iterate the flag constants directly and use `.contains()`. + // VendorPrefix is `bitflags!`, so iterate the flag constants directly + // and use `.contains()`. const PREFIXES: [VendorPrefix; 5] = [ VendorPrefix::WEBKIT, VendorPrefix::MOZ, @@ -323,10 +316,6 @@ impl KeyframesRule { pub(crate) struct KeyframesListParser; -// In Zig these are nested `pub const DeclarationParser = struct { ... }` -// namespaces that the css parser duck-types via `@hasDecl`. In Rust they become -// trait impls on `KeyframesListParser`. -// // blocked_on: css::{DeclarationParser, AtRuleParser, QualifiedRuleParser, // RuleBodyItemParser} trait signatures (css_parser.rs round-5 surface), // Parser::parse_comma_separated, DeclarationBlock::parse, ParserOptions::default @@ -424,4 +413,3 @@ const _: () = { } }; -// ported from: src/css/rules/keyframes.zig diff --git a/src/css/rules/layer.rs b/src/css/rules/layer.rs index 9ff64e94215c..aaa9a89ad35b 100644 --- a/src/css/rules/layer.rs +++ b/src/css/rules/layer.rs @@ -12,22 +12,21 @@ use crate::{PrintErr, Printer, SmallList}; /// notation (`a.b.c`) creates sublayers. #[derive(Default)] pub struct LayerName { - // TODO(port): arena lifetime — Zig `[]const u8` segments borrow the parser - // arena. Thread `'bump` once `CssRuleList` re-gains its arena lifetime; - // until then segments are laundered through `&'static [u8]` like every - // other CSS slice in this crate. + // TODO: arena lifetime — segments borrow the parser arena. Thread + // `'bump` once `CssRuleList` re-gains its arena lifetime; until then + // segments are laundered through `&'static [u8]` like every other CSS + // slice in this crate. pub v: SmallList<&'static [u8], 1>, } // The inline hash/eql context is replaced by `Hash`/`PartialEq` impls on `LayerName` below. -// Iteration order is insertion order in both Zig's `ArrayHashMap` and this -// port (collections/array_hash_map.rs) regardless of hash function, so the -// hasher does not need to match Zig's wyhash. +// Iteration order is insertion order (collections/array_hash_map.rs) +// regardless of hash function. pub type LayerNameHashMap = ArrayHashMap; impl core::hash::Hash for LayerName { fn hash(&self, state: &mut H) { - // Mirrors the Zig ArrayHashMap context: Wyhash(seed=0) over each part's bytes. + // Hash each part's bytes. for part in self.v.slice() { state.write(part); } @@ -53,9 +52,9 @@ impl Clone for LayerName { impl LayerName { pub fn clone_with_import_records(&self, bump: &Arena, _: &mut Vec) -> Self { - // `[]const u8` segments are arena-borrowed, not owned, so the Zig - // `deepClone` here was a shallow `SmallList` copy. No import records to - // rewrite — layer names contain no URLs. + // Segments are arena-borrowed, not owned, so this is a shallow + // `SmallList` copy. No import records to rewrite — layer names + // contain no URLs. // // Allocate the segment-pointer slab from `bump` (not the global // allocator): callers store the result in arena-owned `ImportConditions` @@ -85,7 +84,6 @@ impl LayerName { parts.append(ident); loop { - // Zig: `const Fn = struct { pub fn tryParseFn(...) ... }` let try_parse_fn = |i: &mut css::css_parser::Parser<'_>| -> css::css_parser::CssResult<&'static [u8]> { let start_location = i.current_source_location(); @@ -118,9 +116,8 @@ impl LayerName { } pub fn deep_clone(&self, _bump: &Arena) -> Self { - // `css.implementDeepClone` — `[]const u8` segments are - // arena-owned (identity copy per generics.zig "const strings"). Same - // body as `clone_with_import_records` above. + // Segments are arena-owned (identity copy). Same body as + // `clone_with_import_records` above. LayerName { v: self.v.clone() } } } @@ -132,7 +129,6 @@ impl css::generics::ToCss for LayerName { } } -// Zig: `pub fn format(self, writer: *std.Io.Writer) !void` → `impl Display` impl fmt::Display for LayerName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut first = true; @@ -226,4 +222,3 @@ impl LayerStatementRule { } } -// ported from: src/css/rules/layer.zig diff --git a/src/css/rules/media.rs b/src/css/rules/media.rs index d7bc933f3497..4e51116f64d7 100644 --- a/src/css/rules/media.rs +++ b/src/css/rules/media.rs @@ -46,4 +46,3 @@ impl MediaRule { } } -// ported from: src/css/rules/media.zig diff --git a/src/css/rules/mod.rs b/src/css/rules/mod.rs index 9c67fcf19855..caa63d128475 100644 --- a/src/css/rules/mod.rs +++ b/src/css/rules/mod.rs @@ -4,7 +4,7 @@ use css::PrintErr; use css::Printer; use css::error::MinifyErr; -// PERF(port): heap-backed shim — Zig used arena-backed `std.ArrayListUnmanaged`. +// PERF: heap-backed shim. // TODO(refactor): thread `'bump` and replace this with `crate::generics::ArrayList<'bump, T>` // (= `bun_alloc::ArenaVec`) crate-wide in one pass. pub(super) type ArrayList = Vec; @@ -32,10 +32,8 @@ pub mod unknown; pub mod viewport; // ─── CssRule / CssRuleList ───────────────────────────────────────────────── -// Zig: pub fn CssRule(comptime Rule: type) type { return union(enum) { ... } } -// -// The original port threaded a `'bump` arena lifetime through every -// rule (matching Zig's `ArrayListUnmanaged`-backed AST). That cascades into +// An earlier iteration threaded a `'bump` arena lifetime through every +// rule. That cascades into // every leaf module signature; while those leaves are gated, `CssRule` is // kept lifetime-free here (the gated bodies re-introduce `'bump` when they // un-gate alongside `bumpalo::collections::Vec` storage). @@ -63,16 +61,13 @@ macro_rules! css_rule_variants { match self { $( CssRule::$Variant(x) => x.to_css(dest), )+ CssRule::Unknown(x) => x.to_css(dest), - // Zig: `.custom => |x| x.toCss(dest) catch return dest.addFmtError()`. - // - // The spec has TWO concrete `R` types — `DefaultAtRule` (whose - // `toCss` errors unconditionally, so Zig also lands in - // `addFmtError`) and `TailwindAtRule` (src/css/rules/tailwind.zig: - // 14-19), whose `toCss` succeeds and writes `@tailwind ;`. - // Tailwind parsing is comptime-disabled in both the spec and this - // port (`ENABLE_TAILWIND_PARSING = false`, `BundlerAtRule = - // DefaultAtRule` in css_parser.rs), so erroring here matches the - // spec for every `R` that is actually instantiated. If + // There are TWO concrete `R` types — `DefaultAtRule` (whose + // `to_css` errors unconditionally) and `TailwindAtRule`, + // whose `to_css` succeeds and writes `@tailwind ;`. + // Tailwind parsing is disabled (`ENABLE_TAILWIND_PARSING = + // false`, `BundlerAtRule = DefaultAtRule` in css_parser.rs), + // so erroring here is correct for every `R` that is + // actually instantiated. If // `TailwindAtRule` is ever un-gated, thread a `ToCss`-style bound // (or per-`R` vtable) so `Custom(x)` dispatches to // `x.to_css(dest)` and only the error path maps through @@ -84,8 +79,7 @@ macro_rules! css_rule_variants { } } - /// Zig: `css.implementDeepClone(@This(), this, arena)` — variant-wise - /// dispatch to each leaf rule's `deep_clone`. Hand-written (not + /// Variant-wise dispatch to each leaf rule's `deep_clone`. Hand-written (not /// `#[derive(DeepClone)]`) because the leaf payloads expose `deep_clone` /// as **inherent** methods rather than `DeepClone` trait impls; /// method-syntax dispatch here picks up either. @@ -153,17 +147,16 @@ css_rule_variants! { // `bun_alloc::ArenaVec<'bump, T>` (raw `NonNull` + `&Bump`) deep in // leaf rule payloads, both of which suppress the auto-traits. Those containers // uniquely own their storage exactly like `Vec`, and post-parse the tree is -// shared read-only across the bundler thread pool (mirrors Zig, which freely -// hands the arena-backed AST between threads). Thread-safety therefore follows -// `R`'s auto-traits. +// shared read-only across the bundler thread pool. Thread-safety therefore +// follows `R`'s auto-traits. unsafe impl Send for CssRule {} // SAFETY: see the `Send` impl above — uniquely-owned storage; `Sync` follows `R: Sync`. unsafe impl Sync for CssRule {} -/// Zig: pub fn CssRuleList(comptime AtRule: type) type { return struct { ... } } +/// Ordered list of CSS rules, generic over the custom at-rule type `R`. pub struct CssRuleList { - // PERF(port): was `bun_alloc::ArenaVec<'bump, CssRule<'bump, R>>`; - // arena threading restored when leaf rules un-gate. + // PERF: re-thread to `bun_alloc::ArenaVec<'bump, CssRule<'bump, R>>` + // when leaf rules un-gate. pub v: Vec>, } @@ -257,11 +250,10 @@ pub(super) mod dc { decl_block(this, unsafe { arena_static(bump) }) } - /// Empty `DeclarationBlock<'static>` — Zig spec writes `css.DeclarationBlock{}`. + /// Empty `DeclarationBlock<'static>`. /// - /// Exists so call-sites that need an empty block (rules.zig:363 - /// `nested_rule.declarations = .{}`) route through ONE centralized - /// erasure helper. Delete with `decl_block_static` once + /// Exists so call-sites that need an empty block route through ONE + /// centralized erasure helper. Delete with `decl_block_static` once /// `CssRule<'bump, R>` re-threads the arena lifetime. #[inline] pub(crate) fn decl_block_empty_static(bump: &Arena) -> crate::DeclarationBlock<'static> { @@ -321,8 +313,7 @@ pub(super) mod dc { } /// `Property::deep_clone` — routes to the real inherent - /// `Property::deep_clone` in properties_generated.rs (faithful per-variant - /// port of .zig:6307-6558). + /// `Property::deep_clone` in properties_generated.rs. #[inline] pub(crate) fn property( this: &crate::properties::Property, @@ -335,7 +326,6 @@ pub(super) mod dc { // `Location` is plain `Copy` data; the derive expands to field-wise // `u32::deep_clone` (identity). Doubles as the in-tree smoke test that the // `#[derive(DeepClone)]` proc-macro round-trips through a real CSS type. -// (The Zig `implementDeepClone` returns `this.*` for simple-copy types.) // ─── shared serialization helpers for leaf rules ────────────────────────── // Several leaf-rule `to_css` bodies bottom out on helpers whose canonical @@ -345,8 +335,8 @@ pub(super) mod dc { // 12 leaf rules can serialize for real. Once the upstream gates drop, callers // switch back and these are deleted. -/// Port of `DeclarationBlock.toCssBlock` (declaration.zig). The real impl is -/// gated in `declaration.rs`; `Property::to_css` is un-gated so the body is +/// `DeclarationBlock` block serialization. The real impl is gated in +/// `declaration.rs`; `Property::to_css` is un-gated so the body is /// trivially inlinable here. pub(super) fn decl_block_to_css( decls: &css::DeclarationBlock<'_>, @@ -358,7 +348,6 @@ pub(super) fn decl_block_to_css( let length = decls.len(); let mut i: usize = 0; - // Zig: `inline for (.{"declarations","important_declarations"}) |field|` — unrolled. for decl in decls.declarations.iter() { dest.newline()?; decl.to_css(dest, false)?; @@ -381,7 +370,7 @@ pub(super) fn decl_block_to_css( dest.write_char(b'}') } -/// Port of `VendorPrefix.toCss` (css_parser.zig:182). Lives here because the +/// `VendorPrefix` serialization. Lives here because the /// canonical `impl VendorPrefix` block in lib.rs hasn't grown a `to_css` yet /// and `rules/` is the only un-gated caller. #[inline] @@ -441,7 +430,7 @@ pub(super) fn dashed_ident_to_css( /// rule should be dropped. NOTE: `never_matches()` is a *drop condition*, not /// merely an optimization — omitting it diverges output (e.g. `@media not all /// { a{color:red} }` must be removed). `MediaList::never_matches` is un-gated, -/// so call it here to match the spec (`media.zig:19-23`). +/// so call it here. impl media::MediaRule { pub fn minify( &mut self, @@ -559,8 +548,7 @@ impl CssRuleList { for rule in self.v.iter_mut() { // NOTE Anytime you push `rule` into `rules`, set `moved_rule = true` - // so the source slot is replaced with `Ignored` (mirrors Zig's - // `defer if (moved_rule) rule.* = .ignored`). + // so the source slot is replaced with `Ignored`. let mut moved_rule = false; 'arm: { @@ -568,9 +556,7 @@ impl CssRuleList { CssRule::Keyframes(_keyframez) => { // KeyframesRule minify (unused-symbol drop + same-name // merge + vendor-prefix downlevel + fallbacks) is - // unimplemented in the Zig spec too — rules.zig leaves - // this arm as a debug-TODO fallthrough, so falling - // through here matches it. + // not implemented; fall through. } CssRule::CustomMedia(_) => { if context.custom_media.is_some() { @@ -597,7 +583,7 @@ impl CssRuleList { if let Some(CssRule::Supports(last_rule)) = rules.last_mut() && last_rule.condition.eql(&supp.condition) { - // Zig drops the duplicate-condition rule outright. + // Drop the duplicate-condition rule outright. break 'arm; } supp.minify(context, parent_is_unused)?; @@ -621,8 +607,7 @@ impl CssRuleList { } } CssRule::LayerStatement(_lay) => { - // LayerStatementRule minify — Zig debug-TODO fallthrough - // (rules.zig); falling through matches it. + // LayerStatementRule minify is not implemented; fall through. } CssRule::MozDocument(doc) => { // See `Container` above: recurse so nested style rules @@ -647,7 +632,7 @@ impl CssRuleList { break 'arm; } } - CssRule::CounterStyle(_) => { /* Zig debug-TODO fallthrough */ } + CssRule::CounterStyle(_) => {} CssRule::Scope(scpe) => { // See `Container` above: recurse so nested style rules // count against the selector-expansion cap. @@ -675,8 +660,8 @@ impl CssRuleList { // count against the selector-expansion cap. rl.rules.minify(context, parent_is_unused)?; } - CssRule::FontPaletteValues(_) => { /* Zig debug-TODO fallthrough */ } - CssRule::Property(_) => { /* Zig debug-TODO fallthrough */ } + CssRule::FontPaletteValues(_) => {} + CssRule::Property(_) => {} _ => {} } @@ -698,13 +683,11 @@ impl CssRuleList { } } - // Zig: css.deepDeinit(CssRule(AtRule), context.arena, &this.v); - // Rust drops the old Vec on assignment. + // The old Vec is dropped on assignment. self.v = rules; Ok(()) } - /// Zig: `css.implementDeepClone(@This(), this, arena)`. pub fn deep_clone<'bump>(&self, bump: &'bump bun_alloc::Arena) -> Self where R: css::generics::DeepClone<'bump>, @@ -845,8 +828,8 @@ fn minify_style_arm css::generics::DeepClone<'b>>( { let mut rulesss = CssRuleList::::default(); core::mem::swap(&mut sty.rules, &mut rulesss); - // Zig: `.declarations = css.DeclarationBlock{}` — empty block. Route - // through the centralized `'bump`-erasure helper instead of fabricating + // Empty block: route through the centralized `'bump`-erasure helper + // instead of fabricating // `&'static Arena` here (PORTING.md §Forbidden). Some(style::StyleRule { selectors: sty.selectors.deep_clone(), @@ -913,9 +896,8 @@ fn minify_style_arm css::generics::DeepClone<'b>>( /// duplicates. It stores an index into the live `rules` Vec plus a /// pre-computed hash for fast lookups. /// -/// NOTE: the Zig spec (`rules.zig:StyleRuleKey`) additionally stores -/// `list: *const ArrayList(CssRule(R))` and dereferences it inside `eql()`. -/// That pattern is unsound in Rust under Stacked/Tree Borrows — keys persist +/// NOTE: storing a `*const Vec>` in the key and dereferencing it +/// during equality would be unsound under Stacked/Tree Borrows — keys persist /// in the dedup map across iterations of `minify_style_arm`, and between /// iterations the same `Vec` is written through fresh `&mut` reborrows /// (`rules.push`, `rules[i] = Ignored`), invalidating any previously-derived @@ -938,8 +920,7 @@ impl StyleRuleKey { } } -/// Dedup table for [`StyleRuleKey`]s — the Rust-side equivalent of Zig's -/// `StyleRuleKey(R).HashMap(usize)`. +/// Dedup table for [`StyleRuleKey`]s. /// /// Buckets keyed by the pre-computed `StyleRule::hash_key()` hold indices into /// the caller's `rules` Vec; equality (`StyleRule::is_duplicate`) is evaluated @@ -951,8 +932,8 @@ pub(crate) struct StyleRuleKeyMap { } impl StyleRuleKeyMap { - /// Zig `style_rules.fetchSwapRemove(key)` — find and remove an earlier - /// index whose rule `is_duplicate` of `rules[key.index]`. + /// Find and remove an earlier index whose rule `is_duplicate` of + /// `rules[key.index]`. pub(crate) fn remove_duplicate( &mut self, rules: &[CssRule], @@ -963,8 +944,7 @@ impl StyleRuleKeyMap { return None; }; let pos = bucket.iter().position(|&other_idx| { - // Mirrors `StyleRuleKey.eql` from rules.zig: bounds-check + .style - // tag-check + `isDuplicate`. + // Bounds-check + Style tag-check + `is_duplicate`. match rules.get(other_idx) { Some(CssRule::Style(other_rule)) => rule.is_duplicate(other_rule), _ => false, @@ -973,7 +953,7 @@ impl StyleRuleKeyMap { Some(bucket.swap_remove(pos)) } - /// Zig `style_rules.put(ctx.arena, key, idx)`. + /// Record the rule's index under its style-rule key for later dedup lookups. pub(crate) fn insert(&mut self, key: StyleRuleKey) { self.buckets.entry(key.hash).or_default().push(key.index); } @@ -1043,8 +1023,8 @@ pub(crate) fn merge_style_rules( if sty.is_compatible(context.targets) && last_style_rule.is_compatible(context.targets) { let moved = core::mem::take(&mut sty.selectors.v); // `reserve` (not `ensure_total_capacity`) so capacity grows - // super-linearly across repeated merges — matches .zig - // `appendSlice` and keeps the N-way merge amortized O(N). + // super-linearly across repeated merges, keeping the N-way merge + // amortized O(N). last_style_rule.selectors.v.reserve(moved.len()); for sel in moved { last_style_rule.selectors.v.append_assume_capacity(sel); @@ -1064,9 +1044,8 @@ pub(crate) fn merge_style_rules( // ─── Location / StyleContext / MinifyContext ────────────────────────────── -// Zig spec: `css.Location = css_rules.Location` is a TYPE ALIAS (one nominal -// type). Re-export the crate-root struct so `css_rules::Location` and -// `crate::Location` are interchangeable. +// Re-export the crate-root struct so `css_rules::Location` and +// `crate::Location` are interchangeable (one nominal type). pub use crate::Location; /// Printer's nesting cursor — linked list of parent selector lists used to @@ -1093,8 +1072,7 @@ pub const MAX_SELECTOR_EXPANSION: u32 = 65_536; /// Per-stylesheet minification state threaded through `CssRuleList::minify` /// and every leaf rule's `minify`. /// -/// Zig carried `arena: std.mem.Allocator` for the AST arena; -/// here that is `&'a Arena` (bumpalo). All sub-allocations during minify go +/// All sub-allocations during minify go /// through it so the whole transformed tree is bulk-freed with the arena. // Split lifetimes — `'bump` is the parser arena (long), `'a` is the // per-minify borrow scope (short). `&'a mut DeclarationHandler<'a>` would force @@ -1108,7 +1086,6 @@ pub struct MinifyContext<'a, 'bump> { pub important_handler: &'a mut css::DeclarationHandler<'bump>, pub handler_context: css::PropertyHandlerContext<'bump>, /// Class/id names known to be unused (tree-shaking input). - // Zig: `*const std.StringArrayHashMapUnmanaged(void)`. // `selector::is_unused` currently borrows `&ArrayHashMap<&[u8], ()>`; the // owning `MinifyOptions` stores `Box<[u8]>` keys — reconcile when // `style.rs::minify` un-gates (single key type, `Borrow<[u8]>` lookup). @@ -1118,7 +1095,7 @@ pub struct MinifyContext<'a, 'bump> { Option, custom_media::CustomMediaRule>>, pub extra: &'a css::StylesheetExtra, pub css_modules: bool, - /// First minification error encountered (Zig surfaced this out-of-band). + /// First minification error encountered (surfaced out-of-band). pub err: Option, /// How many copies of the current rule's selectors compiling the enclosing /// nesting for the targets will produce — the product of the enclosing @@ -1129,4 +1106,3 @@ pub struct MinifyContext<'a, 'bump> { pub selector_expansion_total: u32, } -// ported from: src/css/rules/rules.zig diff --git a/src/css/rules/namespace.rs b/src/css/rules/namespace.rs index a9501acf6855..bce29320dd39 100644 --- a/src/css/rules/namespace.rs +++ b/src/css/rules/namespace.rs @@ -29,9 +29,8 @@ impl NamespaceRule { } pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // `CssString` is `*const [u8]` (arena-owned slice → identity copy per - // generics.zig "const strings" rule); `Ident::deep_clone` is the same - // identity copy. + // `CssString` is `*const [u8]` (arena-owned slice → identity copy); + // `Ident::deep_clone` is the same identity copy. Self { prefix: self.prefix.as_ref().map(|p| p.deep_clone(bump)), url: self.url, @@ -40,4 +39,3 @@ impl NamespaceRule { } } -// ported from: src/css/rules/namespace.zig diff --git a/src/css/rules/nesting.rs b/src/css/rules/nesting.rs index 8e7dab0a20c1..3e900c2d4e98 100644 --- a/src/css/rules/nesting.rs +++ b/src/css/rules/nesting.rs @@ -35,4 +35,3 @@ impl NestingRule { } } -// ported from: src/css/rules/nesting.zig diff --git a/src/css/rules/page.rs b/src/css/rules/page.rs index d3a9321ccea5..d674d5c370b0 100644 --- a/src/css/rules/page.rs +++ b/src/css/rules/page.rs @@ -12,7 +12,7 @@ pub struct PageSelector { /// An optional named page type. // Arena-owned slice borrowed from parser input; `&'static` per the // rules/mod.rs lifetime-erasure note. - // TODO(port): re-thread `'bump`. + // TODO: re-thread `'bump`. pub name: Option<&'static [u8]>, /// A list of page pseudo classes. pub pseudo_classes: ArrayList, @@ -253,7 +253,7 @@ pub enum PagePseudoClass { impl PagePseudoClass { #[inline] pub(crate) fn deep_clone(self, _bump: &bun_alloc::Arena) -> Self { - // `Copy` enum (generics.zig "simple copy types" → identity). + // `Copy` enum → identity. self } } @@ -406,4 +406,3 @@ const _: () = { } }; -// ported from: src/css/rules/page.zig diff --git a/src/css/rules/property.rs b/src/css/rules/property.rs index cb554c99b837..e776bd01aa45 100644 --- a/src/css/rules/property.rs +++ b/src/css/rules/property.rs @@ -266,4 +266,3 @@ const _: () = { } }; -// ported from: src/css/rules/property.zig diff --git a/src/css/rules/scope.rs b/src/css/rules/scope.rs index d46de1d92e1c..054add52b1fe 100644 --- a/src/css/rules/scope.rs +++ b/src/css/rules/scope.rs @@ -48,7 +48,6 @@ impl ScopeRule { // is treated as an ancestor of scope end. // https://drafts.csswg.org/css-nesting/#nesting-at-scope if let Some(scope_start) = &self.scope_start { - // Zig passed an anon-struct fn pointer; the Rust // `Printer::with_context` carries the captured state as the // first closure arg (no `&self` capture across `&mut dest`). dest.with_context( @@ -103,4 +102,3 @@ impl ScopeRule { } } -// ported from: src/css/rules/scope.zig diff --git a/src/css/rules/starting_style.rs b/src/css/rules/starting_style.rs index 7f93a4af8159..61b72536df6f 100644 --- a/src/css/rules/starting_style.rs +++ b/src/css/rules/starting_style.rs @@ -34,4 +34,3 @@ impl StartingStyleRule { } } -// ported from: src/css/rules/starting_style.zig diff --git a/src/css/rules/style.rs b/src/css/rules/style.rs index 72541aa5d72a..37a7c155598e 100644 --- a/src/css/rules/style.rs +++ b/src/css/rules/style.rs @@ -5,7 +5,7 @@ use crate::error::MinifyErr; use crate::selectors::selector; use crate::{PrintErr, Printer, VendorPrefix}; -// `fn StyleRule(comptime R: type) type { return struct {...} }` → generic struct. +// `StyleRule` is generic over the custom at-rule type `R`. // // `DeclarationBlock<'bump>` borrows the parser arena (bumpalo Vecs). // Threading `'bump` here cascades into `CssRule<'bump, R>` / `CssRuleList<'bump, R>` @@ -36,7 +36,7 @@ impl StyleRule { /// Returns a hash of this rule for use when deduplicating. /// Includes the selectors and properties. pub fn hash_key(&self) -> u64 { - // std.hash.Wyhash.init(0) — same algorithm as bun.hash + // Wyhash seeded with 0 — same algorithm as bun.hash let mut hasher = bun_wyhash::Wyhash::init(0); self.selectors.hash(&mut hasher); // Inlined `DeclarationBlock::hash_property_ids`: hash just the u16 @@ -190,8 +190,8 @@ impl StyleRule { dest.indent(); let mut i: usize = 0; - // Zig: inline for (.{"declarations", "important_declarations"}) — @field reflection. - // Unrolled into a pair of (slice, important) tuples; same iteration order. + // A pair of (slice, important) tuples; declarations first, then + // important declarations. let decls_groups: [(&[Property], bool); 2] = [ (self.declarations.declarations.as_slice(), false), (self.declarations.important_declarations.as_slice(), true), @@ -209,10 +209,8 @@ impl StyleRule { } if dest.css_module.is_some() { - // Reshaped for borrowck — Zig - // `if (dest.css_module) |*css_module| - // css_module.handleComposes(dest, ...)` overlaps - // `&mut dest.css_module` with `&mut *dest`. Move the + // `handle_composes` needs `&mut dest` while the + // module also lives in `dest.css_module`. Move the // module out for the duration of the call, then put // it back before any `dest.new_error` early return. let mut cm = dest.css_module.take(); @@ -248,7 +246,6 @@ impl StyleRule { } } - // Zig: local `Helpers` struct with two fns. Rust: nested fn items (no capture needed). fn helpers_newline( self_: &StyleRule, d: &mut Printer, @@ -301,8 +298,7 @@ impl StyleRule { helpers_newline(self, dest, supports_nesting, len)?; } dest.skip_prefixed_nested_rules = skip_prefixed_nested; - // Zig: dest.withContext(&this.selectors, this, struct { fn toCss(...) }.toCss) - // Rust `with_context` keeps the (closure-data, fn) split so the + // `with_context` keeps the (closure-data, fn) split so the // `Printer` reborrow lives only inside `func`. let result = dest.with_context(&self.selectors, &self.rules, |rules, d| rules.to_css(d)); @@ -475,13 +471,12 @@ impl StyleRule { .declarations .len() .min(other.declarations.declarations.len()); - // for (a, b) |*a, *b| → zip; Zig asserts equal length but here len is @min so truncation is intended. + // len is the min of the two lengths, so truncation is intended. for (a, b) in self.declarations.declarations[..len] .iter() .zip(&other.declarations.declarations[..len]) { - // Zig `PropertyId.eql` == tag+prefix compare; that's - // exactly the `PartialEq` impl on `PropertyId`. + // `PropertyId`'s `PartialEq` is a tag+prefix compare. if a.property_id() != b.property_id() { break 'brk false; } @@ -510,7 +505,7 @@ impl StyleRule { where R: crate::generics::DeepClone<'bump>, { - // css is an AST crate (PORTING.md §Allocators): std.mem.Allocator → &'bump Bump, threaded. + // css is an AST crate (PORTING.md §Allocators): the allocator is &'bump Bump, threaded. // `declarations` routes through `dc::decl_block` until // `DeclarationBlock::deep_clone` un-gates (declaration.rs — bottoms out // on `Property: DeepClone`). @@ -524,4 +519,3 @@ impl StyleRule { } } -// ported from: src/css/rules/style.zig diff --git a/src/css/rules/supports.rs b/src/css/rules/supports.rs index 51cbfa44ab3c..73a86d80af08 100644 --- a/src/css/rules/supports.rs +++ b/src/css/rules/supports.rs @@ -275,9 +275,8 @@ impl SupportsCondition { let mut seen_declarations: ArrayHashMap = ArrayHashMap::new(); loop { - // Reshaped for borrowck — Zig threaded `*?i32` through a - // local `Closure` struct (LIFETIMES.tsv: BORROW_PARAM); a Rust closure - // capturing `&mut expected_type` is the direct equivalent. + // A closure capturing `&mut expected_type` threads the expected + // type through the parse attempt. let _condition = input.try_parse(|i: &mut css::Parser| -> css::Result { let location = i.current_source_location(); @@ -408,14 +407,14 @@ impl SupportsCondition { } } -// Dedup key for `@supports` declaration conditions; the manual Hash/PartialEq -// below match the Zig hash-map context (wrapping_add of string hash and enum int). +// Dedup key for `@supports` declaration conditions; manual Hash/PartialEq +// (wrapping_add of string hash and enum int). struct SeenDeclKey(PropertyId, &'static [u8]); impl core::hash::Hash for SeenDeclKey { fn hash(&self, state: &mut H) { - // wyhash of the value bytes +% the property-id tag, mirroring the Zig - // hash-map context. `hash_string` returns u32 directly — no narrowing cast. + // wyhash of the value bytes +% the property-id tag. + // `hash_string` returns u32 directly — no narrowing cast. let h: u32 = bun_collections::array_hash_map::hash_string(self.1); state.write_u32(h.wrapping_add(self.0.tag() as u32)); } @@ -423,7 +422,7 @@ impl core::hash::Hash for SeenDeclKey { impl PartialEq for SeenDeclKey { fn eq(&self, other: &Self) -> bool { - // Zig: tag-only equality + slice byte equality. + // Tag-only equality + slice byte equality. self.0.tag() as u16 == other.0.tag() as u16 && self.1 == other.1 } } @@ -487,4 +486,3 @@ impl SupportsRule { } } -// ported from: src/css/rules/supports.zig diff --git a/src/css/rules/tailwind.rs b/src/css/rules/tailwind.rs index 78686113eb48..099e39ca01be 100644 --- a/src/css/rules/tailwind.rs +++ b/src/css/rules/tailwind.rs @@ -47,4 +47,3 @@ pub enum TailwindStyleName { Variants, } -// ported from: src/css/rules/tailwind.zig diff --git a/src/css/rules/unknown.rs b/src/css/rules/unknown.rs index cccc86d93d49..e7faa743aa5e 100644 --- a/src/css/rules/unknown.rs +++ b/src/css/rules/unknown.rs @@ -5,7 +5,7 @@ use crate::{PrintErr, Printer}; /// An unknown at-rule, stored as raw tokens. pub struct UnknownAtRule { /// The name of the at-rule (without the @). - // TODO(port): arena lifetime — Zig `[]const u8` backed by parser arena. + // TODO: arena lifetime — slice backed by parser arena. pub name: &'static [u8], /// The prelude of the rule. pub prelude: TokenList, @@ -40,8 +40,8 @@ impl UnknownAtRule { pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { use crate::generics::DeepClone as _; - // `name` is an arena-owned slice → identity copy (generics.zig - // "const strings" rule); `TokenList` carries `#[derive(DeepClone)]`. + // `name` is an arena-owned slice → identity copy; `TokenList` + // carries `#[derive(DeepClone)]`. Self { name: self.name, prelude: self.prelude.deep_clone(bump), @@ -51,4 +51,3 @@ impl UnknownAtRule { } } -// ported from: src/css/rules/unknown.zig diff --git a/src/css/rules/viewport.rs b/src/css/rules/viewport.rs index 9a3137fefb2c..a4558f2230af 100644 --- a/src/css/rules/viewport.rs +++ b/src/css/rules/viewport.rs @@ -35,4 +35,3 @@ impl ViewportRule { } } -// ported from: src/css/rules/viewport.zig diff --git a/src/css/selectors/builder.rs b/src/css/selectors/builder.rs index 8b7b7ba645fc..745fc3412c69 100644 --- a/src/css/selectors/builder.rs +++ b/src/css/selectors/builder.rs @@ -218,4 +218,3 @@ pub(crate) fn split_from_end(s: &[T], at: usize) -> (&[T], &[T]) { (&s[0..midpoint], &s[midpoint..]) } -// ported from: src/css/selectors/builder.zig diff --git a/src/css/selectors/mod.rs b/src/css/selectors/mod.rs index 4f5433deff09..6e80b609962c 100644 --- a/src/css/selectors/mod.rs +++ b/src/css/selectors/mod.rs @@ -13,8 +13,7 @@ //! the two serializer namespaces (`serialize::*`, `tocss_servo::*`). //! `builder.rs` carries `SelectorBuilder`. //! -//! The `impl_::Selectors` marker (Rust trait-based reshaping of Zig's -//! `selector.impl.Selectors.SelectorImpl` type-alias namespace) lives here in +//! The `impl_::Selectors` marker lives here in //! the hub so the parser↔selector cycle has a single anchor; both files reach //! it via `bun_css::selector::impl_` / `super::impl_`. @@ -25,9 +24,9 @@ pub mod selector; pub use parser::{Component, PseudoClass, PseudoElement, Selector, SelectorList}; -/// Our implementation of the `SelectorImpl` interface — the Rust-shaped -/// equivalent of Zig's `selector.impl.Selectors`. Defined in the hub (not in -/// `selector.rs`) to break the parser↔selector dependency cycle: `parser.rs` +/// Our implementation of the `SelectorImpl` interface. Defined in the hub +/// (not in `selector.rs`) to break the parser↔selector dependency cycle: +/// `parser.rs` /// needs `impl_::Selectors` to instantiate `Component`/`Selector`/ /// `SelectorList`, and `selector.rs` needs those instantiations. pub mod impl_ { @@ -48,7 +47,7 @@ pub mod impl_ { type LocalIdentifier = IdentOrRef; type LocalName = Ident; type NamespacePrefix = Ident; - // TODO(port): lifetime — Zig `[]const u8` type alias borrowing input. + // TODO: lifetime — should borrow the parser input. type NamespaceUrl = &'static [u8]; type BorrowedNamespaceUrl = &'static [u8]; type BorrowedLocalName = Ident; diff --git a/src/css/selectors/parser.rs b/src/css/selectors/parser.rs index 931e8dcf6702..f82080fb6256 100644 --- a/src/css/selectors/parser.rs +++ b/src/css/selectors/parser.rs @@ -1,4 +1,4 @@ -//! CSS selector parser — ported from `src/css/selectors/parser.zig`. +//! CSS selector parser. //! Originally derived from servo/lightningcss selector parsing. use core::fmt; @@ -23,8 +23,7 @@ pub use bun_css::Printer as PrinterRe; // re-export parity (Printer/PrintErr wer /// `css::Result` — the CSS parser result type (`Ok(T)` / `Err(css::ParseError)`). type CResult = css::Result; -// Arena lifetimes: the Zig code threads `parser.arena` / `input.arena()` (a bump -// arena) through every allocation; the Rust port uses `Vec`/`Box` and a `Str` +// Arena lifetimes: this module uses `Vec`/`Box` and a `Str` // alias for source-borrowed byte slices until the crate-wide `'bump` re-threading // (`bun_alloc::ArenaVec<'bump, T>` / `&'bump [u8]` per PORTING.md §Allocators // (AST crates)) lands. @@ -37,8 +36,7 @@ type CResult = css::Result; type Str = &'static [u8]; // arena-backed `[]const u8` source slice // ─── Protocol traits ───────────────────────────────────────────────────────── -// Zig's `implementEql` / `implementHash` / `implementDeepClone` are comptime -// field/variant reflection over `@typeInfo(T)` — in Rust this is the body of +// `eql` / `hash` / `deep_clone` are field/variant reflection, provided by // `#[derive(CssEql, CssHash, DeepClone)]` (`bun_css_derive`). Non-generic // grammar types below carry the derive directly; the ``- // generic types hand-write bodies (the derive's `where Impl: CssEql` bound is @@ -50,7 +48,7 @@ use css::generics::{CssEql, CssHash}; /// Drain a `SmallList` into a `Box<[T]>`. `SmallList` has no `into_vec`; /// this bitwise-moves each element out and `set_len(0)`s the source so its -/// `Drop` doesn't double-free. Mirrors Zig `toOwnedSlice`. +/// `Drop` doesn't double-free. fn small_list_into_box(mut sl: SmallList) -> Box<[T]> { let len = sl.len() as usize; let mut v: Vec = Vec::with_capacity(len); @@ -67,13 +65,11 @@ fn small_list_into_box(mut sl: SmallList) -> Box<[T]> { } /// Allocate an ASCII-lowercased copy of `name` in the parse-session bump arena. -/// Zig used `parser.arena().alloc(u8, n)` (the bump arena owns the buffer -/// for the parse session and frees it on arena reset). Returns a raw arena +/// Returns a raw arena /// pointer (`*const [u8]`) — `Ident.v`'s field type — so we don't fabricate a /// `'static` lifetime (PORTING.md §Forbidden: never `Box::leak` to satisfy /// `&'static`). Re-threading `&'bump Bump` would widen `Ident.v` to /// `&'bump [u8]`. -// PERF(port): was arena alloc — profile if it shows up on a hot path. #[inline] fn arena_lowercase(bump: &Bump, name: &[u8]) -> *const [u8] { let buf = bump.alloc_slice_fill_copy(name.len(), 0u8); @@ -123,11 +119,10 @@ pub const SELECTOR_WHITESPACE: &[u8] = &[b' ', b'\t', b'\n', b'\r', 0x0C]; /// Compile-time check that `T` satisfies the `SelectorImpl` trait shape. /// In Rust this is expressed as a trait bound; this fn is kept for diff parity. pub fn valid_selector_impl() { - // Zig used `_ = T.SelectorImpl.X;` to force decl resolution; in Rust the trait - // bound `T: SelectorImpl` is the check. + // The trait bound `T: SelectorImpl` is the check. } -/// The `SelectorImpl` shape (Zig validated via `ValidSelectorImpl`). Implemented +/// The `SelectorImpl` shape. Implemented /// by `impl_::Selectors` in `bun_css::selector::impl_`. // `PartialEq + Clone` bounds dropped — the concrete assoc types // (`values::ident::{Ident,IdentOrRef}`, `*const [u8]`) implement structural @@ -152,9 +147,8 @@ pub trait SelectorImpl: Sized { /// Constrained `SelectorImpl` with the concrete assoc-type bundle Bun uses. /// -/// In Zig the `parse_*` functions were `comptime Impl: type` generics -/// but every body assumed the concrete `selector.impl.Selectors` shapes (it was -/// the only instantiation). Rust can't see through the open `Impl::LocalName` +/// Every `parse_*` body assumes the concrete `selector.impl.Selectors` shapes +/// (the only instantiation). Rust can't see through the open `Impl::LocalName` /// to `Ident`, so the parse functions bound on this sub-trait instead — the /// associated-type equality clauses make `Impl::LocalName == Ident` etc. /// visible to the body without monomorphizing the signature. @@ -559,10 +553,9 @@ fn compute_simple_selector_specificity( // Does not affect specificity } C::Nesting => { - // No specificity contribution here, matching the Zig source — - // which leaves this as a bare open TODO upstream. Whether `&` - // should contribute specificity at this point is unresolved - // there; nesting substitution happens at print time, after + // No specificity contribution here. Whether `&` should + // contribute specificity at this point is unresolved; + // nesting substitution happens at print time, after // specificity is computed. } } @@ -740,7 +733,6 @@ fn parse_compound_selector( } if parse_type_selector::(parser, input, *state, builder).is_ok() { - // Note: Zig `.asValue()` here means "if Ok"; the bool result is unused. empty = false; } @@ -878,7 +870,6 @@ fn parse_relative_selector( selector .components .push(GenericComponent::Combinator(wombo_combo)); - // PERF(port): was assume_capacity (catch unreachable on arena) selector.components.push(scope); } @@ -888,14 +879,12 @@ fn parse_relative_selector( /// Compile-time validation of the `SelectorParser` shape. In Rust the methods are /// inherent on `SelectorParser`; this is a no-op kept for diff parity. pub fn valid_selector_parser() { - // Zig: `_ = T.SelectorParser.parseSlotted;` etc. — structural duck-typing check. - // In Rust these are inherent methods on `SelectorParser`; nothing to validate at runtime. + // These are inherent methods on `SelectorParser`; nothing to validate at runtime. } /// The [:dir()](https://drafts.csswg.org/selectors-4/#the-dir-pseudo) pseudo class. -// Re-export of the canonical `{ltr, rtl}` enum from `properties::text` — both Zig -// specs (selectors/parser.zig:700, properties/text.zig:251) define the same -// `DefineEnumProperty` shape, so the Rust port shares one definition. The +// Re-export of the canonical `{ltr, rtl}` enum from `properties::text` — the +// selector and property grammars share one definition. The // `#[derive(DefineEnumProperty)]` on the canonical provides `parse`/`to_css`/ // `as_str`; `CssEql`/`CssHash`/`DeepClone` come from `generics::inherent_bridge`. pub use css::css_properties::text::Direction; @@ -911,7 +900,6 @@ pub enum PseudoClass { Lang { /// A list of language codes. languages: Vec, - // PERF(port): was arena ArrayList — profile if it shows up on a hot path. }, /// The [:dir()](https://drafts.csswg.org/selectors-4/#the-dir-pseudo) pseudo class. Dir { @@ -1082,11 +1070,9 @@ impl PseudoClass { } pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // Zig builds a fresh `Printer` over an allocating writer, calls - // `serialize::serializePseudoClass`, then writes the buffer to `dest`. - // The buffered indirection only matters for length-dependent - // minification decisions made by callers (none here), so write - // directly to `dest`. + // A buffered intermediate `Printer` would only matter for + // length-dependent minification decisions made by callers (none + // here), so write directly to `dest`. serialize::serialize_pseudo_class(self, dest, None) } @@ -1209,14 +1195,12 @@ impl WebKitScrollbarPseudoElement { pub struct SelectorParser<'a> { pub is_nesting_allowed: bool, pub options: &'a ParserOptions<'a>, - // `arena: Allocator` dropped — arena threaded via `input.arena()` in Zig. - // PERF(port): was arena bulk-free — re-thread `&'bump Bump` to restore. + // PERF: re-thread `&'bump Bump` here to restore arena allocation. } -// Zig: `pub const Impl = impl_.Selectors;` lived inside the struct for -// `ValidSelectorParser`'s comptime decl-probe. Rust inherent associated types -// are unstable (rust#8995); the equivalent contract is the `BunSelectorImpl` -// blanket impl above, so expose the alias at module scope instead. +// Rust inherent associated types are unstable (rust#8995); the equivalent +// contract is the `BunSelectorImpl` blanket impl above, so expose the alias +// at module scope instead. pub type SelectorParserImpl = impl_::Selectors; impl<'a> SelectorParser<'a> { @@ -1262,13 +1246,11 @@ impl<'a> SelectorParser<'a> { name: Str, input: &mut CssParser, ) -> CResult { - // Spec parity: parser.zig:1054 uses `ComptimeEnumMap.get(name)` which is - // CASE-SENSITIVE (`ComptimeStringMap.get`, not `getAnyCase`/`getASCIIICaseInsensitive`). - // `::CUE(..)` / `::View-Transition-Group(..)` therefore fall through to - // `CustomFunction` in the spec — match that here by looking up `name` - // verbatim with no case folding. + // This lookup is intentionally CASE-SENSITIVE: `::CUE(..)` / + // `::View-Transition-Group(..)` fall through to `CustomFunction`, + // so look up `name` verbatim with no case folding. // - // PERF(port): 6 entries with near-unique lengths (3/10/19/19/21/26) — + // PERF: 6 entries with near-unique lengths (3/10/19/19/21/26) — // a length-gated `match` rejects the overwhelmingly-common miss path // (unknown `::-webkit-foo(...)` etc.) on a single `usize` compare, // versus phf's hash + 2 table loads + slice compare. Only len==19 has @@ -1480,7 +1462,6 @@ impl<'a> SelectorParser<'a> { } /// Case-insensitive lookup table for `parse_non_ts_pseudo_class`. -/// Mirrors the `ComptimeStringMap` at parser.zig:1120. fn lookup_non_ts_pseudo_class(name: &[u8]) -> Option { use PseudoClass as P; use WebKitScrollbarPseudoClass as WS; @@ -1570,7 +1551,6 @@ fn lookup_non_ts_pseudo_class(name: &[u8]) -> Option { } /// Case-insensitive lookup table for `parse_pseudo_element`. -/// Mirrors the `ComptimeStringMap` at parser.zig:1333. fn lookup_pseudo_element(name: &[u8]) -> Option { use PseudoElement as PE; use WebKitScrollbarPseudoElement as WS; @@ -1761,9 +1741,8 @@ impl GenericSelectorList { let mut values: SmallList, 1> = SmallList::default(); loop { - // Reshaped for borrowck — Zig used a `Closure` struct capturing - // `&mut state` and `&mut parser`; Rust captures a local `saw_nesting` flag - // and applies it to `state` after the closure returns (no raw `*mut`). + // For borrowck, the closure captures a local `saw_nesting` flag + // and applies it to `state` after it returns (no raw `*mut`). let mut saw_nesting = false; let selector = input.parse_until_before(css::Delimiters::COMMA, |input2: &mut CssParser| { @@ -1787,7 +1766,6 @@ impl GenericSelectorList { match selector { Ok(sel) => { values.append(sel); - // PERF(port): was arena append — profile if it shows up on a hot path. } Err(e) => match recovery { ParseErrorRecovery::DiscardList => return Err(e), @@ -1939,14 +1917,14 @@ impl<'a> fmt::Display for SelectorDebugFmt<'a, impl_::Selectors> { #[cfg(debug_assertions)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Mirrors the Zig `DebugFmt.format`: serialize through a buffered + // Serialize through a buffered // `Printer` over an empty symbol map. `IN_DEBUG_FMT` makes // `lookup_ident_or_ref` fall back to `debug_ident` instead of // consulting the (empty) symbol table. let arena = Bump::new(); let mut buf: Vec = Vec::new(); let symbols = bun_ast::symbol::Map::default(); - // Unwind-safe equivalent of the Zig `defer P.in_debug_fmt = false`: + // Unwind-safe reset of the flag: // Debug formatting commonly runs while building panic messages, so a // panic inside `to_css_selector` must not leak the flag thread-wide. struct InDebugFmtGuard; @@ -2263,7 +2241,7 @@ impl GenericComponent { } pub fn deep_clone(&self) -> Self { - // Hand-written variant-walk (Zig `implementDeepClone`). + // Hand-written variant-walk. // Every borrowed payload (`Str`, `Ident.v`, `IdentOrRef`) is an // arena-static identity copy; owning containers (`Vec`/`Box`) recurse. use GenericComponent as C; @@ -2452,11 +2430,11 @@ impl GenericComponent { pub fn hash(&self, hasher: &mut Wyhash) { use GenericComponent as C; - // Zig `implementHash`: `bun.writeAnyToHasher(@intFromEnum(this))` then payload. + // Hash a variant tag, then the payload. // SAFETY: `GenericComponent` is `#[repr(Rust)]`; reading the discriminant // via `core::mem::discriminant` is stable but not byte-hashable. Use a - // per-arm tag instead (CSS hashing is in-process dedup only — self- - // consistency, not Zig-byte-identity, is the contract). + // per-arm tag instead (CSS hashing is in-process dedup only — + // self-consistency is the contract). macro_rules! tag { ($n:expr) => { hasher.update(&($n as u32).to_ne_bytes()) @@ -2597,10 +2575,10 @@ impl CssHash for GenericComponent { impl fmt::Display for GenericComponent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Mirrors the Zig `format`: a few variants get detail, the rest print - // their tag name (Zig `@tagName`, hand-expanded). `BunSelectorImpl` + // A few variants get detail, the rest print + // their tag name. `BunSelectorImpl` // pins `PseudoElement`/`LocalIdentifier` to the concrete types, whose - // `Display` impls stand in for the Zig payload `format`s. + // `Display` impls handle the payloads. match self { Self::LocalName(ln) => write!(f, "local_name={}", bstr::BStr::new(ln.name.v())), Self::Combinator(c) => write!(f, "combinator='{}'", c), @@ -3042,7 +3020,7 @@ impl SelectorParseErrorKind { pub fn into_selector_error(self) -> css::SelectorError { // `error.rs::SelectorError` variants are snake_case - // (`#[allow(non_camel_case_types)]` Zig-tagName parity). + // (`#[allow(non_camel_case_types)]`). use SelectorParseErrorKind as K; use css::SelectorError as S; match self { @@ -3266,7 +3244,6 @@ impl PseudoElement { impl fmt::Display for PseudoElement { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Zig `@tagName`, hand-expanded. f.write_str(match self { Self::After => "after", Self::Before => "before", @@ -3403,7 +3380,6 @@ pub fn parse_type_selector( sink.push_simple_selector(GenericComponent::LocalName(LocalName { lower_name: { // PERF: check if it's already lowercase - // PERF(port): was arena alloc — profile if hot (see `arena_lowercase`). Ident { v: arena_lowercase(input.arena(), name), } @@ -3505,7 +3481,6 @@ pub fn parse_one_simple_selector( let names = input.parse_nested_block( |input2: &mut CssParser| -> CResult> { // todo_stuff.think_about_mem_mgmt - // PERF(port): was arena ArrayList with capacity 1 — profile if hot. let mut result: Vec = Vec::with_capacity(1); result.push(Ident { @@ -3647,7 +3622,6 @@ pub fn parse_attribute_selector( Ok(v) => v.clone(), Err(_) => { // [foo] - // PERF(port): was arena alloc — profile if hot (see `arena_lowercase`). let local_name_lower: *const [u8] = arena_lowercase(input.arena(), local_name); if let Some(ns) = namespace { let x = attrs::AttrSelectorWithOptionalNamespace:: { @@ -3733,7 +3707,6 @@ pub fn parse_attribute_selector( }; if let Some(first_uppercase) = first_uppercase { let str_ = &local_name[first_uppercase..]; - // PERF(port): was arena alloc — profile if hot (see `arena_lowercase`). let lowered: *const [u8] = arena_lowercase(input.arena(), str_); break 'brk (Ident { v: lowered }, false); } else { @@ -4004,8 +3977,7 @@ where let selector_slice = inner.into_boxed_selectors(); - // Zig threaded extra `args_` through an ArgsTuple to `func`; in Rust - // the closure captures extras directly (e.g. `prefix` for `:any()`). + // The closure captures extra args directly (e.g. `prefix` for `:any()`). let result = func(selector_slice); Ok(result) @@ -4269,8 +4241,7 @@ impl AttributeFlags { /// explicit `s`/`i` flag is given on the attribute selector. /// /// -/// PERF(port): Zig used `ComptimeEnumMap.has` (zero-cost membership at -/// comptime). An earlier `phf::Set` port paid, on every +/// PERF: an earlier `phf::Set` implementation paid, on every /// `[attr=val]` selector, a 32-bit FNV-ish hash over the name plus a /// bounds check, indirect load, and full key compare — measurable in CSS /// bundling profiles where the dominant inputs (`class`, `href`, `data-*`, @@ -4445,5 +4416,3 @@ pub fn parse_attribute_flags(input: &mut CssParser) -> CResult { } crate::css_eql_partialeq!(NthSelectorData, SpecificityAndFlags, Combinator); - -// ported from: src/css/selectors/parser.zig diff --git a/src/css/selectors/selector.rs b/src/css/selectors/selector.rs index aee0feb611b1..f86a31e12e45 100644 --- a/src/css/selectors/selector.rs +++ b/src/css/selectors/selector.rs @@ -21,9 +21,8 @@ pub use parser::SelectorList; /// Our implementation of the `SelectorImpl` interface — the trait-based /// `impl_::Selectors` marker lives in the hub (`super::impl_`) so the -/// parser↔selector cycle has a single anchor. This module is the literal -/// Zig-shaped namespace (`selector.impl.Selectors.SelectorImpl.*` type -/// aliases) kept for diff parity with `selector.zig`. +/// parser↔selector cycle has a single anchor. This module holds the +/// `SelectorImpl` type aliases. pub use super::impl_; pub mod r#impl { use super::*; @@ -175,7 +174,6 @@ pub(crate) fn downlevel_component<'bump>( // https://drafts.csswg.org/selectors/#specificity-rules if selectors.len() > 1 && targets.should_compile_same(Feature::NotSelectorList) { let is: Selector = Selector::from_component(Component::Is({ - // PERF(port): was arena bulk-alloc — profile if hot. // `Component::Is` carries `Box<[Selector]>` (heap, not arena); // could re-thread `&'bump [Selector]` once the arena lifetime is plumbed. let mut new_selectors: Vec = Vec::with_capacity(selectors.len()); @@ -184,7 +182,6 @@ pub(crate) fn downlevel_component<'bump>( } new_selectors.into_boxed_slice() })); - // PERF(port): was appendAssumeCapacity *component = Component::Negation(vec![is].into_boxed_slice()); if targets.should_compile_same(Feature::IsSelector) { @@ -214,7 +211,6 @@ fn downlevel_dir<'bump>(bump: &'bump Bump, dir: parser::Direction, targets: &Tar // otherwise, use :is/:not, which may be further downleveled to e.g. :-webkit-any. if !targets.should_compile_same(Feature::LangSelectorList) { let c = Component::NonTsPseudoClass(PseudoClass::Lang { - // PERF(port): was appendSliceAssumeCapacity (arena) — could re-thread bump. languages: RTL_LANGS.to_vec(), }); if dir == parser::Direction::Ltr { @@ -236,7 +232,6 @@ fn lang_list_to_selectors<'bump>(_bump: &'bump Bump, langs: &[&'static [u8]]) -> for lang in langs { selectors.push(Selector::from_component(Component::NonTsPseudoClass( PseudoClass::Lang { - // PERF(port): was appendAssumeCapacity (arena) languages: vec![*lang], }, ))); @@ -518,7 +513,7 @@ pub fn is_compatible(selectors: &[parser::Selector], targets: &Targets) -> bool /// A selector is considered unused if it contains a class or id component that exists in the set of unused symbols. pub fn is_unused( selectors: &[parser::Selector], - unused_symbols: &ArrayHashMap, ()>, // Zig `std.StringArrayHashMapUnmanaged(void)` + unused_symbols: &ArrayHashMap, ()>, symbols: &SymbolList, parent_is_unused: bool, ) -> bool { @@ -1819,15 +1814,10 @@ impl<'a> CompoundSelectorIter<'a> { /// The iterator would return: /// ``` /// First slice: - /// .{ - /// .{ .local_name = "div" } - /// } + /// [ LocalName("div") ] /// /// Second slice: - /// .{ - /// .{ .local_name = "p" }, - /// .{ .class = "class" } - /// } + /// [ LocalName("p"), Class("class") ] /// ``` /// /// BUT, the selectors are stored in reverse order, so this code needs to split the components backwards. @@ -1871,5 +1861,3 @@ impl<'a> CompoundSelectorIter<'a> { None } } - -// ported from: src/css/selectors/selector.zig diff --git a/src/css/small_list.rs b/src/css/small_list.rs index d4fbf7989211..6749f3088238 100644 --- a/src/css/small_list.rs +++ b/src/css/small_list.rs @@ -2,7 +2,7 @@ // The container itself now lives in `bun_collections::SmallList` (a thin // `#[repr(transparent)]` newtype over `smallvec::SmallVec<[T; N]>`). This file // keeps only the CSS-domain pieces that depend on `bun_css` types — the -// `ImageFallback` protocol and the two `getFallbacks` comptime branches — +// `ImageFallback` protocol and the two `get_fallbacks` flavors — // which the orphan rule prevents from living on the foreign `SmallList` type // as inherent methods. // @@ -17,7 +17,6 @@ // `grow_capacity`, manual `Drop`, and `SmallListIntoIter` (~800 lines of // `unsafe`) were a direct port of servo/rust-smallvec; that loop is now closed // back onto the upstream crate. -// ported from: src/css/small_list.zig pub use bun_collections::SmallList; @@ -28,11 +27,8 @@ pub use bun_collections::SmallList; // trait at the call site instead.) // ─── getFallbacks ────────────────────────────────────────────────────────── -// The Zig version uses `@hasDecl(T, "getImage")` and `T == TextShadow` comptime -// dispatch with a comptime-computed return type. In Rust this becomes a trait -// with associated type for the return. -/// Duck-typed protocol from the Zig source (`@hasDecl(T, "getImage")`): any +/// Protocol for any /// value type that carries an `Image` and can produce color/prefix fallbacks /// of itself. Implemented by `values::image::Image` and /// `properties::background::Background`. @@ -53,8 +49,8 @@ pub trait ImageFallback: Sized { // `ImageFallback for Image` is implemented alongside the type in // `crate::values::image` to avoid a duplicate impl here. -/// Port of Zig `SmallList(T, N).getFallbacks` for the `@hasDecl(T, "getImage")` -/// branch. The TextShadow branch is `get_fallbacks_text_shadow`. +/// `getFallbacks` for `ImageFallback` element types. +/// The TextShadow variant is `get_fallbacks_text_shadow`. /// /// Free-standing (was an inherent on `SmallList`) so it can live in this /// crate now that `SmallList` is foreign. The lone caller threads `self` diff --git a/src/css/sourcemap.rs b/src/css/sourcemap.rs index 88e391dfa8e0..154ab5607106 100644 --- a/src/css/sourcemap.rs +++ b/src/css/sourcemap.rs @@ -7,7 +7,7 @@ pub struct SourceMap { } pub struct SourceMapInner { - // PERF(port): ArrayListUnmanaged in CSS arena crate — using Vec; may want bun_alloc::ArenaVec<'bump, T> if hot + // PERF: using Vec; may want bun_alloc::ArenaVec<'bump, T> if hot // The `*const [u8]` elements of `sources`/`sources_content`/`names` are // arena-owned slices erased to raw pointers per PORTING.md §Allocators // (AST crates) — never dereferenced after the arena resets; they become @@ -35,5 +35,3 @@ pub struct OriginalLocation { pub source: u32, pub name: Option, } - -// ported from: src/css/sourcemap.zig diff --git a/src/css/targets.rs b/src/css/targets.rs index 03cc67b4ac48..3d0463d18536 100644 --- a/src/css/targets.rs +++ b/src/css/targets.rs @@ -77,8 +77,6 @@ impl Targets { { let mut browsers = Browsers::default(); let mut has_any = false; - // Zig used `inline for (std.meta.fields(Browsers))` reflection; - // expanded manually per field. macro_rules! check_field { ($field:ident, $env:literal) => { if let Some(val) = bun_core::getenv_z_any_case(bun_core::zstr!($env)) { @@ -137,9 +135,8 @@ impl Targets { } pub fn should_compile_same(&self, compat_feature: css::compat::Feature) -> bool { - // PERF(port): was comptime enum param — demoted to runtime (const-generic + // PERF: runtime dispatch (a const-generic param // would need #[derive(ConstParamTy)] on compat::Feature). - // Zig: comptime construct a Features with @field(feature, @tagName(compat_feature)) = true. let target_feature: Features = Features::from_compat(compat_feature); self.should_compile(compat_feature, target_feature) } @@ -185,8 +182,7 @@ pub struct Browsers { pub samsung: Option, } -// Zig: `pub const browserDefault = convertFromString(&.{...}) catch unreachable;` -// convert_from_string is not const-evaluable in Rust; compute once lazily. +// convert_from_string is not const-evaluable; compute once lazily. static BROWSER_DEFAULT: std::sync::LazyLock = std::sync::LazyLock::new(|| { Browsers::convert_from_string(&[ b"es2020", // support import.meta.url @@ -213,9 +209,8 @@ impl Browsers { } let number_part = &str[2..]; - // Zig: `try std.fmt.parseInt(u16, number_part, 10)` — propagates - // error.InvalidCharacter / error.Overflow. Preserve the tag for - // @errorName snapshot compat (do NOT collapse to UnsupportedCSSTarget). + // Propagates InvalidCharacter / Overflow. Preserve the tag for + // error-name snapshot compat (do NOT collapse to UnsupportedCSSTarget). let year = strings::parse_int::(number_part, 10).map_err(|e| match e { strings::ParseIntError::Overflow => bun_core::err!("Overflow"), strings::ParseIntError::InvalidCharacter => { @@ -322,7 +317,6 @@ impl Browsers { break 'entries_without_es &entries_buf[0..4]; } _ => { - // Zig had `if (@inComptime()) @compileLog(...)` here — no equivalent. return Err(bun_core::err!("UnsupportedCSSTarget")); } } @@ -392,8 +386,6 @@ impl Browsers { }; let version: u32 = ((major as u32) << 16) | ((minor as u32) << 8); - // Zig: `switch (browser.?) { inline else => |b| @field(browsers, @tagName(b)) ... }` - // — reflection expanded into a direct match yielding a field ref. let slot: &mut Option = match browser { Browser::Chrome => &mut browsers.chrome, Browser::Edge => &mut browsers.edge, @@ -441,7 +433,6 @@ bitflags::bitflags! { const DOUBLE_POSITION_GRADIENTS = 1 << 17; const VENDOR_PREFIXES = 1 << 18; const LOGICAL_PROPERTIES = 1 << 19; - // __unused: u12 padding in Zig const SELECTORS = Self::NESTING.bits() | Self::NOT_SELECTOR_LIST.bits() @@ -471,9 +462,7 @@ impl Default for Features { impl Features { /// Map a `compat::Feature` enum variant to the same-named `Features` bitflag. /// - /// Zig did this via `@field(feature, @tagName(compat_feature)) = true` reflection - /// inside `shouldCompileSame` (a `comptime` parameter, so a non-matching variant - /// was a compile error). Rust takes the variant at runtime, so the table is + /// The variant is taken at runtime, so the table is /// hand-written: every `compat::Feature` whose snake_case tag matches a /// `Features` field gets an arm; any other variant is a programmer error. pub fn from_compat(compat_feature: css::compat::Feature) -> Features { @@ -497,9 +486,8 @@ impl Features { Feature::SpaceSeparatedColorNotation => Features::SPACE_SEPARATED_COLOR_NOTATION, Feature::FontFamilySystemUi => Features::FONT_FAMILY_SYSTEM_UI, Feature::DoublePositionGradients => Features::DOUBLE_POSITION_GRADIENTS, - // Zig: `@field` on a tag with no matching `Features` field is a - // *compile* error; in Rust the equivalent guard is a runtime - // unreachable since `compat_feature` is no longer `comptime`. + // A tag with no matching `Features` field is a programmer error, + // guarded by a runtime unreachable. _ => unreachable!( "compat::Feature::{:?} has no same-named targets::Features flag", compat_feature @@ -507,5 +495,3 @@ impl Features { } } } - -// ported from: src/css/targets.zig diff --git a/src/css/values/alpha.rs b/src/css/values/alpha.rs index 9d17a4d5c825..ad1b35ed634e 100644 --- a/src/css/values/alpha.rs +++ b/src/css/values/alpha.rs @@ -27,11 +27,9 @@ impl AlphaValue { } impl crate::generics::CssHash for AlphaValue { - /// Zig `css.implementHash` — field-wise: hash the single `f32` payload. + /// Field-wise: hash the single `f32` payload. #[inline] fn hash(&self, hasher: &mut crate::generics::Wyhash) { crate::generics::CssHash::hash(&self.v, hasher); } } - -// ported from: src/css/values/alpha.zig diff --git a/src/css/values/angle.rs b/src/css/values/angle.rs index 2ce1655092de..f62cb9c17b30 100644 --- a/src/css/values/angle.rs +++ b/src/css/values/angle.rs @@ -205,7 +205,6 @@ impl Angle { (Angle::Turn(a), Angle::Turn(b)) => Angle::Turn(op_fn(ctx, a, b)), _ => Angle::Deg(op_fn(ctx, self.to_degrees(), other.to_degrees())), } - // PERF(port): was comptime monomorphization (fn-ptr arg) — profile if it shows up on a hot path. } pub(crate) fn sign(self) -> f32 { @@ -223,7 +222,6 @@ impl Angle { impl crate::generics::CssEql for Angle { #[inline] fn eql(&self, other: &Self) -> bool { - // Spec angle.zig:200-202 — `lhs.toDegrees() == rhs.toDegrees()`. // NOT structural variant comparison: Deg(180) eql Rad(PI) eql Turn(0.5). self.to_degrees() == other.to_degrees() } @@ -232,5 +230,3 @@ impl crate::generics::CssEql for Angle { /// A CSS [``](https://www.w3.org/TR/css-values-4/#typedef-angle-percentage) value. /// May be specified as either an angle or a percentage that resolves to an angle. pub(crate) type AnglePercentage = DimensionPercentage; - -// ported from: src/css/values/angle.zig diff --git a/src/css/values/calc.rs b/src/css/values/calc.rs index 820a44a36e24..1c6fec6b392d 100644 --- a/src/css/values/calc.rs +++ b/src/css/values/calc.rs @@ -39,9 +39,9 @@ pub enum CalcUnit { } impl CalcUnit { - /// Zig: `bun.ComptimeEnumMap(CalcUnit).getAnyCase(f)` + /// Case-insensitive lookup of a `CalcUnit` by its function name. pub fn get_any_case(f: &[u8]) -> Option { - // PERF(port): Zig used a comptime perfect hash; this is a linear match on a + // PERF: linear match on a // stack-lowercased byte slice. Profile before swapping in a phf. // §Strings: source bytes are &[u8], never &str/String — no from_utf8/to_ascii_lowercase(). let (buf, len) = bun_core::strings::ascii_lowercase_buf::<5>(f)?; @@ -114,7 +114,6 @@ pub enum Calc { } // ───────────────────────────── CalcValue trait ───────────────────────────── -// Replaces the Zig `switch (V)` / `@hasDecl(V, ...)` comptime-type dispatch. // Every type that can appear inside `Calc` implements this. // // The numeric protocol (`mul_f32`/`try_sign`/`try_map`/`try_op`/`try_op_to`/ @@ -138,9 +137,9 @@ pub trait CalcValue: + protocol::IsCompatible { fn add_internal(self, rhs: Self) -> Self; - /// Wrap a value as a `Calc` (Zig: `intoCalc`). + /// Wrap a value as a `Calc`. fn into_calc(self) -> Calc; - /// Convert a `Calc` into `Self` if representable (Zig: `intoValue`). + /// Convert a `Calc` into `Self` if representable. fn from_calc(c: Calc, input: &mut css::Parser) -> CssResult; fn eql(&self, other: &Self) -> bool; } @@ -193,8 +192,7 @@ impl Calc { { match self { Calc::Value(v) => { - // Zig: if (needs_deepclone) v.deepClone(arena) else v.* - // Rust: V: Clone covers both — V's Clone impl is the deep clone. + // V's Clone impl is the deep clone. Calc::Value(Box::new((**v).clone())) } Calc::Number(n) => Calc::Number(*n), @@ -217,7 +215,7 @@ impl Calc { Box::new(self.deep_clone()) } - // Zig `deinit` only freed owned Box fields → handled by Drop on Box/Box>/ + // Cleanup is handled by Drop on Box/Box>/ // Box>. No explicit Drop impl needed. pub fn eql(&self, other: &Self) -> bool @@ -258,22 +256,18 @@ impl Calc { impl Calc { fn mul_value_f32(lhs: V, rhs: f32) -> V { - // Zig: `f32 => lhs * rhs, else => lhs.mulF32(...)` — folded into trait. lhs.mul_f32(rhs) } pub fn add_value(lhs: V, rhs: V) -> V { - // Zig: `f32 => lhs + rhs, else => lhs.addInternal(...)` — folded into trait. lhs.add_internal(rhs) } pub fn into_value(self, input: &mut css::Parser) -> CssResult { - // Zig comptime type switch on V → trait method `V::from_calc`. V::from_calc(self, input) } pub fn into_calc(val: V) -> Self { - // Zig: `f32 => .{ .value = box(val) }, else => val.intoCalc()` — folded into trait. val.into_calc() } @@ -347,8 +341,7 @@ impl Calc { other => return Err(location.new_unexpected_token_error(other)), }; - // Zig used explicit `Closure` structs because Zig lacks closures. - // Rust closures capture `ctx` + `parse_ident` directly. + // The closures capture `ctx` + `parse_ident` directly. match unit { CalcUnit::Calc => { let calc = input.parse_nested_block(|i| Self::parse_sum(i, ctx, parse_ident))?; @@ -451,7 +444,7 @@ impl Calc { i, (), |_, a, b| { - // Zig `@mod(a, b)`: floored modulo, result takes sign of divisor. + // Floored modulo: result takes the sign of the divisor. // Equivalent to `a - b * floor(a / b)`. Rust `%` is truncated (sign of // dividend) and `rem_euclid` is non-negative, so neither matches for // negative `b` — use the explicit floor formula. @@ -470,14 +463,13 @@ impl Calc { i, (), |_, a, b| { - // Zig: `@mod((@mod(a, b) + b), b)` — floored modulo (result takes - // the sign of the divisor). `((a % b) + b) % b` over Rust's - // truncated `%` agrees in sign and value for NaN/inf and ordinary - // finite inputs, but the two formulas apply different rounding - // sequences, so results can differ near exact multiples of the - // divisor (e.g. f32 `mod(-3e-8, 1)`: this yields 0.99999994 where - // Zig's double-wrap rounds `0.99999994 + 1.0` to 2.0 and returns - // 0.0 — a divergence of ~|b| at the wrap-around boundary). + // Floored modulo (result takes the sign of the divisor): + // `((a % b) + b) % b` over Rust's truncated `%` gets the + // sign right for NaN/inf and ordinary finite inputs. + // Note: this double-wrap formula applies a different rounding + // sequence than the explicit `a - b * floor(a / b)`, so results + // can differ by ~|b| near exact multiples of the divisor + // (e.g. f32 `mod(-3e-8, 1)` -> 0.99999994 vs 0.0). ((a % b) + b) % b }, |_, a, b| MathFunction::Mod { @@ -560,7 +552,6 @@ impl Calc { ctx: C, parse_ident: impl Fn(C, &[u8]) -> Option + Copy, ) -> CssResult { - // PERF(port): was comptime monomorphization on `op` — profile if hot. input.parse_nested_block(|i| { let v = Self::parse_numeric(i, ctx, parse_ident)?; Ok(Calc::Number(match op { @@ -750,7 +741,6 @@ impl Calc { ctx: C, parse_ident: impl Fn(C, &[u8]) -> Option + Copy, ) -> CssResult { - // PERF(port): was comptime monomorphization on `trig_fn_kind` — profile if hot. let trig_fn = move |x: f32| -> f32 { match trig_fn_kind { TrigFnKind::Sin => x.sin(), @@ -763,8 +753,8 @@ impl Calc { }; input.parse_nested_block(|i| { - // Zig wrapped `parse_ident` to project `Calc::Number` into - // `Calc::Number`. Rust closure does the same. + // Wrap `parse_ident` to project `Calc::Number` into + // `Calc::Number`. let parse_ident_fn = |_self: (), ident: &[u8]| -> Option> { let v = parse_ident(ctx, ident)?; if let Calc::Number(n) = v { @@ -773,7 +763,7 @@ impl Calc { None } }; - // Zig passed `&closure` (a *@This()) as ctx; here ctx is `()` and the + // ctx is `()` and the // outer closure captures `ctx`/`parse_ident` (both `Copy`, satisfying // `parse_sum`'s `C: Copy` / `F: Fn(C, &[u8]) -> Option + Copy`). let v = Calc::::parse_sum(i, (), parse_ident_fn)?; @@ -863,8 +853,8 @@ impl Calc { // blocked_on: values/length.rs un-gate — until Length is real, // `atan2(10px, 5px)` (and any other length-dimension pair) falls - // through to the CSSNumber path below and errors with `invalid_value`, - // diverging from Zig (`Angle::Rad(atan2(10,5))`). Tracked as a known + // through to the CSSNumber path below and errors with `invalid_value` + // instead of producing `Angle::Rad(atan2(10,5))`. Tracked as a known // incompleteness; no behaviour stub is added because a partial // dimension matcher would mis-reduce mixed-unit lengths. if let Ok(v) = try_parse_atan2_args::(input, ctx) { @@ -934,7 +924,6 @@ impl Calc { Ok(val) } - // PERF(port): `args` was arena bulk-free (ArrayList fed input.arena()) — profile if hot pub fn parse_hypot(args: &mut [Self]) -> CssResult> { if args.len() == 1 { let v = core::mem::replace(&mut args[0], Calc::Number(0.0)); @@ -1104,7 +1093,6 @@ impl Calc { /// I don't like how this function requires allocating a second ArrayList /// I am pretty sure we could do this reduction in place, or do it as the /// arguments are being parsed. - // PERF(port): `args`/`reduced` were arena bulk-free (ArrayList fed input.arena()) — profile if hot fn reduce_args(args: &mut Vec, order: Ordering) { // Reduces the arguments of a min() or max() expression, combining compatible values. // e.g. min(1px, 1em, 2px, 3in) => min(1px, 1em) @@ -1129,7 +1117,7 @@ impl Calc { } } - // Reshaped for borrowck — Zig stored `?*This`; Rust stores index. + // For borrowck, `found` stores an index rather than a pointer. if let Some(maybe_idx) = found { if let Some(idx) = maybe_idx { reduced[idx] = core::mem::replace(arg, Calc::Number(420.0)); @@ -1137,14 +1125,13 @@ impl Calc { } } else { reduced.push(core::mem::replace(arg, Calc::Number(420.0))); - // PERF(port): was assume_capacity-free append continue; } - // arg dropped here (Zig: arg.deinit + dummy) + // arg dropped here *arg = Calc::Number(420.0); } - // Zig: css.deepDeinit(This, arena, args) — Rust: Drop on replace handles it. + // Drop on replace frees the old args. *args = reduced; } @@ -1194,10 +1181,8 @@ pub enum MathFunction { /// The `calc()` function. Calc(Calc), /// The `min()` function. - // PERF(port): was arena bulk-free (ArrayList fed input.arena()) — profile if hot Min(Vec>), /// The `max()` function. - // PERF(port): was arena bulk-free (ArrayList fed input.arena()) — profile if hot Max(Vec>), /// The `clamp()` function. Clamp { @@ -1220,7 +1205,6 @@ pub enum MathFunction { /// The `sign()` function. Sign(Calc), /// The `hypot()` function. - // PERF(port): was arena bulk-free (ArrayList fed input.arena()) — profile if hot Hypot(Vec>), } @@ -1392,7 +1376,7 @@ impl MathFunction { } } - // Zig `deinit` only freed owned Vec/Calc fields → handled by Drop. No explicit impl. + // Owned Vec/Calc fields are freed by Drop. No explicit impl. pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> where @@ -1540,7 +1524,6 @@ pub enum RoundingStrategy { } fn arr2(a: T, b: T) -> Vec { - // PERF(port): was arena bulk-free (ArrayList fed input.arena()) — profile if hot vec![a, b] } @@ -1566,7 +1549,7 @@ fn sqrtf32(v: f32) -> f32 { v.sqrt() } -/// Zig `std.math.sign` — returns -1.0, 0.0, or 1.0 (NOT Rust's `f32::signum`, which +/// Returns -1.0, 0.0, or 1.0 (NOT Rust's `f32::signum`, which /// returns ±1.0 for ±0.0). fn std_math_sign(v: f32) -> f32 { if v > 0.0 { @@ -1615,7 +1598,7 @@ fn absf(a: f32) -> f32 { // numeric-protocol surface (`mul_f32`/`try_sign`/`try_map`/`try_op{,_to}`/ // `partial_cmp`/`try_from_angle`/`parse`/`to_css`/`is_compatible`) is // satisfied via `crate::values::protocol::*` supertraits; only the -// calc-specific Zig `switch (V)` arms (`intoValue` / `addValue` / `intoCalc` +// calc-specific methods (`intoValue` / `addValue` / `intoCalc` // / `eql`) live here. // // Any type whose protocol impls don't already exist elsewhere gets them @@ -1668,9 +1651,7 @@ impl CalcValue for Angle { // ───────────────────────────────────────────────────────────────────────────── // `protocol::*` forwarder stamper for `CalcValue` leaf types. // -// Rust analogue of Zig's single comptime dispatcher in `src/css/generics.zig` -// (`tryFromAngle`/`trySign`/`tryMap`/`tryOp`/`tryOpTo`/`partialCmp`, lines -// ~489-570), which duck-types via `@hasDecl(T, "sign")` etc. Here each leaf +// Each leaf // opts in per-trait; only the listed arms are stamped — `Parse`/`ToCss`/ // `IsCompatible` already supplied by `impl_parse_tocss_via_inherent!` / // `bridge_is_compatible!` stay out of the invocation. @@ -1799,7 +1780,6 @@ impl CalcValue for Percentage { fn from_calc(c: Calc, _input: &mut css::Parser) -> CssResult { match c { Calc::Value(v) => Ok(*v), - // Zig: else → Percentage { v: NaN } _ => Ok(Percentage { v: f32::NAN }), } } @@ -1888,7 +1868,6 @@ impl CalcValue for Length { Length::into_calc(self) } fn from_calc(c: Calc, _input: &mut css::Parser) -> CssResult { - // Zig: Length { .calc = Box::new(self) } Ok(Length::Calc(Box::new(c))) } #[inline] @@ -1949,5 +1928,3 @@ macro_rules! dim_pct_protocol { } dim_pct_protocol!(LengthValue); dim_pct_protocol!(Angle, parse_to_css: forward,); - -// ported from: src/css/values/calc.zig diff --git a/src/css/values/color.rs b/src/css/values/color.rs index b5886a9b926a..fd3f599a467d 100644 --- a/src/css/values/color.rs +++ b/src/css/values/color.rs @@ -17,7 +17,7 @@ use bun_alloc::Arena; use bun_core::strings; // ───────────────────────── colorspace structs ──────────────────────────── -// Field layout matches `color.zig`; every space is 3 channels + alpha. +// Every space is 3 channels + alpha. /// A color with red, green, blue, and alpha components, in a byte each. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -86,20 +86,19 @@ impl RGBA { } } - /// Zig: `rgba.into(.HSL)` — routes RGBA → SRGB → HSL. + /// Routes RGBA → SRGB → HSL. #[inline] pub fn into_hsl(self) -> HSL { HSL::from_rgba(self) } - /// Zig: `rgba.into(.LAB)` — routes RGBA → SRGB → LAB. + /// Routes RGBA → SRGB → LAB. #[inline] pub fn into_lab(self) -> LAB { LAB::from_rgba(self) } /// Convert any `CssColor` into `RGBA` by routing through `SRGB`. - /// Zig: `ColorspaceConversions(@This()).tryFromCssColor`. #[inline] pub fn try_from_css_color(color: &CssColor) -> Option { Some(SRGB::try_from_css_color(color)?.into_rgba()) @@ -122,8 +121,7 @@ impl RGBA { /// /// NaN → 0 (clamp passes NaN through; `NaN as u8` saturates to 0). /// -/// NOTE: this *rounds*. Do **not** use for thumbhash, whose spec truncates -/// (`thumbhash.zig:256` `@intFromFloat`). +/// NOTE: this *rounds*. Do **not** use for thumbhash, whose spec truncates. #[inline] pub(crate) fn clamp_unit_f32(val: f32) -> u8 { (val * 255.0).round().clamp(0.0, 255.0) as u8 @@ -165,11 +163,8 @@ pub enum FloatColor { // Variant dispatch — single source of truth for (Variant ↔ Payload type ↔ // css-name ↔ hash-ordinal). Every match-over-all-variants in this file is // driven from the three `impl_variant_dispatch!` invocations below; do NOT -// hand-roll a new copy. -// -// Mirrors Zig's `switch (color.*) { inline else => |*v| v.into(T) }` shape -// (color.zig:3213 `ColorspaceConversions`) which the original Rust port -// regressed into 14 textual copies inside `define_colorspace!`. +// hand-roll a new copy (an earlier version +// regressed into 14 textual copies inside `define_colorspace!`). // // ROW ORDER IS LOAD-BEARING: ordinals feed `CssColor::hash` (Wyhash). // css-name is NOT derivable from the ident: `XyzD65` serializes as `"xyz"` @@ -392,7 +387,7 @@ pub enum CssColor { System(SystemColor), } -/// `Result(CssColor)` — Zig: `pub const ParseResult = Result(CssColor);` +/// `Result(CssColor)`. pub type ParseResult = css::CssResult; impl Default for CssColor { @@ -581,14 +576,12 @@ impl CssColor { let mut res = crate::SmallList::::default(); if fallbacks.contains(ColorFallbackKind::RGB) { - // PERF(port): was assume_capacity if let Some(rgb) = self.to_rgb() { res.append(rgb); } } if fallbacks.contains(ColorFallbackKind::P3) { - // PERF(port): was assume_capacity if let Some(p3) = self.to_p3() { res.append(p3); } @@ -879,7 +872,6 @@ impl CssColor { } pub fn hash(&self, hasher: &mut bun_wyhash::Wyhash) { - // Zig `css.implementHash` — variant-tag prefix + payload fields. // Hash the discriminant + the active variant's f32 components explicitly; // never reinterpret a `repr(Rust)` enum as raw bytes (unspecified layout / // padding → UB and non-deterministic hashes). @@ -933,8 +925,8 @@ impl crate::generics::ToCss for CssColor { } } -// `light_dark` payload helpers (Zig anonymous struct methods) -// `takeLightFreeDark` / `takeDarkFreeLight` — in Rust, taking ownership of one +// `light_dark` payload helpers +// `takeLightFreeDark` / `takeDarkFreeLight` — taking ownership of one // `Box` and dropping the other is just destructuring; provided as free fns. #[inline] pub(crate) fn take_light_free_dark(light: Box, dark: Box) -> Box { @@ -995,14 +987,11 @@ impl ColorFallbackKind { } // ────────────────────────────────────────────────────────────────────────── -// Colorspace traits (replaces Zig comptime mixins: DefineColorspace, -// BoundedColorGamut, UnboundedColorGamut, HslHwbColorGamut, DeriveInterpolate, -// RecangularPremultiply, PolarPremultiply, AdjustPowerlessLAB/LCH, -// ColorspaceConversions, ColorIntoMixin, ImplementIntoCssColor) +// Colorspace traits // ────────────────────────────────────────────────────────────────────────── -/// Trait every colorspace implements. The Zig used `@field(this, "x")` over the -/// first three struct fields plus `alpha`; here we expose them by index. +/// Trait every colorspace implements. The +/// first three struct fields plus `alpha` are exposed by index. pub trait Colorspace: Copy + Sized + FromAnyColorspace { const CHANNEL_NAMES: (&'static [u8], &'static [u8], &'static [u8]); const CHANNEL_TYPES: (ChannelType, ChannelType, ChannelType); @@ -1078,8 +1067,7 @@ pub trait Colorspace: Copy + Sized + FromAnyColorspace { fn into_css_color(self) -> CssColor; } -/// Gamut behavior — replaces UnboundedColorGamut / BoundedColorGamut / -/// HslHwbColorGamut comptime mixins. +/// Gamut behavior — in-gamut check and clipping per color space. pub trait ColorGamut: Sized + Copy { fn in_gamut(&self) -> bool; fn clip(&self) -> Self; @@ -1248,7 +1236,6 @@ pub fn parse_hslhwb_components( parser: &mut ComponentParser, allows_legacy: bool, ) -> CssResult<(f32, f32, f32, bool)> { - // Zig name: parseHSLHWBComponents — acronym run collapses to one segment let _ = core::marker::PhantomData::; // autofix let h = parse_angle_or_number(input, parser)?; let is_legacy_syntax = allows_legacy @@ -1505,18 +1492,18 @@ impl LABColor { } pub fn new_oklab(l: f32, a: f32, b: f32, alpha: f32) -> LABColor { - // Zig had `LABColor{ .lab = OKLAB.new(...) }` which looks like a bug; - // mirrored as Lab variant for behavioral parity. + // Intentionally the `Lab` variant (sic) — kept for behavioral + // compatibility. LABColor::Lab(LAB { l, a, b, alpha }) } pub fn new_lch(l: f32, a: f32, b: f32, alpha: f32) -> LABColor { - // Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored. + // Intentionally the `Lab` variant (sic) — kept for behavioral compatibility. LABColor::Lab(LAB { l, a, b, alpha }) } pub fn new_oklch(l: f32, a: f32, b: f32, alpha: f32) -> LABColor { - // Zig had `LABColor{ .lab = LCH.new(...) }` (likely bug); mirrored. + // Intentionally the `Lab` variant (sic) — kept for behavioral compatibility. LABColor::Lab(LAB { l, a, b, alpha }) } @@ -1556,8 +1543,7 @@ impl FloatColor { // Colorspace structs (LAB, SRGB, HSL, HWB, SRGBLinear, P3, A98, ProPhoto, // Rec2020, XYZd50, XYZd65, LCH, OKLAB, OKLCH) // -// In Zig each struct manually wires `pub const X = mixin.X` for ~12 mixin -// items. In Rust the trait impls below cover that surface; the per-type +// The trait impls below cover the shared surface; the per-type // declarations collapse into a `define_colorspace!` macro invocation. // ────────────────────────────────────────────────────────────────────────── @@ -1810,8 +1796,8 @@ define_colorspace! { define_colorspace! { /// A color in the [`sRGB-linear`](https://www.w3.org/TR/css-color-4/#predefined-sRGB-linear) color space. SRGBLinear { r, g, b } - // Zig had `.r = ChannelType{ .angle = true }` for SRGBLinear which looks like a bug; - // mirrored for parity. + // `r` intentionally uses the angle channel type (sic) — kept for + // behavioral compatibility. types = (CT_ANG, CT_PCT, CT_PCT); gamut = bounded; premultiply = rectangular; @@ -2066,7 +2052,7 @@ impl ComponentParser { } /// Helper trait so `parse_from` can build a light-dark wrapper for the result -/// type `C`. (Zig used `C.lightDarkOwned`.) +/// type `C`. pub(crate) trait LightDarkOwned: Sized { fn light_dark_owned(light: Self, dark: Self) -> Self; } @@ -2138,9 +2124,7 @@ impl RelativeComponentParser { return Ok(css::color::AngleOrNumber::Number { value }); } - // Zig threads a stack `Angle` through `Calc(Angle).parseWith` - // via a closure that returns `Calc{ .value = &t.angle }` (raw stack - // pointer). Rust `Calc::Value` is `Box`, so box the temporary. + // `Calc::Value` is `Box`, so box the temporary `Angle`. if let Ok(value) = input.try_parse(|i| { match Calc::::parse_with(i, this, |ctx, ident| { let value = ctx.get_ident(ident, allowed)?; @@ -2398,7 +2382,7 @@ pub fn parse_predefined_relative( b"srgb" => PredefinedColor::Srgb(SRGB { r: a, g: b, b: c, alpha }), b"srgb-linear" => PredefinedColor::SrgbLinear(SRGBLinear { r: a, g: b, b: c, alpha }), b"display-p3" => PredefinedColor::DisplayP3(P3 { r: a, g: b, b: c, alpha }), - // Zig has "a99-rgb" here (typo?); mirrored for behavioral parity. + // "a99-rgb" (sic) — kept for behavioral compatibility. b"a99-rgb" => PredefinedColor::A98(A98 { r: a, g: b, b: c, alpha }), b"prophoto-rgb" => PredefinedColor::Prophoto(ProPhoto { r: a, g: b, b: c, alpha }), b"rec2020" => PredefinedColor::Rec2020(Rec2020 { r: a, g: b, b: c, alpha }), @@ -2497,7 +2481,7 @@ pub fn parse_color_mix(input: &mut css::Parser) -> CssResult { ColorSpaceName::Xyz | ColorSpaceName::XyzD65 => { first_color.interpolate::(p1, &second_color, p2, hue_method) } - // Zig used XYZd65 for xyz-d50 too (likely bug); mirrored for parity. + // Intentionally XYZd65 for xyz-d50 too (sic) — kept for behavioral compatibility. ColorSpaceName::XyzD50 => { first_color.interpolate::(p1, &second_color, p2, hue_method) } @@ -2579,7 +2563,6 @@ fn rectangular_to_polar(l: f32, a: f32, b: f32) -> (f32, f32, f32) { h += 360.0; } - // PERF: Zig does not have Rust's f32::powi let c = (a.powi(2) + b.powi(2)).sqrt(); h = h.rem_euclid(360.0); @@ -2778,14 +2761,9 @@ const D50: [f32; 3] = [ ]; // ────────────────────────────────────────────────────────────────────────── -// Handwritten conversions (Zig `color_conversions` namespace). -// -// In Zig, `ColorIntoMixin(T, .Space).into(target)` looked up `intoXXX` in -// (a) handwritten `color_conversions.convert_`, then -// (b) generated `generated_color_conversions.convert_`, then -// (c) the type itself. +// Handwritten conversions. // -// In Rust we express each conversion as `impl From for Dst`. The +// Each conversion is an `impl From for Dst`. The // handwritten ones are below; generated ones live in `color_generated.rs`. // ────────────────────────────────────────────────────────────────────────── @@ -3634,15 +3612,10 @@ impl From for OKLAB { } // ────────────────────────────────────────────────────────────────────────── -// ConvertTo (kept for parity; in Rust the `.into()` dispatch above replaces -// `ColorIntoMixin(T, .Space).into(target)`). +// ConvertTo (kept for parity with the `.into()` dispatch above). // ────────────────────────────────────────────────────────────────────────── -// `ColorIntoMixin` resolved conversions at comptime via @hasDecl -// across handwritten + generated tables. In Rust this is the union of the -// `impl From for Dst` blocks above plus `color_generated.rs`; the -// generated file fills the transitive gaps the macro requires. +// Conversions are the union of the `impl From for Dst` blocks above +// plus `color_generated.rs`; the generated file fills the transitive gaps. crate::css_eql_partialeq!(CssColor); - -// ported from: src/css/values/color.zig diff --git a/src/css/values/color_generated.rs b/src/css/values/color_generated.rs index 43823af928df..b2d7629fd8bb 100644 --- a/src/css/values/color_generated.rs +++ b/src/css/values/color_generated.rs @@ -1,17 +1,14 @@ //! This file is generated by `color_via.ts`. Do not edit it directly! //! -//! Ported from `src/css/values/color_generated.zig` (946 lines). -//! -//! Each `convert_.into` in the Zig becomes an `impl From for Dst` -//! here. The Zig comptime mixin `ColorIntoMixin(T, .Space).into(target)` searched -//! handwritten conversions first, then this generated table; in Rust both are +//! Generated colorspace conversions: each is an `impl From for Dst`. +//! Handwritten conversions and this generated table are both //! flattened into the global `From`/`Into` trait graph, so any pair already //! defined by a handwritten impl in `color.rs` is *omitted* below to avoid the //! overlapping-impl error (Rust forbids duplicate `From` impls). //! //! Omitted (handwritten in `color.rs::gated_full_impl` takes precedence): -//! - `From for RGBA` (Zig: `convert_HSL.intoRGBA`) -//! - `From for RGBA` (Zig: `convert_HWB.intoRGBA`) +//! - `From for RGBA` +//! - `From for RGBA` use super::color::{ A98, HSL, HWB, LAB, LCH, OKLAB, OKLCH, P3, ProPhoto, RGBA, Rec2020, SRGB, SRGBLinear, XYZd50, @@ -248,5 +245,3 @@ convert_via!(OKLCH => XYZd65 => Rec2020); convert_via!(OKLCH => XYZd65 => HSL); convert_via!(OKLCH => XYZd65 => HWB); convert_via!(OKLCH => SRGB => RGBA); - -// ported from: src/css/values/color_generated.zig diff --git a/src/css/values/css_string.rs b/src/css/values/css_string.rs index cbb4bed93bb5..960cd50e69cb 100644 --- a/src/css/values/css_string.rs +++ b/src/css/values/css_string.rs @@ -27,5 +27,3 @@ impl CssStringFns { dest.serialize_string(s) } } - -// ported from: src/css/values/css_string.zig diff --git a/src/css/values/easing.rs b/src/css/values/easing.rs index 699a59df0a0c..df2154dba9ca 100644 --- a/src/css/values/easing.rs +++ b/src/css/values/easing.rs @@ -52,9 +52,9 @@ enum EasingKeyword { StepEnd, } -/// Zig: `Map.getASCIIICaseInsensitive(ident)`. +/// ASCII-case-insensitive keyword lookup. /// -/// PERF(port): was `phf::Map<&[u8], _>` + lowercase-into-stack-buf + `get`. +/// PERF: chosen over `phf::Map<&[u8], _>` + lowercase-into-stack-buf + `get`. /// 7 keys with near-unique lengths (only len 8 collides: `ease-out` / /// `step-end`), so a length-gated byte match is cheaper than phf's /// hash+displace+verify — one `usize` compare rejects almost every miss @@ -258,10 +258,10 @@ enum StepPositionKeyword { JumpEnd, } -/// Zig: `Map.getASCIIICaseInsensitive(ident)` — lowercase into a stack buffer, +/// ASCII-case-insensitive keyword lookup — lowercase into a stack buffer, /// then a length-gated byte match. /// -/// PERF(port): was `phf::Map<&[u8], _>` (6 keys). phf hashes the whole slice +/// PERF: chosen over `phf::Map<&[u8], _>` (6 keys). phf hashes the whole slice /// before a single bucket compare; with 6 keys spread across 5 distinct /// lengths (3/5/8/9/9/10), gating on `len()` rejects every miss with one /// `usize` compare and resolves every hit with at most one slice compare @@ -285,8 +285,7 @@ fn step_position_map_get_any_case(ident: &[u8]) -> Option { } impl StepPosition { - /// Zig used `css.DeriveToCss(@This()).toCss` (reflection-derived - /// keyword serializer); hand-written equivalent. + /// Hand-written keyword serializer. pub fn to_css(self, dest: &mut Printer) -> core::result::Result<(), PrintErr> { dest.write_str(<&'static str>::from(self)) } @@ -313,5 +312,3 @@ impl StepPosition { Ok(keyword) } } - -// ported from: src/css/values/easing.zig diff --git a/src/css/values/gradient.rs b/src/css/values/gradient.rs index e47b8637aa04..dbc501a9a2fb 100644 --- a/src/css/values/gradient.rs +++ b/src/css/values/gradient.rs @@ -35,8 +35,7 @@ pub trait GradientPosition: Sized + Clone + PartialEq { // Only two `D` instantiations exist (`LengthValue` / `Angle`); both already // satisfy `DimensionPercentage: CalcValue` in `calc.rs`. A blanket impl -// would need to re-state that bound; concrete impls are simpler and match -// the Zig monomorphization sites exactly. +// would need to re-state that bound; concrete impls are simpler. macro_rules! impl_gradient_position { ($ty:ty) => { impl GradientPosition for $ty { @@ -1575,5 +1574,3 @@ pub(crate) fn convert_stops_to_webkit( Some(stops) } - -// ported from: src/css/values/gradient.zig diff --git a/src/css/values/ident.rs b/src/css/values/ident.rs index 90bdcd94c5d2..84a44d90e775 100644 --- a/src/css/values/ident.rs +++ b/src/css/values/ident.rs @@ -47,15 +47,13 @@ macro_rules! arena_slice_newtype { } pub fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // Zig `css.implementDeepClone` — field-wise. The - // `*const [u8]` slice is arena-owned (never mutated, freed on - // arena reset), so identity copy is correct (matches generics.zig - // "const strings" fast-path). + // The `*const [u8]` slice is arena-owned (never mutated, freed + // on arena reset), so identity copy is correct. *self } pub fn hash(&self, hasher: &mut Wyhash) { - // Zig `css.implementHash` (comptime field-walk) → arena slice bytes. + // Hash the arena slice bytes. hasher.update(self.v()); } @@ -94,7 +92,7 @@ pub struct DashedIdentReference { impl DashedIdentReference { pub(crate) fn eql(&self, rhs: &Self) -> bool { - // Zig `css.implementEql` — field-wise over `ident` and `from`. + // Field-wise over `ident` and `from`. use crate::generics::CssEql; self.ident.eql(&rhs.ident) && self.from.eql(&rhs.from) } @@ -148,11 +146,10 @@ impl DashedIdentReference { let ident_v = unsafe { crate::arena_str(self.ident.v) }; let source_index = dest.loc.source_index; let bump = dest.arena; - // Zig `referenceDashed` took `*Printer` and called - // `dest.importRecord()` internally. Rust borrowck forbids handing + // Borrowck forbids handing // `dest` to a method on `dest.css_module`, so resolve the path - // here and pass the slice down. The `?` preserves the Zig - // `try dest.importRecord(...)` error path. + // here and pass the slice down. The `?` propagates the + // `import_record` error path. use crate::properties::css_modules::Specifier; let specifier_path: Option<&[u8]> = match &self.from { Some(Specifier::ImportRecordIndex(idx)) => { @@ -186,10 +183,8 @@ arena_slice_newtype! { DashedIdent } -/// Hash/eql context for [`DashedIdentHashMap`] — port of the anonymous context -/// struct in Zig `DashedIdent.HashMap(V)`: keys hash their string bytes via -/// `std.array_hash_map.hashString` (wyhash seed 0, truncated to u32) and -/// compare by byte equality. +/// Hash/eql context for [`DashedIdentHashMap`]: keys hash their string bytes +/// (wyhash seed 0, truncated to u32) and compare by byte equality. #[derive(Default, Clone, Copy)] pub struct DashedIdentContext; @@ -205,7 +200,7 @@ impl bun_collections::array_hash_map::ArrayHashContext for DashedId } } -/// Zig `pub fn HashMap(comptime V: type) type` — inherent assoc type aliases +/// Inherent assoc type aliases /// are unstable in Rust, so this is a free type alias instead. pub type DashedIdentHashMap = bun_collections::ArrayHashMap; @@ -262,7 +257,7 @@ impl Ident { #[derive(Clone, Copy, Default)] pub struct IdentOrRef(u128); -// Zig packed struct(u128) field layout, LSB-first: +// Packed u128 field layout, LSB-first: // __ptrbits: u63 -> bits 0..63 // __ref_bit: bool -> bit 63 // __len: u64 -> bits 64..128 @@ -330,15 +325,14 @@ impl IdentOrRef { } } - // NOTE: no `#[cfg(not(debug_assertions))]` variant. Zig's `@compileError` is lazy (fires only - // if the body is analyzed); Rust's `compile_error!` fires at expansion and would break every - // release build. Omitting the fn in release yields a name-resolution error at the call site, - // which is the closest Rust equivalent. + // NOTE: no `#[cfg(not(debug_assertions))]` variant. `compile_error!` fires at expansion and + // would break every release build. Omitting the fn in release yields a name-resolution error + // at the call site, which is the intent: this is debug-only. pub fn from_ident(ident: Ident) -> Self { let s = ident.v(); let (ptr, len) = (s.as_ptr() as usize as u64, s.len() as u64); - // @intCast(@intFromPtr(...)) — narrowing usize→u63 is checked in debug + // narrowing usize→u63 is checked in debug debug_assert!(ptr & (1u64 << 63) == 0); Self::pack(ptr, false, len) } @@ -432,9 +426,9 @@ impl IdentOrRef { if let Some(ident) = self.as_ident() { hasher.update(ident.v()); } else { - // SAFETY: self is #[repr(transparent)] u128; reading first 2 bytes matches Zig's - // `slice_u8[0..2]` (which is almost certainly a Zig bug — hashes 2 bytes, not 16). - // Preserved verbatim for behavioral parity; PR #30784 hashes the full identity. + // SAFETY: self is #[repr(transparent)] u128 (16 bytes), so reading the first 2 + // bytes is in-bounds. Hashing only 2 of the 16 bytes (sic) is preserved for + // behavioral compatibility; PR #30784 hashes the full identity. let bytes = unsafe { core::slice::from_raw_parts(std::ptr::from_ref::(self).cast::(), 2) }; @@ -537,5 +531,3 @@ impl CustomIdent { /// A list of CSS [``](https://www.w3.org/TR/css-values-4/#custom-idents) values. pub type CustomIdentList = SmallList; - -// ported from: src/css/values/ident.zig diff --git a/src/css/values/image.rs b/src/css/values/image.rs index d14791972f57..b15fff649988 100644 --- a/src/css/values/image.rs +++ b/src/css/values/image.rs @@ -19,14 +19,12 @@ pub enum Image { /// A `url()`. Url(Url), /// A gradient. - // PERF(port): arena-allocated in Zig (bun.create); LIFETIMES.tsv → Box Gradient(Box), /// An `image-set()`. ImageSet(ImageSet), } impl Image { - // NOTE: `pub fn deinit` was a no-op in Zig (all CSS parser memory is arena-owned). // No `Drop` impl needed — Box/Vec fields drop automatically. pub fn is_compatible(&self, browsers: &css::targets::Browsers) -> bool { @@ -65,7 +63,6 @@ impl Image { pub fn get_prefixed(&self, arena: &Arena, prefix: css::VendorPrefix) -> Image { match self { - // PERF(port): was arena bulk-free — profile if hot Image::Gradient(grad) => Image::Gradient(Box::new(grad.get_prefixed(arena, prefix))), Image::ImageSet(image_set) => Image::ImageSet(image_set.get_prefixed(arena, prefix)), _ => self.deep_clone(arena), @@ -134,7 +131,6 @@ impl Image { pub fn get_legacy_webkit(&self, arena: &Arena) -> Option { match self { Image::Gradient(gradient) => { - // PERF(port): was arena bulk-free — profile if hot Some(Image::Gradient(Box::new( gradient.get_legacy_webkit(arena)?, ))) @@ -164,8 +160,8 @@ impl Image { let prefix_image: &Image = if let Some(r) = &rgb { r } else { &*self }; // Legacy -webkit-gradient() - // Zig's `and`/`if-else` precedence here is preserved verbatim: - // `if (targets.browsers) |b| isWebkitGradient(b) else (false and prefix_image.* == .gradient)` + // The `false && ...` else branch is intentional (sic) — kept for + // behavioral compatibility. if prefixes.contains(VendorPrefix::WEBKIT) && if let Some(browsers) = targets.browsers { css::prefixes::Feature::is_webkit_gradient(&browsers) @@ -218,7 +214,6 @@ impl Image { pub fn get_fallback(&self, arena: &Arena, kind: ColorFallbackKind) -> Image { match self { - // PERF(port): was arena bulk-free — profile if hot Image::Gradient(grad) => Image::Gradient(Box::new(grad.get_fallback(arena, kind))), _ => self.deep_clone(arena), } @@ -283,7 +278,6 @@ impl crate::small_list::ImageFallback for Image { /// display the most appropriate resolution or file type that it supports. pub struct ImageSet { /// The image options to choose from. - // PERF(port): was ArrayListUnmanaged fed arena — profile if hot pub options: Vec, /// The vendor prefix for the `image-set()` function. @@ -401,7 +395,6 @@ impl ImageSetOption { loc: css::dependencies::Location::from_source_location(loc), }) } else { - // For some reason, `Image.parse` made zls crash; the Zig used `@call(.auto, ...)`. Image::parse(input)? }; @@ -452,7 +445,7 @@ impl ImageSetOption { let placeholder = unsafe { crate::arena_str(dep.placeholder) }; dest.serialize_string(placeholder)?; if let Some(dependencies) = &mut dest.dependencies { - // PERF(port): was `catch |err| bun.handleOom(err)` — Vec::push aborts on OOM by default + // Vec::push aborts on OOM by default. dependencies.push(css::Dependency::Url(dep)); } } else { @@ -521,5 +514,3 @@ fn parse_file_type(input: &mut css::Parser) -> Result<*const [u8]> { i.expect_string().map(std::ptr::from_ref::<[u8]>) }) } - -// ported from: src/css/values/image.zig diff --git a/src/css/values/length.rs b/src/css/values/length.rs index b2c60105164d..3ebd9bd285b2 100644 --- a/src/css/values/length.rs +++ b/src/css/values/length.rs @@ -37,9 +37,6 @@ impl Default for LengthOrNumber { } } -// Zig `deinit` only freed the owned `calc` Box inside Length — handled by Drop now. -// Zig `eql` → derive(PartialEq); Zig `deepClone` → derive(Clone). - pub(crate) type LengthPercentage = DimensionPercentage; /// Either a [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage), or the `auto` keyword. @@ -62,8 +59,6 @@ impl LengthPercentageOrAuto { } } -// Zig `eql` → derive(PartialEq); Zig `deepClone` → derive(Clone). - const PX_PER_IN: f32 = 96.0; const PX_PER_CM: f32 = PX_PER_IN / 2.54; const PX_PER_MM: f32 = PX_PER_CM / 10.0; @@ -74,15 +69,13 @@ const PX_PER_PC: f32 = PX_PER_IN / 6.0; // ────────────────────────────────────────────────────────────────────────── // LengthValue // -// The Zig original is a `union(enum)` with ~50 variants, each carrying a single -// `CSSNumber` (f32). Nearly every method iterates `std.meta.fields(@This())` / -// `bun.meta.EnumFields(@This())` to dispatch by tag — Zig comptime reflection. +// An enum with ~50 variants, each carrying a single `CSSNumber` (f32). // // Per PORTING.md §"Comptime reflection": >8 variants → small macro generator. // `define_length_units!` generates the enum plus the handful of per-variant // dispatch helpers (`value()`, `unit()`, `from_unit_ci()`, `map_value()`, // `try_same_unit_op()`, `feature()`); all higher-level methods are then written -// in terms of those, keeping the logic 1:1 with the Zig. +// in terms of those. // ────────────────────────────────────────────────────────────────────────── macro_rules! define_length_units { @@ -138,7 +131,7 @@ macro_rules! define_length_units { } } - /// Compat-feature gate for this unit (the Zig `FeatureMap`). + /// Compat-feature gate for this unit. #[inline] fn feature(&self) -> Option { match self { $( Self::$variant(_) => $feature, )* } @@ -268,8 +261,7 @@ define_length_units! { Cqmax: b"cqmax" => Some(Feature::ContainerQueryLengthUnits), } -// The Zig `comptime { ... }` block at :253-262 statically asserts that -// `FeatureMap` covers every variant. The macro above guarantees this by +// The macro above guarantees feature coverage of every variant by // construction (one `=> $feature` per variant), so no separate assert needed. impl LengthValue { @@ -351,7 +343,6 @@ impl LengthValue { } pub(crate) fn map(self, map_fn: impl FnOnce(f32) -> f32) -> LengthValue { - // PERF(port): was comptime monomorphization (`comptime map_fn: *const fn`). self.map_value(map_fn) } @@ -397,7 +388,7 @@ impl LengthValue { impl PartialEq for LengthValue { #[inline] fn eq(&self, other: &Self) -> bool { - // Zig `eql`: same tag AND equal payload (f32 `==`). + // Same tag AND equal payload (f32 `==`). core::mem::discriminant(self) == core::mem::discriminant(other) && self.value() == other.value() } @@ -422,8 +413,6 @@ impl Length { self.clone() } - // Zig `deinit` → Drop on Box> handles this. - pub(crate) fn parse(input: &mut Parser) -> CssResult { if let Ok(calc_value) = input.try_parse(Calc::::parse) { // PERF: I don't like this redundant allocation @@ -440,8 +429,6 @@ impl Length { // to_css — provided by #[derive(css::ToCss)] (Box> auto-derefs // to Calc::::to_css inherent). parse KEPT (custom Calc::Value unwrap). - // Zig `eql` → derive(PartialEq). - pub(crate) fn px(p: CSSNumber) -> Length { Self::Value(LengthValue::Px(p)) } @@ -526,8 +513,7 @@ impl Length { right: Box::new(b.into_calc()), })), } - // Reshaped for borrowck — Zig matched on tags then accessed - // `a.calc.value` / `b.calc.value` while both were still bound; Rust needs + // For borrowck this needs // to move out of the Box, so the conditions are folded into match guards. } @@ -557,8 +543,8 @@ impl Length { } _ => return None, } - // The Zig builds `Length{ .calc = s.left }` without cloning (alias - // of the same heap node); with `Box` ownership we must clone here. + // `Box` ownership requires cloning the sub-nodes here (no aliasing + // of the same heap node). } if let Self::Calc(c) = other { @@ -595,8 +581,7 @@ impl Length { }, _ => length, } - // Reshaped for borrowck — Zig rebinds `c` while reading `c.*`; - // Rust moves out of the Box once and rebuilds. + // For borrowck, this moves out of the Box once and rebuilds. } pub(crate) fn try_sign(&self) -> Option { @@ -707,8 +692,8 @@ impl protocol::TryOp for LengthValue { let v = f(ctx, self.value(), rhs.value()); return Some(self.map_value(|_| v)); } - // Zig calls `this.toPx()` for BOTH operands here (length.zig:447) — - // preserving that behavior verbatim; likely an upstream bug. + // Intentionally calls `self.to_px()` for BOTH operands (sic) — kept + // for behavioral compatibility. if let (Some(a), Some(b)) = (self.to_px(), self.to_px()) { return Some(LengthValue::Px(f(ctx, a, b))); } @@ -721,8 +706,8 @@ impl protocol::TryOpTo for LengthValue { if core::mem::discriminant(self) == core::mem::discriminant(rhs) { return Some(f(ctx, self.value(), rhs.value())); } - // Zig calls `this.toPx()` for BOTH operands here (length.zig:473) — - // preserving that behavior verbatim; likely an upstream bug. + // Intentionally calls `self.to_px()` for BOTH operands (sic) — kept + // for behavioral compatibility. if let (Some(a), Some(b)) = (self.to_px(), self.to_px()) { return Some(f(ctx, a, b)); } @@ -798,9 +783,8 @@ impl protocol::TryMap for Angle { impl protocol::TryOp for Angle { #[inline] fn try_op(&self, rhs: &Self, ctx: C, f: impl Fn(C, f32, f32) -> f32) -> Option { - // `Angle::op` takes a `fn(C, f32, f32)` pointer (Zig - // `comptime`-monomorphized fn arg), so we can't pass the generic - // closure through it. Inline the per-variant dispatch here instead. + // `Angle::op` takes a `fn(C, f32, f32)` pointer, so we can't pass the + // generic closure through it. Inline the per-variant dispatch here instead. Some(match (self, rhs) { (Angle::Deg(a), Angle::Deg(b)) => Angle::Deg(f(ctx, *a, *b)), (Angle::Rad(a), Angle::Rad(b)) => Angle::Rad(f(ctx, *a, *b)), @@ -846,5 +830,3 @@ impl protocol::IsCompatible for Angle { true } } - -// ported from: src/css/values/length.zig diff --git a/src/css/values/mod.rs b/src/css/values/mod.rs index eee401eb419e..00652cfa40b3 100644 --- a/src/css/values/mod.rs +++ b/src/css/values/mod.rs @@ -34,10 +34,9 @@ pub mod position; pub mod rect; pub mod size; pub mod syntax; -// `color_generated.rs` is the codegen'd named-color tables (47KB). Its parent -// in Zig was `color.zig`'s `pub usingnamespace`; here it's a sibling module -// re-exported through `color::*` so the stub-set re-export at crate root -// (`pub use values::color::{CssColor, RGBA, ...}`) keeps resolving. +// `color_generated.rs` is the codegen'd named-color tables (47KB). It's a +// sibling module re-exported through `color::*` so the stub-set re-export at +// crate root (`pub use values::color::{CssColor, RGBA, ...}`) keeps resolving. #[path = "color_generated.rs"] pub mod color_generated; pub mod ident; @@ -52,5 +51,3 @@ pub mod protocol { TryMap, TryOp, TryOpTo, TrySign, Zero, }; } - -// ported from: src/css/values/values.zig diff --git a/src/css/values/number.rs b/src/css/values/number.rs index ba32d31d773b..e1e141b35917 100644 --- a/src/css/values/number.rs +++ b/src/css/values/number.rs @@ -25,7 +25,6 @@ impl CSSNumberFns { let number: f32 = this; if number != 0.0 && number.abs() < 1.0 { let mut dtoa_buf: [u8; 129] = [0; 129]; - // PERF(port): Zig left dtoa_buf uninitialized — profile if hot. let (str, _) = css::dtoa_short(&mut dtoa_buf, number, 6); if number < 0.0 { dest.write_char(b'-')?; @@ -44,7 +43,7 @@ impl CSSNumberFns { pub fn sign(this: CSSNumber) -> f32 { if this == 0.0 { - // Spec-faithful (number.zig:45): ±0.0 both map to +0.0 — do NOT + // Spec-faithful: ±0.0 both map to +0.0 — do NOT // collapse with `signfns::sign_f32` / `calc::std_math_sign`. return 0.0; } @@ -68,5 +67,3 @@ impl CSSIntegerFns { css::to_css::integer(this, dest) } } - -// ported from: src/css/values/number.zig diff --git a/src/css/values/percentage.rs b/src/css/values/percentage.rs index 21bd7aaa556b..46efc34fccd0 100644 --- a/src/css/values/percentage.rs +++ b/src/css/values/percentage.rs @@ -30,7 +30,6 @@ impl Percentage { pub(crate) fn to_css(self, dest: &mut Printer) -> Result<(), PrintErr> { let x = self.v * 100.0; let int_value: Option = if (x - x.trunc()) == 0.0 { - // Rust `as` saturates on overflow/NaN where Zig is UB. Some(self.v as i32) } else { None @@ -152,9 +151,9 @@ impl PartialEq for DimensionPercentage { } // `Zero`/`MulF32`/`TryAdd`/`Parse` protocol traits live in -// `crate::values::protocol`. The Zig comptime-method surface on `D` is -// expressed via per-method `where` clauses, so plain -// `DimensionPercentage` (no behavior) needs no bounds at all. +// `crate::values::protocol`. Bounds on `D` are expressed via per-method +// `where` clauses, so plain `DimensionPercentage` (no behavior) needs no +// bounds at all. impl DimensionPercentage { pub(crate) fn parse(input: &mut css::Parser) -> CssResult where @@ -165,7 +164,6 @@ impl DimensionPercentage { if let Calc::Value(v) = calc_value { return Ok(*v); } - // PERF(port): was arena alloc (bun.create with input.arena()) — profile if hot. return Ok(Self::Calc(Box::new(calc_value))); } @@ -209,17 +207,15 @@ impl DimensionPercentage { D: Clone, { match self { - // Zig branched on `comptime needs_deepclone` to avoid cloning POD types. - // In Rust, D: Clone covers both — Copy types' clone is a bitwise copy. + // D: Clone covers POD types too — Copy types' clone is a bitwise copy. Self::Dimension(d) => Self::Dimension(d.clone()), Self::Percentage(p) => Self::Percentage(*p), - // PERF(port): was arena alloc (bun.create) — profile if hot. Self::Calc(calc) => Self::Calc(Box::new(calc.deep_clone())), } } - // `deinit` dropped — Box> frees via Drop; D's Drop (if any) runs - // automatically. Zig body only freed owned fields, so no explicit `impl Drop` needed. + // No explicit `impl Drop` needed — Box> frees via Drop; D's Drop + // (if any) runs automatically. pub(crate) fn zero() -> Self where @@ -254,7 +250,6 @@ impl DimensionPercentage { match self { Self::Dimension(d) => Self::Dimension(Self::mul_value_f32(d, other)), Self::Percentage(p) => Self::Percentage(p.mul_f32(other)), - // PERF(port): was arena alloc (bun.create) — profile if hot. Self::Calc(c) => Self::Calc(Box::new(c.mul_f32(other))), } } @@ -287,11 +282,9 @@ impl DimensionPercentage { (Self::Calc(this_calc), _) => match this_calc.as_ref() { Calc::Value(v) => return v.add_recursive(other), Calc::Sum { left, right } => { - // Reshaped for borrowck — Zig wrapped sum.left/right (raw ptrs) - // directly in This{.calc = ...} (aliasing the same heap nodes); - // with owning Boxes we deep_clone instead. The values are only - // read during this computation, so the clone is semantically - // equivalent (just extra allocation). + // With owning Boxes we deep_clone the sum operands. The values + // are only read during this computation, so the clone is + // semantically equivalent (just extra allocation). let left_calc = Self::Calc(left.deep_clone_boxed()); if let Some(res) = left_calc.add_recursive(other) { return Some(res.add_impl(Self::Calc(right.deep_clone_boxed()))); @@ -362,7 +355,6 @@ impl DimensionPercentage { a.add_impl(*v) } (a, b) => { - // PERF(port): was arena alloc (bun.create) — profile if hot. Self::Calc(Box::new(Calc::Sum { left: Box::new(a.into_calc()), right: Box::new(b.into_calc()), @@ -438,7 +430,6 @@ impl DimensionPercentage { pub(crate) fn into_calc(self) -> Calc> { match self { Self::Calc(calc) => *calc, - // PERF(port): was arena alloc (bun.create) — profile if hot. other => Calc::Value(Box::new(other)), } } @@ -454,9 +445,8 @@ pub enum NumberOrPercentage { } impl NumberOrPercentage { - // Zig used `css.DeriveParse(@This()).parse` / `css.DeriveToCss(@This()).toCss` - // (comptime reflection derives). Hand-rolled here as the trivial two-variant - // try-parse cascade so `AlphaValue::parse` doesn't panic at runtime. + // Hand-rolled as the trivial two-variant try-parse cascade so + // `AlphaValue::parse` doesn't panic at runtime. pub(crate) fn parse(input: &mut css::Parser) -> CssResult { if let Ok(n) = input.try_parse(crate::values::number::CSSNumberFns::parse) { return Ok(NumberOrPercentage::Number(n)); @@ -497,5 +487,3 @@ impl PartialEq for Percentage { } crate::css_eql_partialeq!(NumberOrPercentage); - -// ported from: src/css/values/percentage.zig diff --git a/src/css/values/position.rs b/src/css/values/position.rs index 31ecefbf448a..e3fccdd99885 100644 --- a/src/css/values/position.rs +++ b/src/css/values/position.rs @@ -417,5 +417,3 @@ impl VerticalPositionKeyword { pub(crate) type HorizontalPosition = PositionComponent; pub(crate) type VerticalPosition = PositionComponent; - -// ported from: src/css/values/position.zig diff --git a/src/css/values/ratio.rs b/src/css/values/ratio.rs index a705536f3579..c78f40063ba3 100644 --- a/src/css/values/ratio.rs +++ b/src/css/values/ratio.rs @@ -51,5 +51,3 @@ impl Ratio { } } } - -// ported from: src/css/values/ratio.zig diff --git a/src/css/values/rect.rs b/src/css/values/rect.rs index 70c1afe23efe..ccaf32c9d33e 100644 --- a/src/css/values/rect.rs +++ b/src/css/values/rect.rs @@ -3,11 +3,9 @@ use crate::css_parser::{CssResult as Result, PrintErr, Printer}; use crate::targets::Browsers; use crate::values::protocol::{IsCompatible, Parse, ToCss}; -// The Zig `needsDeinit(comptime T: type) bool` switch and the -// `deinit(this, arena)` method are dropped entirely. They existed to -// thread per-field `arena.free` through a comptime type table; in Rust, -// `T: Drop` on the four fields handles this automatically (and arena-owned -// payloads in `bun_css` are bulk-freed by the bump, never per-value). +// No `deinit` is needed: `T: Drop` on the four fields handles cleanup +// automatically (and arena-owned payloads in `bun_css` are bulk-freed by the +// bump, never per-value). /// A generic value that represents a value for four sides of a box, /// e.g. border-width, margin, padding, etc. @@ -40,9 +38,7 @@ impl Rect { where T: Clone, { - // Zig branched on `comptime needs_deinit` to decide between - // bitwise copy and per-field `.deepClone(arena)`. In Rust this is - // just the `Clone` trait on `T` — the cheap-copy types + // The `Clone` trait on `T` covers everything — the cheap-copy types // (`f32`, `NumberOrPercentage`, `LineStyle`) impl it as a bit copy. Self { top: self.top.clone(), @@ -162,5 +158,3 @@ impl IsCompatible for Rect { && self.left.is_compatible(browsers) } } - -// ported from: src/css/values/rect.zig diff --git a/src/css/values/resolution.rs b/src/css/values/resolution.rs index 52d102f23c38..8a9bbee8917f 100644 --- a/src/css/values/resolution.rs +++ b/src/css/values/resolution.rs @@ -87,5 +87,3 @@ impl Resolution { } } } - -// ported from: src/css/values/resolution.zig diff --git a/src/css/values/size.rs b/src/css/values/size.rs index 765c08fc0fa7..f3df7adc6909 100644 --- a/src/css/values/size.rs +++ b/src/css/values/size.rs @@ -10,11 +10,9 @@ pub struct Size2D { pub b: T, } -// Zig's `switch (T) { f32 => ..., LengthPercentage => ..., else => T.parse }` -// is comptime type dispatch. In Rust this is expressed via trait bounds — `f32` and +// Per-type dispatch is expressed via trait bounds — `f32` and // `LengthPercentage` impl the same `Parse`/`ToCss` traits as other CSS value -// types (the `f32` impls delegate to `CSSNumberFns`). The per-type `switch` arms are -// therefore collapsed into trait method calls below. +// types (the `f32` impls delegate to `CSSNumberFns`). impl Size2D where T: Clone + PartialEq, @@ -63,8 +61,7 @@ where } pub(crate) fn deep_clone(&self, _bump: &Arena) -> Self { - // Zig's `css.implementDeepClone` was @typeInfo-based reflection; - // `T: Clone` covers it (Box payloads deep-clone via their Clone impls). + // `T: Clone` covers this (Box payloads deep-clone via their Clone impls). Size2D { a: self.a.clone(), b: self.b.clone(), @@ -79,13 +76,10 @@ where #[inline] pub(crate) fn eql(lhs: &Self, rhs: &Self) -> bool { - // Preserved verbatim from Zig — compares lhs.a against rhs.b only - // (not a/a && b/b). Suspect upstream bug, but ported faithfully. + // Note: compares lhs.a against rhs.b only (not a/a && b/b). lhs.a == rhs.b } } // Keep references to the f32/LengthPercentage special-case helpers so trait // impls can be wired up later if they don't already exist. - -// ported from: src/css/values/size.zig diff --git a/src/css/values/syntax.rs b/src/css/values/syntax.rs index fc9cd0145824..00e93951a563 100644 --- a/src/css/values/syntax.rs +++ b/src/css/values/syntax.rs @@ -26,7 +26,7 @@ const SPACE_CHARACTERS: &[u8] = &[0x20, 0x09]; #[derive(Debug, Clone, PartialEq, Eq)] pub enum SyntaxString { /// A list of syntax components. - // PERF(port): was arena ArrayList — `Vec` until `'bump` is threaded into BumpVec. + // PERF: `Vec` until `'bump` is threaded into BumpVec. Components(Vec), /// The universal syntax definition. Universal, @@ -34,8 +34,7 @@ pub enum SyntaxString { impl SyntaxString { pub(crate) fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // Zig's `css.implementDeepClone` (comptime field reflection) maps to - // `Clone` here — every payload owns its data. + // `Clone` covers this — every payload owns its data. self.clone() } @@ -103,8 +102,6 @@ impl SyntaxString { match self { SyntaxString::Universal => Ok(ParsedComponent::TokenList(TokenList::parse( input, - // Zig passes `ParserOptions.default(input.arena(), null)`; - // Rust's signature drops the arena param (global-alloc port). &ParserOptions::default(None), 0, )?)), @@ -293,10 +290,10 @@ pub enum SyntaxComponentKind { CustomIdent, /// A literal component. // PORTING.md §Forbidden bans laundering a parser-borrowed slice to - // `&'static`. Zig's arena keeps the source alive for the AST's lifetime; Rust - // would need a `'bump` lifetime threaded through `SyntaxString`. The port owns - // the bytes instead — `Box<[u8]>` per §Forbidden ("the field should be - // `Box<[T]>` … not `&'static [T]`"). May swap for `&'bump [u8]` later. + // `&'static`; that would need a `'bump` lifetime threaded through + // `SyntaxString`. We own the bytes instead — `Box<[u8]>` per §Forbidden + // ("the field should be `Box<[T]>` … not `&'static [T]`"). May swap for + // `&'bump [u8]` later. Literal(Box<[u8]>), } @@ -386,7 +383,7 @@ fn is_name_code_point(c: u8) -> bool { // carries `Image` / `CssColor` / `Transform{,List}` / `TokenList` payloads. // No `#[derive]` — payload types lack a common Debug/Clone/PartialEq // surface (Image: none; TokenList: Default-only; Ident/CustomIdent: no Eq; -// Transform: no Debug). Zig has only `deepClone` + `toCss`, mirrored below. +// Transform: no Debug). Only `deep_clone` + `to_css` are provided below. pub enum ParsedComponent { /// A `` value. Length(Length), @@ -399,9 +396,9 @@ pub enum ParsedComponent { /// A `` value. Color(CssColor), /// An `` value. - Image(Image), // Zig doesn't have lifetimes, so 'i is omitted. + Image(Image), /// A `` value. - Url(Url), // Lifetimes are omitted in Zig. + Url(Url), /// An `` value. Integer(CSSInteger), /// An `` value. @@ -430,7 +427,7 @@ pub enum ParsedComponent { /// A repeated component value. pub struct Repeated { /// The components to repeat. - // PERF(port): was arena ArrayList — `Vec` until `'bump` is threaded into BumpVec. + // PERF: `Vec` until `'bump` is threaded into BumpVec. pub components: Vec, /// A multiplier describing how the components repeat. pub multiplier: Multiplier, @@ -479,11 +476,9 @@ impl ParsedComponent { } pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // Hand-expanded `css.implementDeepClone` (variant-wise reflection). // Payload signatures aren't yet uniform across the crate (some `deep_clone()` // take no arena, some take `&Arena`, some are `Copy`), so the `#[derive(DeepClone)]` - // macro can't cover this enum until the signatures are unified. Match-arm dispatch - // mirrors the Zig comptime switch exactly. + // macro can't cover this enum until the signatures are unified. match self { ParsedComponent::Length(v) => ParsedComponent::Length(v.deep_clone()), ParsedComponent::Number(v) => ParsedComponent::Number(*v), @@ -524,5 +519,3 @@ pub enum Multiplier { /// The component may repeat one or more times, separated by commas. Comma, } - -// ported from: src/css/values/syntax.zig diff --git a/src/css/values/time.rs b/src/css/values/time.rs index ac12fca1ae3f..e6c150ba5b6a 100644 --- a/src/css/values/time.rs +++ b/src/css/values/time.rs @@ -26,7 +26,6 @@ pub enum Time { Milliseconds(CSSNumber) = 2, } -// Mirrors Zig's nested `Tag = enum(u8) { seconds = 1, milliseconds = 2 }`. const TAG_SECONDS: u16 = 1; const TAG_MILLISECONDS: u16 = 2; @@ -54,8 +53,6 @@ impl Time { Ok(vv) => match vv { Calc::Value(v) => { let ret: Time = *v; - // redundant allocation - // Zig: vvv.deinit(input.arena()) — Drop handles this; line deleted. return Ok(ret); } // Time is always compatible, so they will always compute to a value. @@ -139,7 +136,6 @@ impl Time { } pub fn mul_f32(self, other: f32) -> Time { - // Zig arena param dropped (unused). match self { Time::Seconds(s) => Time::Seconds(s * other), Time::Milliseconds(ms) => Time::Milliseconds(ms * other), @@ -147,12 +143,10 @@ impl Time { } pub fn add_internal(self, other: Time) -> Time { - // Zig arena param dropped (forwarded but ultimately unused). self.add(other) } pub fn into_calc(self) -> Calc