Move format_args!() into AST (and expand it during AST lowering) - #106745
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
Rustfmt doesn't expand macros, so that's easy: FormatArgs nodes do not occur in the unexpanded AST.
4552f7f to
36fe3b9
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
b7f2b61 to
9e67854
Compare
|
☀️ Test successful - checks-actions |
|
🎉! |
|
Finished benchmarking commit (3e97763): comparison URL. Overall result: ❌✅ regressions and improvements - ACTION NEEDEDNext Steps: If you can justify the regressions found in this perf run, please indicate this with @rustbot label: +perf-regression Instruction countThis is a highly reliable metric that was used to determine the overall result at the top of this comment.
Max RSS (memory usage)ResultsThis is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
CyclesResultsThis is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
|
Move format_args!() into AST (and expand it during AST lowering) Implements rust-lang/compiler-team#541 This moves FormatArgs from rustc_builtin_macros to rustc_ast_lowering. For now, the end result is the same. But this allows for future changes to do smarter things with format_args!(). It also allows Clippy to directly access the ast::FormatArgs, making things a lot easier. This change turns the format args types into lang items. The builtin macro used to refer to them by their path. After this change, the path is no longer relevant, making it easier to make changes in `core`. This updates clippy to use the new language items, but this doesn't yet make clippy use the ast::FormatArgs structure that's now available. That should be done after this is merged.
|
The perf improvements greatly outweigh the regressions. @rustbot label: +perf-regression-triaged |
Pkgsrc changes: * Adjust patches and cargo checksums to new versions. * Sadly, the patch to reduce the cargo verbosity no longer applies, so I've asked upstream about the proper way to get the old result. (so the build log becomes Quite Bloated for now). Upstream changes: Version 1.69.0 (2023-04-20) ========================== Language -------- - [Deriving built-in traits on packed structs works with `Copy` fields.] (rust-lang/rust#104429) - [Stabilize the `cmpxchg16b` target feature on x86 and x86_64.] (rust-lang/rust#106774) - [Improve analysis of trait bounds for associated types.] (rust-lang/rust#103695) - [Allow associated types to be used as union fields.] (rust-lang/rust#106938) - [Allow `Self: Autotrait` bounds on dyn-safe trait methods.] (rust-lang/rust#107082) - [Treat `str` as containing `[u8]` for auto trait purposes.] (rust-lang/rust#107941) Compiler -------- - [Upgrade `*-pc-windows-gnu` on CI to mingw-w64 v10 and GCC 12.2.] (rust-lang/rust#100178) - [Rework min_choice algorithm of member constraints.] (rust-lang/rust#105300) - [Support `true` and `false` as boolean flags in compiler arguments.] (rust-lang/rust#107043) - [Default `repr(C)` enums to `c_int` size.] (rust-lang/rust#107592) Libraries --------- - [Implement the unstable `DispatchFromDyn` for cell types, allowing downstream experimentation with custom method receivers.] (rust-lang/rust#97373) - [Document that `fmt::Arguments::as_str()` may return `Some(_)` in more cases after optimization, subject to change.] (rust-lang/rust#106823) - [Implement `AsFd` and `AsRawFd` for `Rc`.] (rust-lang/rust#107317) Stabilized APIs --------------- - [`CStr::from_bytes_until_nul`] (https://doc.rust-lang.org/stable/core/ffi/struct.CStr.html#method.from_bytes_until_nul) - [`core::ffi::FromBytesUntilNulError`] (https://doc.rust-lang.org/stable/core/ffi/struct.FromBytesUntilNulError.html) These APIs are now stable in const contexts: - [`SocketAddr::new`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.new) - [`SocketAddr::ip`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.ip) - [`SocketAddr::port`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.port) - [`SocketAddr::is_ipv4`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.is_ipv4) - [`SocketAddr::is_ipv6`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.is_ipv6) - [`SocketAddrV4::new`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV4.html#method.new) - [`SocketAddrV4::ip`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV4.html#method.ip) - [`SocketAddrV4::port`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV4.html#method.port) - [`SocketAddrV6::new`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.new) - [`SocketAddrV6::ip`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.ip) - [`SocketAddrV6::port`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.port) - [`SocketAddrV6::flowinfo`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.flowinfo) - [`SocketAddrV6::scope_id`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.scope_id) Cargo ----- - [Cargo now suggests `cargo fix` or `cargo clippy --fix` when compilation warnings are auto-fixable.] (rust-lang/cargo#11558) - [Cargo now suggests `cargo add` if you try to install a library crate.] (rust-lang/cargo#11410) - [Cargo now sets the `CARGO_BIN_NAME` environment variable also for binary examples.] (rust-lang/cargo#11705) Rustdoc ----- - [Vertically compact trait bound formatting.] (rust-lang/rust#102842) - [Only include stable lints in `rustdoc::all` group.] (rust-lang/rust#106316) - [Compute maximum Levenshtein distance based on the query.] (rust-lang/rust#107141) - [Remove inconsistently-present sidebar tooltips.] (rust-lang/rust#107490) - [Search by macro when query ends with `!`.] (rust-lang/rust#108143) Compatibility Notes ------------------- - [The `rust-analysis` component from `rustup` now only contains a warning placeholder.] (rust-lang/rust#101841) This was primarily intended for RLS, and the corresponding `-Zsave-analysis` flag has been removed from the compiler as well. - [Unaligned references to packed fields are now a hard error.] (rust-lang/rust#102513) This has been a warning since 1.53, and denied by default with a future-compatibility warning since 1.62. - [Update the minimum external LLVM to 14.] (rust-lang/rust#107573) - [Cargo now emits errors on invalid characters in a registry token.] (rust-lang/cargo#11600) - [When `default-features` is set to false of a workspace dependency, and an inherited dependency of a member has `default-features = true`, Cargo will enable default features of that dependency.] (rust-lang/cargo#11409) - [Cargo denies `CARGO_HOME` in the `[env]` configuration table. Cargo itself doesn't pick up this value, but recursive calls to cargo would, which was not intended.] (rust-lang/cargo#11644) - [Debuginfo for build dependencies is now off if not explicitly set. This is expected to improve the overall build time.] (rust-lang/cargo#11252) Internal Changes ---------------- These changes do not affect any public interfaces of Rust, but they represent significant improvements to the performance or internals of rustc and related tools. - [Move `format_args!()` into AST (and expand it during AST lowering)] (rust-lang/rust#106745)
Pkgsrc changes: * Adjust patches and cargo checksums to new versions. Upstream changes: Version 1.69.0 (2023-04-20) ========================== Language -------- - [Deriving built-in traits on packed structs works with `Copy` fields.] (rust-lang/rust#104429) - [Stabilize the `cmpxchg16b` target feature on x86 and x86_64.] (rust-lang/rust#106774) - [Improve analysis of trait bounds for associated types.] (rust-lang/rust#103695) - [Allow associated types to be used as union fields.] (rust-lang/rust#106938) - [Allow `Self: Autotrait` bounds on dyn-safe trait methods.] (rust-lang/rust#107082) - [Treat `str` as containing `[u8]` for auto trait purposes.] (rust-lang/rust#107941) Compiler -------- - [Upgrade `*-pc-windows-gnu` on CI to mingw-w64 v10 and GCC 12.2.] (rust-lang/rust#100178) - [Rework min_choice algorithm of member constraints.] (rust-lang/rust#105300) - [Support `true` and `false` as boolean flags in compiler arguments.] (rust-lang/rust#107043) - [Default `repr(C)` enums to `c_int` size.] (rust-lang/rust#107592) Libraries --------- - [Implement the unstable `DispatchFromDyn` for cell types, allowing downstream experimentation with custom method receivers.] (rust-lang/rust#97373) - [Document that `fmt::Arguments::as_str()` may return `Some(_)` in more cases after optimization, subject to change.] (rust-lang/rust#106823) - [Implement `AsFd` and `AsRawFd` for `Rc`.] (rust-lang/rust#107317) Stabilized APIs --------------- - [`CStr::from_bytes_until_nul`] (https://doc.rust-lang.org/stable/core/ffi/struct.CStr.html#method.from_bytes_until_nul) - [`core::ffi::FromBytesUntilNulError`] (https://doc.rust-lang.org/stable/core/ffi/struct.FromBytesUntilNulError.html) These APIs are now stable in const contexts: - [`SocketAddr::new`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.new) - [`SocketAddr::ip`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.ip) - [`SocketAddr::port`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.port) - [`SocketAddr::is_ipv4`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.is_ipv4) - [`SocketAddr::is_ipv6`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.is_ipv6) - [`SocketAddrV4::new`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV4.html#method.new) - [`SocketAddrV4::ip`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV4.html#method.ip) - [`SocketAddrV4::port`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV4.html#method.port) - [`SocketAddrV6::new`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.new) - [`SocketAddrV6::ip`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.ip) - [`SocketAddrV6::port`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.port) - [`SocketAddrV6::flowinfo`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.flowinfo) - [`SocketAddrV6::scope_id`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.scope_id) Cargo ----- - [Cargo now suggests `cargo fix` or `cargo clippy --fix` when compilation warnings are auto-fixable.] (rust-lang/cargo#11558) - [Cargo now suggests `cargo add` if you try to install a library crate.] (rust-lang/cargo#11410) - [Cargo now sets the `CARGO_BIN_NAME` environment variable also for binary examples.] (rust-lang/cargo#11705) Rustdoc ----- - [Vertically compact trait bound formatting.] (rust-lang/rust#102842) - [Only include stable lints in `rustdoc::all` group.] (rust-lang/rust#106316) - [Compute maximum Levenshtein distance based on the query.] (rust-lang/rust#107141) - [Remove inconsistently-present sidebar tooltips.] (rust-lang/rust#107490) - [Search by macro when query ends with `!`.] (rust-lang/rust#108143) Compatibility Notes ------------------- - [The `rust-analysis` component from `rustup` now only contains a warning placeholder.] (rust-lang/rust#101841) This was primarily intended for RLS, and the corresponding `-Zsave-analysis` flag has been removed from the compiler as well. - [Unaligned references to packed fields are now a hard error.] (rust-lang/rust#102513) This has been a warning since 1.53, and denied by default with a future-compatibility warning since 1.62. - [Update the minimum external LLVM to 14.] (rust-lang/rust#107573) - [Cargo now emits errors on invalid characters in a registry token.] (rust-lang/cargo#11600) - [When `default-features` is set to false of a workspace dependency, and an inherited dependency of a member has `default-features = true`, Cargo will enable default features of that dependency.] (rust-lang/cargo#11409) - [Cargo denies `CARGO_HOME` in the `[env]` configuration table. Cargo itself doesn't pick up this value, but recursive calls to cargo would, which was not intended.] (rust-lang/cargo#11644) - [Debuginfo for build dependencies is now off if not explicitly set. This is expected to improve the overall build time.] (rust-lang/cargo#11252) Internal Changes ---------------- These changes do not affect any public interfaces of Rust, but they represent significant improvements to the performance or internals of rustc and related tools. - [Move `format_args!()` into AST (and expand it during AST lowering)] (rust-lang/rust#106745)
Move format_args!() into AST (and expand it during AST lowering) Implements rust-lang/compiler-team#541 This moves FormatArgs from rustc_builtin_macros to rustc_ast_lowering. For now, the end result is the same. But this allows for future changes to do smarter things with format_args!(). It also allows Clippy to directly access the ast::FormatArgs, making things a lot easier. This change turns the format args types into lang items. The builtin macro used to refer to them by their path. After this change, the path is no longer relevant, making it easier to make changes in `core`. This updates clippy to use the new language items, but this doesn't yet make clippy use the ast::FormatArgs structure that's now available. That should be done after this is merged.
Pkgsrc changes: * Adjust patches and cargo checksums to new versions. Upstream changes: Version 1.69.0 (2023-04-20) ========================== Language -------- - [Deriving built-in traits on packed structs works with `Copy` fields.] (rust-lang/rust#104429) - [Stabilize the `cmpxchg16b` target feature on x86 and x86_64.] (rust-lang/rust#106774) - [Improve analysis of trait bounds for associated types.] (rust-lang/rust#103695) - [Allow associated types to be used as union fields.] (rust-lang/rust#106938) - [Allow `Self: Autotrait` bounds on dyn-safe trait methods.] (rust-lang/rust#107082) - [Treat `str` as containing `[u8]` for auto trait purposes.] (rust-lang/rust#107941) Compiler -------- - [Upgrade `*-pc-windows-gnu` on CI to mingw-w64 v10 and GCC 12.2.] (rust-lang/rust#100178) - [Rework min_choice algorithm of member constraints.] (rust-lang/rust#105300) - [Support `true` and `false` as boolean flags in compiler arguments.] (rust-lang/rust#107043) - [Default `repr(C)` enums to `c_int` size.] (rust-lang/rust#107592) Libraries --------- - [Implement the unstable `DispatchFromDyn` for cell types, allowing downstream experimentation with custom method receivers.] (rust-lang/rust#97373) - [Document that `fmt::Arguments::as_str()` may return `Some(_)` in more cases after optimization, subject to change.] (rust-lang/rust#106823) - [Implement `AsFd` and `AsRawFd` for `Rc`.] (rust-lang/rust#107317) Stabilized APIs --------------- - [`CStr::from_bytes_until_nul`] (https://doc.rust-lang.org/stable/core/ffi/struct.CStr.html#method.from_bytes_until_nul) - [`core::ffi::FromBytesUntilNulError`] (https://doc.rust-lang.org/stable/core/ffi/struct.FromBytesUntilNulError.html) These APIs are now stable in const contexts: - [`SocketAddr::new`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.new) - [`SocketAddr::ip`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.ip) - [`SocketAddr::port`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.port) - [`SocketAddr::is_ipv4`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.is_ipv4) - [`SocketAddr::is_ipv6`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.is_ipv6) - [`SocketAddrV4::new`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV4.html#method.new) - [`SocketAddrV4::ip`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV4.html#method.ip) - [`SocketAddrV4::port`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV4.html#method.port) - [`SocketAddrV6::new`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.new) - [`SocketAddrV6::ip`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.ip) - [`SocketAddrV6::port`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.port) - [`SocketAddrV6::flowinfo`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.flowinfo) - [`SocketAddrV6::scope_id`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.scope_id) Cargo ----- - [Cargo now suggests `cargo fix` or `cargo clippy --fix` when compilation warnings are auto-fixable.] (rust-lang/cargo#11558) - [Cargo now suggests `cargo add` if you try to install a library crate.] (rust-lang/cargo#11410) - [Cargo now sets the `CARGO_BIN_NAME` environment variable also for binary examples.] (rust-lang/cargo#11705) Rustdoc ----- - [Vertically compact trait bound formatting.] (rust-lang/rust#102842) - [Only include stable lints in `rustdoc::all` group.] (rust-lang/rust#106316) - [Compute maximum Levenshtein distance based on the query.] (rust-lang/rust#107141) - [Remove inconsistently-present sidebar tooltips.] (rust-lang/rust#107490) - [Search by macro when query ends with `!`.] (rust-lang/rust#108143) Compatibility Notes ------------------- - [The `rust-analysis` component from `rustup` now only contains a warning placeholder.] (rust-lang/rust#101841) This was primarily intended for RLS, and the corresponding `-Zsave-analysis` flag has been removed from the compiler as well. - [Unaligned references to packed fields are now a hard error.] (rust-lang/rust#102513) This has been a warning since 1.53, and denied by default with a future-compatibility warning since 1.62. - [Update the minimum external LLVM to 14.] (rust-lang/rust#107573) - [Cargo now emits errors on invalid characters in a registry token.] (rust-lang/cargo#11600) - [When `default-features` is set to false of a workspace dependency, and an inherited dependency of a member has `default-features = true`, Cargo will enable default features of that dependency.] (rust-lang/cargo#11409) - [Cargo denies `CARGO_HOME` in the `[env]` configuration table. Cargo itself doesn't pick up this value, but recursive calls to cargo would, which was not intended.] (rust-lang/cargo#11644) - [Debuginfo for build dependencies is now off if not explicitly set. This is expected to improve the overall build time.] (rust-lang/cargo#11252) Internal Changes ---------------- These changes do not affect any public interfaces of Rust, but they represent significant improvements to the performance or internals of rustc and related tools. - [Move `format_args!()` into AST (and expand it during AST lowering)] (rust-lang/rust#106745)
Pkgsrc changes: * Adjust patches and cargo checksums to new versions. Upstream changes: Version 1.69.0 (2023-04-20) ========================== Language -------- - [Deriving built-in traits on packed structs works with `Copy` fields.] (rust-lang/rust#104429) - [Stabilize the `cmpxchg16b` target feature on x86 and x86_64.] (rust-lang/rust#106774) - [Improve analysis of trait bounds for associated types.] (rust-lang/rust#103695) - [Allow associated types to be used as union fields.] (rust-lang/rust#106938) - [Allow `Self: Autotrait` bounds on dyn-safe trait methods.] (rust-lang/rust#107082) - [Treat `str` as containing `[u8]` for auto trait purposes.] (rust-lang/rust#107941) Compiler -------- - [Upgrade `*-pc-windows-gnu` on CI to mingw-w64 v10 and GCC 12.2.] (rust-lang/rust#100178) - [Rework min_choice algorithm of member constraints.] (rust-lang/rust#105300) - [Support `true` and `false` as boolean flags in compiler arguments.] (rust-lang/rust#107043) - [Default `repr(C)` enums to `c_int` size.] (rust-lang/rust#107592) Libraries --------- - [Implement the unstable `DispatchFromDyn` for cell types, allowing downstream experimentation with custom method receivers.] (rust-lang/rust#97373) - [Document that `fmt::Arguments::as_str()` may return `Some(_)` in more cases after optimization, subject to change.] (rust-lang/rust#106823) - [Implement `AsFd` and `AsRawFd` for `Rc`.] (rust-lang/rust#107317) Stabilized APIs --------------- - [`CStr::from_bytes_until_nul`] (https://doc.rust-lang.org/stable/core/ffi/struct.CStr.html#method.from_bytes_until_nul) - [`core::ffi::FromBytesUntilNulError`] (https://doc.rust-lang.org/stable/core/ffi/struct.FromBytesUntilNulError.html) These APIs are now stable in const contexts: - [`SocketAddr::new`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.new) - [`SocketAddr::ip`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.ip) - [`SocketAddr::port`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.port) - [`SocketAddr::is_ipv4`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.is_ipv4) - [`SocketAddr::is_ipv6`] (https://doc.rust-lang.org/stable/std/net/enum.SocketAddr.html#method.is_ipv6) - [`SocketAddrV4::new`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV4.html#method.new) - [`SocketAddrV4::ip`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV4.html#method.ip) - [`SocketAddrV4::port`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV4.html#method.port) - [`SocketAddrV6::new`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.new) - [`SocketAddrV6::ip`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.ip) - [`SocketAddrV6::port`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.port) - [`SocketAddrV6::flowinfo`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.flowinfo) - [`SocketAddrV6::scope_id`] (https://doc.rust-lang.org/stable/std/net/struct.SocketAddrV6.html#method.scope_id) Cargo ----- - [Cargo now suggests `cargo fix` or `cargo clippy --fix` when compilation warnings are auto-fixable.] (rust-lang/cargo#11558) - [Cargo now suggests `cargo add` if you try to install a library crate.] (rust-lang/cargo#11410) - [Cargo now sets the `CARGO_BIN_NAME` environment variable also for binary examples.] (rust-lang/cargo#11705) Rustdoc ----- - [Vertically compact trait bound formatting.] (rust-lang/rust#102842) - [Only include stable lints in `rustdoc::all` group.] (rust-lang/rust#106316) - [Compute maximum Levenshtein distance based on the query.] (rust-lang/rust#107141) - [Remove inconsistently-present sidebar tooltips.] (rust-lang/rust#107490) - [Search by macro when query ends with `!`.] (rust-lang/rust#108143) Compatibility Notes ------------------- - [The `rust-analysis` component from `rustup` now only contains a warning placeholder.] (rust-lang/rust#101841) This was primarily intended for RLS, and the corresponding `-Zsave-analysis` flag has been removed from the compiler as well. - [Unaligned references to packed fields are now a hard error.] (rust-lang/rust#102513) This has been a warning since 1.53, and denied by default with a future-compatibility warning since 1.62. - [Update the minimum external LLVM to 14.] (rust-lang/rust#107573) - [Cargo now emits errors on invalid characters in a registry token.] (rust-lang/cargo#11600) - [When `default-features` is set to false of a workspace dependency, and an inherited dependency of a member has `default-features = true`, Cargo will enable default features of that dependency.] (rust-lang/cargo#11409) - [Cargo denies `CARGO_HOME` in the `[env]` configuration table. Cargo itself doesn't pick up this value, but recursive calls to cargo would, which was not intended.] (rust-lang/cargo#11644) - [Debuginfo for build dependencies is now off if not explicitly set. This is expected to improve the overall build time.] (rust-lang/cargo#11252) Internal Changes ---------------- These changes do not affect any public interfaces of Rust, but they represent significant improvements to the performance or internals of rustc and related tools. - [Move `format_args!()` into AST (and expand it during AST lowering)] (rust-lang/rust#106745)
…oses #630, advances #491) GREEN for the RED commit before it. Slices 5 and 6 of the design record `.claude/briefs/W12-B.md`, approved by the owner on 2026-09-13; PR #636 landed slices 1-4 (the three declarations, the tagged reference-semantic sink on every engine/compiler/runtime, fixture `466_string_sink`). `write!` needs NO type information, and that is the design. `core` defines it as `($dst:expr, $($arg:tt)*) => { $dst.write_fmt($crate::format_args!($($arg)*)) }` — the destination is a method RECEIVER, so the first argument IS the sink by construction. rustc special-cases only `format_args!` (rust-lang/rust#106745) and rust-analyzer makes the same split. That matters concretely: 6 of the 7 first-blocked Tier A files write through an UNANNOTATED closure parameter (`|f| write!(f, "-")`), which no inference could resolve. `methods.rs::encode_write_macro` therefore classifies by syntax alone: * not a local binding (parameter, field, closure param, call result) -> `std.sink_write{sink, text}`; * a bare name bound by a `let` whose initialiser is a `String` constructor -> a re-assignment `s = std.concat(s, text)`. This is the JOIN-SITES rule: in `itertools::join` the same local is also read AS a `String`, so an opaque sink would silently change those reads; * a bare name bound by a `let` of any other shape -> a LOUD refusal naming the local and its initialiser. Guessing either way is a behaviour change. `writeln!` is `write!` + a `"\n"` part, exactly how `core` spells its own no-argument arm. Both arms are wrapped in the unified `Ok(..)` outcome, because `write!` evaluates to a `fmt::Result` that 22 of the 25 corpus sites consume with `?` or `.unwrap()`; `?` on a non-outcome value is a silent-degradation seed. `build_format_expr` is extracted to `build_format_args(&[&syn::Expr], extra_newline)` so the text is built by the very lowering `println!`/`format!` already use — a pure extraction, no behaviour change. Two supporting changes, both load-bearing: * `Encoder::local_scopes` — a binding-frame stack, one frame per fn / closure / `impl` method / default-bodied trait method, seeded with that body's parameters, filled with its `let`s, looked up innermost-first so a closure parameter shadows a same-named enclosing local. Deliberately SEPARATE from `push_fn_scope`, which records parameters only for a 2+-parameter body and is not pushed at all for an `impl` method — either would leave a parameter looking like a local, and a parameter misread as a local `String` is exactly the silent miscompile the frame prevents. * `String::new()` / `String::with_capacity(n)` encode as the empty string. Both were in the encoder's "unsupported call target" bucket, so a `let mut s = String::new();` could not be encoded at all and the local-String arm would have been unreachable. Capacity is an allocation hint with no observable effect and Ball has no allocation model to carry it into; every other `Type::assoc()` on a foreign type stays the documented gap it was. No new dependency: `syn` + `quote` + `ball-lang-shared`, unchanged. `write!` is a builtin, so none of #629's `macro_rules!` machinery is involved. Measured (slice 6), re-run on the merged tree because the Tier A methodology lane changed the scored set — 77 scored, 34 excluded (test-only), not the 110 the #491 histograms are written against. One binary per side, same 5 pins: stage before after 1 encoded 1/77 7/77 2 compiled back 1/77 7/77 3 re-encoded 1/77 1/77 4 declarations kept 0/77 0/77 5 fixpoint (clean) 0/77 0/77 `tools/coverage-study/baseline.json`'s Rust row is raised on `encoded` ONLY. `clean` does not move and is not promised: the remaining walls for those 7 are issue #632 (the compiler's method dispatcher emits a `panic!` its own encoder refuses) and declaration drift. Stage 3 does not move for a second pre-existing reason: every `MessageCreation` — which the `Ok(..)` outcome is, and which a plain `Ok(x)` in hand-written source always has been — compiles to `{ let mut __ball_map = BallMap::new(); ... }`, and `BallMap::new()` is an associated function on a foreign type the encoder documents as a permanent gap. Same round-trip-closure class as #632; flagged, not folded in. Docs: `rust/AGENTS.md` gains a "`write!`/`writeln!` and the declared text sink" section with the measured funnel, and its stale "`write!` is the measured next-highest-yield target" prose is marked closed; `.claude/rules/rust.md` gains the encoder rule and has its gap list and Tier A baseline corrected. Generated-By: claude-code (model: claude-opus-5[1m]; operator: ahmednfwela@digrum.com) Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xv8Kka2YThKg6NMUSGMcw9
…oses #630, advances #491) (#698) * test(rust): RED — `write!`/`writeln!` must encode through the declared text sink (advances #630) Slice 5 of the W12-B design record (`.claude/briefs/W12-B.md`): the test-only half. Every assertion here fails on this commit, against the encoder's own refusal ``unsupported macro invocation `write!` ``. `rust/encoder/tests/write_sinks.rs` (17 cases) covers every destination shape the measured corpus contains plus the rules the design makes normative: - a `&mut fmt::Formatter` parameter — 22 of the corpus' 25 invocations — and the borrowed `&mut f` spelling reaching the same sink; - an UNANNOTATED closure parameter (`|f| write!(f, "-")`), the shape 6 of the 7 first-blocked Tier A files use, which no type inference could resolve; - the text argument reusing `format!`'s existing lowering unchanged; - the `Ok(..)` outcome wrap, so `write!(..)?` and `.unwrap()` see the shape `encode_try_operator`/`encode_unwrap` already expect; - `writeln!(f, "x")` = `write!` + `"\n"`, and `writeln!(f)` = just `"\n"` — `core`'s own definition; - the JOIN-SITES rule: a provably-local `String` is RE-ASSIGNED, never turned into a sink, so its non-sink reads in the same function still see a `String`; every `String` constructor initialiser is recognised; a closure parameter shadows a same-named enclosing local; - a local of some other type is a LOUD refusal naming the local and why; - `write!()` with no destination is a loud refusal; - `String::new()`/`String::with_capacity(n)` encode as the empty string (the enabler the local-`String` arm needs — both are currently in the encoder's "unsupported call target" bucket, so a `let mut s = String::new()` cannot be encoded at all today); - compile-back: the encoded program compiles to a library `cargo build` accepts and that calls `ball_sink_write` (#636); - end to end: a local-`String` `write!` program compiles and RUNS, printing the expected bytes. `documented_gaps.rs`'s macro pin gains its positive goalpost for the closed `write!` bucket, per that file's "closed gaps keep their test, flipped" rule. Generated-By: claude-code (model: claude-opus-5[1m]; operator: ahmednfwela@digrum.com) Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xv8Kka2YThKg6NMUSGMcw9 * feat(rust): encode write!/writeln! through the std sink functions (closes #630, advances #491) GREEN for the RED commit before it. Slices 5 and 6 of the design record `.claude/briefs/W12-B.md`, approved by the owner on 2026-09-13; PR #636 landed slices 1-4 (the three declarations, the tagged reference-semantic sink on every engine/compiler/runtime, fixture `466_string_sink`). `write!` needs NO type information, and that is the design. `core` defines it as `($dst:expr, $($arg:tt)*) => { $dst.write_fmt($crate::format_args!($($arg)*)) }` — the destination is a method RECEIVER, so the first argument IS the sink by construction. rustc special-cases only `format_args!` (rust-lang/rust#106745) and rust-analyzer makes the same split. That matters concretely: 6 of the 7 first-blocked Tier A files write through an UNANNOTATED closure parameter (`|f| write!(f, "-")`), which no inference could resolve. `methods.rs::encode_write_macro` therefore classifies by syntax alone: * not a local binding (parameter, field, closure param, call result) -> `std.sink_write{sink, text}`; * a bare name bound by a `let` whose initialiser is a `String` constructor -> a re-assignment `s = std.concat(s, text)`. This is the JOIN-SITES rule: in `itertools::join` the same local is also read AS a `String`, so an opaque sink would silently change those reads; * a bare name bound by a `let` of any other shape -> a LOUD refusal naming the local and its initialiser. Guessing either way is a behaviour change. `writeln!` is `write!` + a `"\n"` part, exactly how `core` spells its own no-argument arm. Both arms are wrapped in the unified `Ok(..)` outcome, because `write!` evaluates to a `fmt::Result` that 22 of the 25 corpus sites consume with `?` or `.unwrap()`; `?` on a non-outcome value is a silent-degradation seed. `build_format_expr` is extracted to `build_format_args(&[&syn::Expr], extra_newline)` so the text is built by the very lowering `println!`/`format!` already use — a pure extraction, no behaviour change. Two supporting changes, both load-bearing: * `Encoder::local_scopes` — a binding-frame stack, one frame per fn / closure / `impl` method / default-bodied trait method, seeded with that body's parameters, filled with its `let`s, looked up innermost-first so a closure parameter shadows a same-named enclosing local. Deliberately SEPARATE from `push_fn_scope`, which records parameters only for a 2+-parameter body and is not pushed at all for an `impl` method — either would leave a parameter looking like a local, and a parameter misread as a local `String` is exactly the silent miscompile the frame prevents. * `String::new()` / `String::with_capacity(n)` encode as the empty string. Both were in the encoder's "unsupported call target" bucket, so a `let mut s = String::new();` could not be encoded at all and the local-String arm would have been unreachable. Capacity is an allocation hint with no observable effect and Ball has no allocation model to carry it into; every other `Type::assoc()` on a foreign type stays the documented gap it was. No new dependency: `syn` + `quote` + `ball-lang-shared`, unchanged. `write!` is a builtin, so none of #629's `macro_rules!` machinery is involved. Measured (slice 6), re-run on the merged tree because the Tier A methodology lane changed the scored set — 77 scored, 34 excluded (test-only), not the 110 the #491 histograms are written against. One binary per side, same 5 pins: stage before after 1 encoded 1/77 7/77 2 compiled back 1/77 7/77 3 re-encoded 1/77 1/77 4 declarations kept 0/77 0/77 5 fixpoint (clean) 0/77 0/77 `tools/coverage-study/baseline.json`'s Rust row is raised on `encoded` ONLY. `clean` does not move and is not promised: the remaining walls for those 7 are issue #632 (the compiler's method dispatcher emits a `panic!` its own encoder refuses) and declaration drift. Stage 3 does not move for a second pre-existing reason: every `MessageCreation` — which the `Ok(..)` outcome is, and which a plain `Ok(x)` in hand-written source always has been — compiles to `{ let mut __ball_map = BallMap::new(); ... }`, and `BallMap::new()` is an associated function on a foreign type the encoder documents as a permanent gap. Same round-trip-closure class as #632; flagged, not folded in. Docs: `rust/AGENTS.md` gains a "`write!`/`writeln!` and the declared text sink" section with the measured funnel, and its stale "`write!` is the measured next-highest-yield target" prose is marked closed; `.claude/rules/rust.md` gains the encoder rule and has its gap list and Tier A baseline corrected. Generated-By: claude-code (model: claude-opus-5[1m]; operator: ahmednfwela@digrum.com) Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xv8Kka2YThKg6NMUSGMcw9 * docs: land the mutable-output-sink design record in docs/ (advances #630) Issue #630's first DoD item is a design record, reviewed before any encoder code. The record was written and reviewed, but it lived only in the lane's untracked `.claude/briefs/W12-B.md` — so the four references this work added (`.claude/rules/rust.md`, `rust/AGENTS.md`, `rust/encoder/src/methods.rs`, `rust/encoder/tests/write_sinks.rs`) all pointed at a path that does not exist in the repository, and neither #636's nor this PR's readers could reach it. `git grep claude/briefs origin/main` finds nothing: this branch introduced the first such reference. `docs/SINK_DESIGN.md` is that record, carrying the durable half — the normative runtime contract (a `__type__ = "std:Sink"`-tagged, REFERENCE-semantic map, with the required backing per target and why a by-value backing fails silently), the `std`-not-`std_io` placement and its `ball audit` reason, how all seven source languages' sink constructs map onto the same three declarations, the Rust encoder's syntax-only `write!`/`writeln!` classification with its join-sites rule and loud refusal, the rejected options (notably sink-as-`List<String>`, an issue #488-class silent API change), and the live citations each claim rests on. The lane-local half (worktree paths, the slice plan, per-commit tallies) is deliberately left out: status belongs in issues, per docs/AGENTS.md. The four references are re-pointed at it and `docs/AGENTS.md`'s index gains a row, so the file is discoverable rather than only linkable. Generated-By: claude-code (model: claude-opus-5[1m]; operator: ahmednfwela@digrum.com) Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xv8Kka2YThKg6NMUSGMcw9 * docs: record the syntax-only rule's cross-function boundary and name the instrument behind the Tier A numbers (advances #630) Two gaps a reader of this work would otherwise have to rediscover. **The boundary.** A local `String` handed to another function that writes into it (`let mut s = String::new(); helper(&mut s);`) splits across the two arms: the caller's `s` encodes as a Ball `String`, `helper`'s parameter as a sink, so the encoded program passes a string where `std.sink_write` expects a sink. That is the one shape the syntax-only rule cannot see, and the record now says so — together with the reason it is safe to leave: every engine and runtime proves the value is a tagged sink before touching it and panics naming the function (`dart/engine/lib/engine_std.dart::_stdSinkBacking`, `rust/shared/src/runtime.rs::sink_backing`), so the mismatch is LOUD, never a discarded write. It does not occur in the Tier A corpus, and closing it needs cross-function knowledge of how a local is used — an `encode_crate` resolution question, not a `write!` question. **The instrument.** `rust/AGENTS.md`'s funnel table gave measured numbers with no way to re-derive them. It now names the run that produced them: a `Coverage Study` dispatch on this branch, whose `Tier A (Rust)` job runs the exact `rq1-study` invocation `coverage-study.yml` pins and whose `publish` job checks the raised row against `tools/coverage-study/baseline.json` — so the "after" column is the repo's own gate output, and the "before" column is the committed baseline row it was floored against, not a hand-run local build. Generated-By: claude-code (model: claude-opus-5[1m]; operator: ahmednfwela@digrum.com) Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xv8Kka2YThKg6NMUSGMcw9 * test(rust): RED — a `let` inside a nested block must not outlive it (advances #630) `Encoder::local_scopes` opens ONE frame per fn / closure / `impl` method body, and `record_local` writes into it from anywhere inside that body — so a `let` in a nested block survives its own closing brace. That is not Rust's scoping rule, and it is wrong in both directions for the `write!` destination rule this PR adds: - silently: an inner `let f = String::from(..)` that shadows a `&mut fmt::Formatter` parameter `f` leaves `f` looking like a local `String` AFTER the block, so a later `write!(f, ..)` encodes as a re-assignment of a binding that is not in scope instead of `std.sink_write` on the parameter — a miscompile with nothing to observe it; - loudly: an inner `let out = 1;` that shadows a sink parameter `out` makes a later `write!(out, ..)` a refused "local whose initialiser is not a `String` constructor" — a false refusal of an ordinary sink. Two cases, one per direction, both valid Rust. They fail on this commit. Deliberately NOT covered, and recorded as a known boundary rather than guessed at: a PATTERN binding (a for-loop variable, a match-arm or `if let` binding) that shadows an enclosing local `String`. That one has no obvious right answer — `for s in writers.iter_mut()` binds a genuine `&mut String`, which is BOTH a sink and a string — so it is a design question, not a scoping bug. Generated-By: claude-code (model: claude-opus-5[1m]; operator: ahmednfwela@digrum.com) Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xv8Kka2YThKg6NMUSGMcw9 * chore(coverage-study): publish the measured Rust Tier A row (advances #630) `tools/coverage-study/baseline.json` already carries the raised Rust row (`encoded: 7`), but `README.md`'s published table still showed the pre-#630 `1`, because `coverage-study.yml`'s `publish` job only commits the regenerated table on `main`. The two halves of the same ratchet disagreeing is the state that makes a published number untrustworthy, so the row lands with the change that earned it. Regenerated, not hand-edited — by the workflow's own renderer, over the artifacts of the `Coverage Study` dispatch on this branch (run 34787000468): python3 tools/coverage-study/coverage_table.py \ --artifacts <the run's seven downloaded reports> \ --baseline tools/coverage-study/baseline.json \ --excluded-list tools/coverage-study/excluded.json \ --readme README.md --write which reports `Rows checked: 8, breaches: 0` and `baseline already current` — that second phrase is the proof the committed `encoded: 7` is EXACTLY the measured value, not a floor set above or below it. The run's own `publish` job reaches the same state and prints the identical one-line README diff under "Report what a main run would have committed". Generated-By: claude-code (model: claude-opus-5[1m]; operator: ahmednfwela@digrum.com) Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xv8Kka2YThKg6NMUSGMcw9 * fix(rust): scope a `let` to its own block in the encoder's binding frames (advances #630) `Encoder::local_scopes` opened one frame per fn / closure / `impl` method body, so a `let` in a NESTED block survived its own closing brace — not Rust's rule, and wrong in both directions for the `write!` destination classification: - silently, an inner `let f = String::from(..)` shadowing a `&mut fmt::Formatter` parameter `f` left `f` looking like a local `String` after the block, and a later `write!(f, ..)` encoded as a re-assignment of an out-of-scope binding instead of `std.sink_write` on the parameter; - loudly, an inner `let out = 1;` shadowing a sink parameter made a later `write!(out, ..)` a refused "local whose initialiser is not a `String` constructor". `block.rs::encode_block` now opens a frame of its own and pops it around the statements. Frames nest and lookup stays innermost-first, so an enclosing body's bindings are still visible — which is exactly what a block, unlike a `fn` item, may see — while its own `let`s stop leaking out. The two RED cases from the previous commit pass; nothing else moves, because every existing frame push is unchanged and a block frame is simply a narrower place for the same records. Recorded in the design record, the rule file and `rust/AGENTS.md`, together with the boundary this deliberately does NOT close: a PATTERN binding (a for-loop variable, a `match`-arm or `if let` binding) that shadows an enclosing local `String`. That has no obvious right answer — `for s in writers.iter_mut()` binds a genuine `&mut String`, which is both a sink and a string — so it is the same representation question the join-sites rule answers for `let`, left open rather than guessed at. `docs/SINK_DESIGN.md` §5 also now names the other shape a syntax-only rule cannot see (a `String` reaching a `write!` as a field, or as a local handed to a callee) and why it is loud on every target rather than silent. `tests/conformance/COVERAGE_STUDY.md`'s "Rust stops at stage 1" bullet is corrected in the same pass: with `write!` routed, 7 of 77 scored files now reach stage 1 and six of them stage 3, so "every scored file is an encode-error" is no longer true of that row. Generated-By: claude-code (model: claude-opus-5[1m]; operator: ahmednfwela@digrum.com) Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xv8Kka2YThKg6NMUSGMcw9 * docs: point both stale cross-references at the committed design record (advances #630) Two one-line corrections found reading the merged result. `.claude/rules/rust.md`'s documented-gaps bullet said `write!`/`writeln!` are closed "— next bullet", but the bullet that describes the rule is several bullets further down that section, so the pointer sent a reader to the wrong place. It now names the bullet. `docs/TESTING_STRATEGY.md` §5b's sink row states the contract and lists the per-target tests but never named where the representation decision is written down. It now points at `SINK_DESIGN.md`, so the row and the record are reachable from each other. Generated-By: claude-code (model: claude-opus-5[1m]; operator: ahmednfwela@digrum.com) Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xv8Kka2YThKg6NMUSGMcw9 * docs(rust): cite the coverage run taken at this branch's final head (advances #630) The funnel table's provenance link pointed at the `Coverage Study` dispatch made before the block-scoping fix landed. A measurement taken at an earlier commit is not evidence for the tree being merged, however unlikely the fix was to move it — the whole point of naming the instrument is that a reader can re-derive the number from the code in front of them. It now names the dispatch made at this branch's final head, whose `publish` job floors the same row against `tools/coverage-study/baseline.json`. Generated-By: claude-code (model: claude-opus-5[1m]; operator: ahmednfwela@digrum.com) Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xv8Kka2YThKg6NMUSGMcw9 * test(rust): pin a `write!` through a `&mut` alias binding, both directions (advances #630) RED on purpose: `a_write_through_a_mut_alias_re_assigns_the_local_string_it_borrows` fails on this commit (`test result: FAILED. 20 passed; 1 failed` in `rust/encoder/tests/write_sinks.rs`). Merging `origin/main` brought issue #642's `&mut` alias table into the same encoder this branch's `write!` destination rule lives in, and the two do not agree about the same name. `let slot = &mut result;` is an ALIAS binding: `block.rs::encode_local` records it and emits no `let` at all (Ball has no references), and every later READ of `slot` resolves back to `result` in `lib.rs::encode_path_expr`. `classify_write_destination` does not do that resolution: it looks `slot` up in `local_scopes`, finds nothing — an alias is deliberately never recorded there — and reads that absence as "not a local". * Borrowing a local `String` (`let mut result = String::new(); let slot = &mut result;`) therefore encodes `write!(slot, ..)` as `std.sink_write` against a plain `String`. Every engine and runtime proves a sink is the tagged sink value before touching it (`rust/shared/src/runtime.rs::sink_backing`, `dart/engine/lib/engine_std.dart::_stdSinkBacking`), so the program still fails LOUD — but at run time, for a question the encoder answered at encode time. `write!(&mut result, ..)`, the same write with no intermediate binding, already takes the re-assignment arm today. * Borrowing a sink PARAMETER is the other direction of the same question. It passes already — the sink arm encodes the destination through `encode_path_expr`, which resolves the alias — and is pinned here so the fix cannot buy one direction by breaking the other. Neither shape occurs in the Tier A corpus; both are ordinary Rust, and the pair is what the fix commit is measured against. Generated-By: claude-code (model: claude-opus-5[1m]; operator: ahmednfwela@digrum.com) Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xv8Kka2YThKg6NMUSGMcw9 * fix(rust): resolve a `&mut` alias before classifying a `write!` destination (advances #630) GREEN: `rust/encoder/tests/write_sinks.rs` is `test result: ok. 21 passed; 0 failed` — the case the previous commit left RED now passes, and the other direction it pinned still passes. `classify_write_destination` now resolves a bare destination name through `Encoder::ref_aliases` before looking it up in the binding frames, which is what `lib.rs::encode_path_expr` already does for every other read of an alias (issue #642). One line of behaviour, in the one place the two rules meet: * `let mut result = String::new(); let slot = &mut result; write!(slot, ..)` now takes the local-`String` arm and re-assigns **`result`** — the borrowed variable, which is the only binding the encoded program has, since an alias `let` emits no binding at all. It previously took the sink arm and handed `std.sink_write` a plain string. * `let out = &mut f;` over a sink parameter still encodes `std.sink_write` on `f`. That direction already worked (the sink arm encodes the destination through `encode_path_expr`), and its test is what keeps this change from buying one direction with the other. An alias of a local that is NOT a `String` reaches the same loud refusal the local itself would, naming the borrowed variable and its initialiser — the guess is still refused, just no longer missed. Recorded where the rule lives: `docs/SINK_DESIGN.md` §5 (a third supporting mechanism beside `local_scopes` and the `String::new()` enabler), `rust/AGENTS.md`, and the doc comment on `classify_write_destination` itself. Generated-By: claude-code (model: claude-opus-5[1m]; operator: ahmednfwela@digrum.com) Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xv8Kka2YThKg6NMUSGMcw9 * docs(rust): record the alias-resolution half of the `write!` destination rule in the rust rule file (advances #630) `.claude/rules/rust.md`'s `write!` bullet lists the rule's supporting mechanisms; it was the one of the three references (with `rust/AGENTS.md` and `docs/SINK_DESIGN.md` §5) that had not gained the `&mut` alias resolution the previous commit added. Same sentence, same claim, so the three cannot drift. Generated-By: claude-code (model: claude-opus-5[1m]; operator: ahmednfwela@digrum.com) Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xv8Kka2YThKg6NMUSGMcw9 * test(rust): pin a `write!` destination bound by a PATTERN, and the `impl Display` shape #630 names (advances #630) RED, test-only (`rust/encoder/tests/write_sinks.rs` is the only file touched): `test result: FAILED. 23 passed; 4 failed`. The four are exactly the four shadowing cases below; the two other new cases pass, and are pins on behaviour that already holds. Round 2's independent review raised the pattern-binding hole as advisory (#698 (comment) — "a pattern binding that shadows an enclosing local `String` mis-encodes silently"). It is the one remaining SILENT direction in issue #630's `write!` rule, so it is pinned and fixed rather than carried: * `record_local`'s only call site is `block.rs`'s `let` handling, so a for-loop variable, a `match`-arm binding and an `if let` binding are never recorded anywhere. The classifier therefore falls through to whatever ENCLOSING binding wears the same name — and an enclosing local `String` is the one kind that does not fail loud. `let mut s = String::new(); for s in writers.iter_mut() { write!(s, "x")?; }` encodes as a re-assignment of the OUTER `s`: every write the Rust aims at an element is lost, and nothing anywhere reports it. Measured here as `all calls: ["std.for_in", "std.assign", "std.concat", …]` with zero `std.sink_write`. * the same fall-through reaches a `&mut` ALIAS of the shadowed name (`let slot = &mut result; for slot in writers.iter_mut() { write!(slot, ..) }`), because the alias table is consulted for every read and a pattern binding never removed the entry — so the loop's own `slot` resolved to `result`. Three of the four are the three pattern-binding constructs (for / `if let` / `match`), which reach the rule through different call sites in `control_flow.rs`; the fourth is the alias direction. Two cases that already pass are added in the same commit, deliberately: * `a_for_loop_variable_that_shadows_nothing_is_still_a_sink` — the positive half. It is what makes the fix's choice falsifiable: a pattern binding must classify as a SINK (the arm every non-`let` destination takes), not as a loud refusal, because iterating real sinks and writing into each is an ordinary working shape. Without this case, "make pattern bindings refuse" would also pass. * `write_inside_an_impl_display_targets_the_formatter` — round 1's advisory B: nothing tested a `write!` inside an `impl` block, which is the shape #630's body names ("inside `impl Display` targets the formatter") and the shape 6 of the 7 newly-encoding Tier A files have. It pins that `types.rs::encode_item_impl`'s binding frame carries the formatter parameter — an `impl` method pushes no fn scope at all — and that the method's own local `String` does not become the destination. Generated-By: claude-code (model: claude-opus-5; operator: ahmednfwela@digrum.com) Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xv8Kka2YThKg6NMUSGMcw9 * fix(rust encoder): give a pattern binding a scope of its own, so a `write!` through one stops silently re-assigning an enclosing local (closes #630) GREEN: `rust/encoder/tests/write_sinks.rs` is `test result: ok. 27 passed; 0 failed` — the four cases the previous commit left RED now pass, and the two it added as pins on existing behaviour still do. `cargo test -p ball-lang-encoder --no-fail-fast` is green in every binary (29/9/14/12/5/20/12/2/14/2/2/2/8/2/2/27), `cargo fmt -p ball-lang-encoder -- --check` and `cargo clippy -p ball-lang-encoder --all-targets -- -D warnings` both clean. `Encoder::with_pattern_binding` is a binding frame for the three constructs that introduce a name WITHOUT a `let` — a for-loop variable, a `match`-arm binding, an `if let` binding — applied at the four call sites in `control_flow.rs` that encode a body under such a name. `record_local`'s only call site is `block.rs`'s `let` handling, so before this a pattern binding was not merely unknown but INVISIBLE: the innermost-first lookup walked past it to whatever enclosing binding wore the same name. Both directions were wrong, and the first one silently: * `let mut s = String::new(); for s in writers.iter_mut() { write!(s, "x")?; }` encoded as a re-assignment of the OUTER `s` — every write the Rust aims at an element lost, nothing anywhere reporting it; * the same fall-through reached a `&mut` ALIAS of the shadowed name (`let slot = &mut result; for slot in writers.iter_mut() { … }`), because the alias table is consulted for every READ and a pattern binding never removed the entry, so the loop's own `slot` resolved to `result`. The frame drops the alias for its duration and restores it after, exactly as a plain `let` of that name does in `encode_local` — which fixes the read direction too, not only the `write!` destination. A pattern binding classifies as a **sink** (`LocalKind::PatternBinding`, a member of its own so the two can never be confused), like a parameter — not as a loud refusal: * arm (a) is not expressible for one. `s = std.concat(s, text)` would write to the loop variable, and whether that reaches the collection is not something Ball models. * `for w in writers.iter_mut() { write!(w, ..)?; }` over real sinks is an ordinary working shape and must keep encoding. Refusing every pattern binding would have regressed it, which is why `a_for_loop_variable_that_shadows_nothing_is_still_a_sink` ships alongside: without that case, "refuse" would have passed the other four. * when the element IS a plain `String`, the write lands in the boundary `docs/SINK_DESIGN.md` §5 already documents for a `String` field — a string where `std.sink_write` expects a sink, which every engine and runtime rejects LOUDLY at run time (`rust/shared/src/runtime.rs::sink_backing`). Docs, at the altitude each one owns: `docs/SINK_DESIGN.md` §5 replaces the "open representation question" paragraph with the decided rule and its tests; `rust/AGENTS.md` and `.claude/rules/rust.md` gain the same in their `write!` sections (plus the `AliasTarget::Opaque` half of the alias resolution, which main's #693 introduced under this branch). **Three stale wall references corrected in the same pass** (round 2's advisory 2): `tests/conformance/COVERAGE_STUDY.md`, `rust/AGENTS.md` and `.claude/rules/rust.md` each named issue #632 as the stage-3 wall for the six newly-encoding Tier A files. #685 closed #632 on `main` while this PR was open; the last measured wall is ``unsupported runtime helper `ball_arg_get(...)` ``, which is issue #692 — the same compiler↔encoder round-trip class, one construct further along. Each correction says to re-measure rather than quote, because this wall moved twice in a week. Closes #630: the design record (DoD 1) is `docs/SINK_DESIGN.md`, the full std chain (DoD 2) landed in #636, and DoD 3's test-first work plus the re-measurement are this PR. Generated-By: claude-code (model: claude-opus-5; operator: ahmednfwela@digrum.com) Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xv8Kka2YThKg6NMUSGMcw9 --------- Signed-off-by: Ahmed Fwela <ahmednfwela@digrum.com> Co-authored-by: CI <ci@bdaya-dev.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Part of #99012
Implements rust-lang/compiler-team#541
This moves FormatArgs from rustc_builtin_macros to rustc_ast_lowering. For now, the end result is the same. But this allows for future changes to do smarter things with format_args!(). It also allows Clippy to directly access the ast::FormatArgs, making things a lot easier.
This change turns the format args types into lang items. The builtin macro used to refer to them by their path. After this change, the path is no longer relevant, making it easier to make changes in
core.This updates clippy to use the new language items, but this doesn't yet make clippy use the ast::FormatArgs structure that's now available. That should be done after this is merged.