Skip to content

Move format_args!() into AST (and expand it during AST lowering) - #106745

Merged
bors merged 20 commits into
rust-lang:masterfrom
m-ou-se:format-args-ast
Jan 26, 2023
Merged

bors merged 20 commits into
rust-lang:masterfrom
m-ou-se:format-args-ast

Conversation

@m-ou-se

@m-ou-se m-ou-se commented Jan 11, 2023

Copy link
Copy Markdown
Member

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.

@m-ou-se m-ou-se added A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-HIR Area: The high-level intermediate representation (HIR) A-fmt Area: `core::fmt` labels Jan 11, 2023
@m-ou-se m-ou-se self-assigned this Jan 11, 2023
@rustbot rustbot added T-bootstrap Relevant to the bootstrap subteam: Rust's build system (x.py and src/bootstrap) S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 11, 2023
@m-ou-se

This comment was marked as outdated.

@rust-timer

This comment was marked as outdated.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jan 11, 2023
@bors

This comment was marked as outdated.

@m-ou-se

This comment was marked as outdated.

@rust-timer

This comment was marked as outdated.

@bors

This comment was marked as outdated.

@bors

bors commented Jan 26, 2023

Copy link
Copy Markdown
Collaborator

☀️ Test successful - checks-actions
Approved by: oli-obk
Pushing 3e97763 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Jan 26, 2023
@bors
bors merged commit 3e97763 into rust-lang:master Jan 26, 2023
@rustbot rustbot added this to the 1.69.0 milestone Jan 26, 2023
@m-ou-se
m-ou-se deleted the format-args-ast branch January 26, 2023 16:10
@m-ou-se

m-ou-se commented Jan 26, 2023

Copy link
Copy Markdown
Member Author

🎉!

@rust-timer

Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (3e97763): comparison URL.

Overall result: ❌✅ regressions and improvements - ACTION NEEDED

Next Steps: If you can justify the regressions found in this perf run, please indicate this with @rustbot label: +perf-regression-triaged along with sufficient written justification. If you cannot justify the regressions please open an issue or create a new PR that fixes the regressions, add a comment linking to the newly created issue or PR, and then add the perf-regression-triaged label to this PR.

@rustbot label: +perf-regression
cc @rust-lang/wg-compiler-performance

Instruction count

This is a highly reliable metric that was used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
0.6% [0.2%, 0.8%] 4
Regressions ❌
(secondary)
0.8% [0.3%, 1.3%] 3
Improvements ✅
(primary)
-0.6% [-2.3%, -0.2%] 51
Improvements ✅
(secondary)
-0.7% [-0.8%, -0.7%] 6
All ❌✅ (primary) -0.6% [-2.3%, 0.8%] 55

Max RSS (memory usage)

Results

This 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.

mean range count
Regressions ❌
(primary)
0.4% [0.4%, 0.4%] 1
Regressions ❌
(secondary)
3.3% [2.0%, 7.9%] 7
Improvements ✅
(primary)
-2.9% [-3.8%, -2.1%] 4
Improvements ✅
(secondary)
-3.1% [-3.9%, -1.8%] 3
All ❌✅ (primary) -2.2% [-3.8%, 0.4%] 5

Cycles

Results

This 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.

mean range count
Regressions ❌
(primary)
1.6% [1.5%, 1.8%] 9
Regressions ❌
(secondary)
3.0% [1.5%, 4.9%] 18
Improvements ✅
(primary)
-2.2% [-3.1%, -1.3%] 5
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 0.3% [-3.1%, 1.8%] 14

@rustbot rustbot added the perf-regression Performance regression. label Jan 26, 2023
flip1995 pushed a commit to flip1995/rust that referenced this pull request Jan 27, 2023
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.
@nnethercote

Copy link
Copy Markdown
Contributor

The perf improvements greatly outweigh the regressions.

@rustbot label: +perf-regression-triaged

@rustbot rustbot added the perf-regression-triaged The performance regression has been triaged. label Jan 29, 2023
wip-sync pushed a commit to NetBSD/pkgsrc-wip that referenced this pull request Apr 24, 2023
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)
netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this pull request May 4, 2023
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)
calebcartwright pushed a commit to calebcartwright/rust that referenced this pull request Jun 20, 2023
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.
msk pushed a commit to msk/pkgsrc that referenced this pull request May 11, 2026
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)
jperkin pushed a commit to TritonDataCenter/pkgsrc that referenced this pull request May 14, 2026
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)
ahmednfwela pushed a commit to Ball-Lang/ball that referenced this pull request Sep 13, 2026
…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
ahmednfwela added a commit to Ball-Lang/ball that referenced this pull request Sep 21, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-fmt Area: `core::fmt` A-HIR Area: The high-level intermediate representation (HIR) A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) merged-by-bors This PR was explicitly merged by bors. perf-regression Performance regression. perf-regression-triaged The performance regression has been triaged. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.