From f4a28a3549f23736ce327c379876081ea202f53a Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 11:58:13 -0800 Subject: [PATCH 1/3] chore(quest): claim quest/m3/ts-argument-spans.md Quest-Claim: 111b25c3ca2e94b8 From 4fc63e2d551c210b06ee3fbfa92884b2667a68e5 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 12:09:08 -0800 Subject: [PATCH 2/3] feat(ts): map a name or an operator argument to its own Rust Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/reject/spans.toml | 2 +- ts/fixtures/reject/spans.txt | 22 +++++++----- ts/fixtures/reject/src/spans.rs | 7 +++- ts/src/lower/mir/call.rs | 60 ++++++++++++++++++++++++++++++++- ts/src/lower/mir/mod.rs | 4 +++ ts/src/lower/mir/scalar.rs | 1 + 6 files changed, 85 insertions(+), 11 deletions(-) diff --git a/ts/fixtures/reject/spans.toml b/ts/fixtures/reject/spans.toml index 033dae557..f02a659df 100644 --- a/ts/fixtures/reject/spans.toml +++ b/ts/fixtures/reject/spans.toml @@ -1,4 +1,4 @@ # Rust that translates, which the stand-in `tsc` the test puts on `PATH` fails at each # `return` and each argument: `spans.txt` holds where each is reported. package = "reject-fixture" -roots = ["spans::tail", "spans::early", "spans::method", "spans::chained"] +roots = ["spans::tail", "spans::early", "spans::method", "spans::chained", "spans::operators"] diff --git a/ts/fixtures/reject/spans.txt b/ts/fixtures/reject/spans.txt index 859b87e31..8b58b69f8 100644 --- a/ts/fixtures/reject/spans.txt +++ b/ts/fixtures/reject/spans.txt @@ -1,20 +1,20 @@ fixtures/reject/src/spans.rs:chained+1:2: spans.ts:23:2 doesn't type-check, which is a bug in rust2-ts: error TS2322: a return +fixtures/reject/src/spans.rs:chained+1:2: spans.ts:23:68 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, v fixtures/reject/src/spans.rs:chained+1:4: spans.ts:23:55 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, rt.iterSlice(v) -fixtures/reject/src/spans.rs:chained+1:4: spans.ts:23:68 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, v fixtures/reject/src/spans.rs:chained+1:11: spans.ts:23:44 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, rt.iterMap(rt.iterSlice(v), (x: rt.u32): rt.u32 => { fixtures/reject/src/spans.rs:chained+1:11: spans.ts:23:72 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, (x: rt.u32): rt.u32 => { +fixtures/reject/src/spans.rs:chained+1:19: spans.ts:24:29 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, x fixtures/reject/src/spans.rs:chained+1:19: spans.ts:24:3 doesn't type-check, which is a bug in rust2-ts: error TS2322: a return -fixtures/reject/src/spans.rs:chained+1:21: spans.ts:24:29 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, x fixtures/reject/src/spans.rs:chained+1:38: spans.ts:23:33 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, rt.iterMax(rt.iterMap(rt.iterSlice(v), (x: rt.u32): rt.u32 => { fixtures/reject/src/spans.rs:chained+1:38: spans.ts:25:6 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, rt.lt fixtures/reject/src/spans.rs:chained+1:44: spans.ts:25:14 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, 0 fixtures/reject/src/spans.rs:early+2:10: spans.ts:15:2 doesn't type-check, which is a bug in rust2-ts: error TS2322: a return -fixtures/reject/src/spans.rs:early+2:12: spans.ts:15:26 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, a +fixtures/reject/src/spans.rs:early+2:10: spans.ts:15:26 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, a fixtures/reject/src/spans.rs:early+2:12: spans.ts:15:29 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, 2 fixtures/reject/src/spans.rs:early+2:12: spans.ts:15:32 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, 32 fixtures/reject/src/spans.rs:early+2:12: spans.ts:15:36 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, false +fixtures/reject/src/spans.rs:early+4:2: spans.ts:13:26 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, a fixtures/reject/src/spans.rs:early+4:2: spans.ts:13:3 doesn't type-check, which is a bug in rust2-ts: error TS2322: a return -fixtures/reject/src/spans.rs:early+4:4: spans.ts:13:26 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, a fixtures/reject/src/spans.rs:early+4:4: spans.ts:13:29 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, 32 fixtures/reject/src/spans.rs:early+4:4: spans.ts:13:33 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, false fixtures/reject/src/spans.rs:impl Meter+2:3: spans.Meter.ts:10:19 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, self.reads[rt.idx(self.reads, at)] @@ -23,15 +23,21 @@ fixtures/reject/src/spans.rs:impl Meter+2:3: spans.Meter.ts:10:37 doesn't type-c fixtures/reject/src/spans.rs:impl Meter+2:3: spans.Meter.ts:10:49 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, at fixtures/reject/src/spans.rs:impl Meter+2:3: spans.Meter.ts:10:55 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, by fixtures/reject/src/spans.rs:method+1:2: spans.ts:19:2 doesn't type-check, which is a bug in rust2-ts: error TS2322: a return -fixtures/reject/src/spans.rs:method+1:4: spans.ts:19:22 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, m +fixtures/reject/src/spans.rs:method+1:2: spans.ts:19:22 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, m +fixtures/reject/src/spans.rs:method+1:11: spans.ts:19:43 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, a fixtures/reject/src/spans.rs:method+1:13: spans.ts:19:25 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, rt.u32WrappingAdd(a, 1) -fixtures/reject/src/spans.rs:method+1:13: spans.ts:19:43 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, a fixtures/reject/src/spans.rs:method+1:13: spans.ts:19:46 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, 1 fixtures/reject/src/spans.rs:method+1:30: spans.ts:19:50 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, rt.usizeSub(m.reads.length, 1) fixtures/reject/src/spans.rs:method+1:30: spans.ts:19:78 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, 1 fixtures/reject/src/spans.rs:method+1:38: spans.ts:19:62 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, m.reads.length +fixtures/reject/src/spans.rs:operators+1:2: spans.ts:29:2 doesn't type-check, which is a bug in rust2-ts: error TS2322: a return +fixtures/reject/src/spans.rs:operators+1:2: spans.ts:29:22 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, m +fixtures/reject/src/spans.rs:operators+1:11: spans.ts:29:25 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, (a ^ 7) >>> 0 +fixtures/reject/src/spans.rs:operators+1:18: spans.ts:29:40 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, rt.usizeDiv(at, 2) +fixtures/reject/src/spans.rs:operators+1:18: spans.ts:29:52 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, at +fixtures/reject/src/spans.rs:operators+1:18: spans.ts:29:56 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, 2 fixtures/reject/src/spans.rs:tail+2:2: spans.ts:8:2 doesn't type-check, which is a bug in rust2-ts: error TS2322: a return +fixtures/reject/src/spans.rs:tail+2:2: spans.ts:8:37 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, (a ^ b) >>> 0 fixtures/reject/src/spans.rs:tail+2:2: spans.ts:8:56 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, b fixtures/reject/src/spans.rs:tail+2:4: spans.ts:8:19 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, rt.u32WrappingMul((a ^ b) >>> 0, a) -fixtures/reject/src/spans.rs:tail+2:4: spans.ts:8:37 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, (a ^ b) >>> 0 -fixtures/reject/src/spans.rs:tail+2:4: spans.ts:8:52 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, a +fixtures/reject/src/spans.rs:tail+2:17: spans.ts:8:52 doesn't type-check, which is a bug in rust2-ts: error TS2345: an argument, a diff --git a/ts/fixtures/reject/src/spans.rs b/ts/fixtures/reject/src/spans.rs index dc61b0630..af91a981b 100644 --- a/ts/fixtures/reject/src/spans.rs +++ b/ts/fixtures/reject/src/spans.rs @@ -1,5 +1,6 @@ //! Rust that translates, for the stand-in `tsc` of `spans.toml`, which fails each `return` -//! and each argument, to be reported at the Rust each comes from. +//! and each argument, to be reported at the Rust each comes from: a call, a name, or an +//! operator. pub struct Meter { pub reads: Vec, @@ -30,3 +31,7 @@ pub fn method(m: &Meter, a: u32) -> u32 { pub fn chained(v: &[u32]) -> u32 { v.iter().map(|x| x.leading_zeros()).max().unwrap_or(0) } + +pub fn operators(m: &Meter, a: u32, at: usize) -> u32 { + m.scaled(a ^ 7, at / 2) +} diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index 61d39f08d..96f6bd84f 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -14,7 +14,7 @@ use crate::lower::fmt::{self, Piece}; use crate::lower::lend::{self, Plan}; use crate::lower::library::{self, Flow, Std}; use crate::lower::num::Int; -use crate::ts::{Expr, Stmt, StmtKind}; +use crate::ts::{self, Expr, Stmt, StmtKind}; /// What a `&mut` borrows: its path, and the place a store through the reference writes, /// unless the reference is refused. @@ -30,6 +30,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { out: &mut Vec, ) { let tcx = self.tcx; + self.arguments.clear(); if self.forwards(dest) { self.forward_call(args, span, out); return; @@ -134,6 +135,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { Some(local) if self.stepped.contains(&local) => self.stepped_make(value, span), _ => value, }; + let value = self.locate_arguments(value); let value = match self.b.t.loc(span) { Some(loc) if effect => Expr::Located(loc, Box::new(value)), _ => value, @@ -214,11 +216,26 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { } _ => value, }); + if arg.node.place().is_some() + && let Some(value) = values.last().filter(|v| !matches!(v, Expr::Located(..))) + && let Some(loc) = self.b.t.loc(arg.span) + { + self.arguments.push((value.clone(), loc)); + } } self.into_frozen = saved; values } + /// `call` with each argument [`Self::operands`] read from a place at that argument's + /// own Rust. Std's mappings match the arguments' shapes, so they are given them bare, + /// and each is located where it appears as an argument of what they made. + pub(super) fn locate_arguments(&mut self, mut call: Expr) -> Expr { + let mut arguments = std::mem::take(&mut self.arguments); + locate(&mut call, &mut arguments); + call + } + /// An argument, which the callee copies if it changes it in place. fn operand_frozen(&mut self, op: &Operand<'tcx>, span: Span, out: &mut Vec) -> Expr { match op { @@ -2085,3 +2102,44 @@ fn assignable(e: &Expr) -> bool { e => e.is_path(), } } + +/// Locate each of `arguments` at the first argument of a call or `new` in `e` it is, and +/// drop it from the list. +fn locate(e: &mut Expr, arguments: &mut Vec<(Expr, ts::Loc)>) { + if arguments.is_empty() { + return; + } + match e { + Expr::Call(callee, args) | Expr::New(callee, args) => { + locate(callee, arguments); + for arg in args { + match arguments.iter().position(|(value, _)| value == arg) { + Some(i) => { + let (_, loc) = arguments.remove(i); + *arg = Expr::Located(loc, Box::new(std::mem::replace(arg, Expr::undefined()))); + } + None => locate(arg, arguments), + } + } + } + Expr::Member(e, _) + | Expr::Unary(_, e) + | Expr::Postfix(e, _) + | Expr::As(e, _) + | Expr::Instantiate(e, _) + | Expr::Lambda(_, e) + | Expr::Located(_, e) => locate(e, arguments), + Expr::Index(a, b) | Expr::Binary(a, _, b) | Expr::Assign(a, b) => { + locate(a, arguments); + locate(b, arguments); + } + Expr::Cond(a, b, c) => { + locate(a, arguments); + locate(b, arguments); + locate(c, arguments); + } + Expr::Array(es) => es.iter_mut().for_each(|e| locate(e, arguments)), + Expr::Object(props) => props.iter_mut().for_each(|(_, e)| locate(e, arguments)), + _ => {} + } +} diff --git a/ts/src/lower/mir/mod.rs b/ts/src/lower/mir/mod.rs index f0e3c2835..4ffeb0286 100644 --- a/ts/src/lower/mir/mod.rs +++ b/ts/src/lower/mir/mod.rs @@ -380,6 +380,9 @@ pub struct Fx<'m, 'b, 'a, 'tcx> { redirect: FxHashMap, /// Whether the operands lowered now go to a local nothing changes in place. into_frozen: bool, + /// Each argument read from a place for the call being lowered, and its Rust, which + /// [`Self::locate_arguments`] locates in the call once std's mappings made it. + arguments: Vec<(Expr, ts::Loc)>, /// Whether the value held back next is copied unless it folds into an argument. copy_on_flush: bool, /// Whether a copy lowered now shares what it copies, whatever it reads from. @@ -499,6 +502,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { rereadable_parts: None, redirect: FxHashMap::default(), into_frozen: false, + arguments: Vec::new(), copy_on_flush: false, force_share: false, arg_use: IndexVec::from_elem_n(false, n), diff --git a/ts/src/lower/mir/scalar.rs b/ts/src/lower/mir/scalar.rs index 013980bde..9c3bdf804 100644 --- a/ts/src/lower/mir/scalar.rs +++ b/ts/src/lower/mir/scalar.rs @@ -1921,6 +1921,7 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { let values = values.into_iter().map(Expr::typed).collect(); let mut pre = Vec::new(); let call = self.b.direct_call(instance, values, span, &mut pre); + let call = self.locate_arguments(call); self.emit(pre, false, out); match self.b.t.loc(span) { Some(loc) => Expr::Located(loc, Box::new(call)), From f4ae80d5159c3b4c6afb83c9129f5eadc36347dd Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 12:17:42 -0800 Subject: [PATCH 3/3] chore(quest): finish ts-argument-spans Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 1 - quest/m3/ts-argument-spans.md | 22 ---------------------- 2 files changed, 23 deletions(-) delete mode 100644 quest/m3/ts-argument-spans.md diff --git a/quest/m3/README.md b/quest/m3/README.md index 29f464044..aefd26bb9 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -36,7 +36,6 @@ generic std gap is planned here even when moq never calls it. - [Python's wake on the loop's own thread](/quest/m3/ffi-python-wake.md) - a wake on the event loop's thread resumes the future with no socket pair - [Python's call cost](/quest/m3/ffi-python-calls.md) - a call on an object costs what BoltFFI's does, or the gap is explained - [A map built with BuildHasherDefault](/quest/m3/ts-build-hasher-default.md) - `FxHashMap`-style maps, of `BuildHasherDefault` over a crate's own `Hasher`, translate -- [A name or an operator argument at its own Rust span](/quest/m3/ts-argument-spans.md) - a `tsc` error on a plain-name or operator argument names that argument's Rust, not the method it is passed to - [A panic's Location](/quest/m3/ts-panic-location.md) - `Location::caller` and `#[track_caller]` translate with native Rust's locations - [A panic hook](/quest/m3/ts-panic-hook.md) - `set_hook`, `take_hook`, and `PanicHookInfo` translate, every panic running the hook - [A string Cow's variant](/quest/m3/ts-cow-str-variant.md) - matching a `Cow`'s `Borrowed` or `Owned`, and `to_mut`'s change of it, translate as std's diff --git a/quest/m3/ts-argument-spans.md b/quest/m3/ts-argument-spans.md deleted file mode 100644 index d70afcd73..000000000 --- a/quest/m3/ts-argument-spans.md +++ /dev/null @@ -1,22 +0,0 @@ -# [S] A name or an operator argument at its own Rust span - -## Goal - -When `tsc` rejects an argument of a translated call that is a plain name or an operator, -[rust2-ts](/ts/README.md) reports the error at that argument's Rust, not at the method name of -the call it is passed to, proven by the `spans` fixture's snapshot. - -## Plan - -Left out of [#520](https://github.com/moq-dev/rust2/pull/520), which maps an argument made by -a call to its own Rust. In `Meter.scaled(m, rt.u32WrappingAdd(a, 1), ...)`, the receiver `m` -still maps to `scaled`, as does an argument such as `(a ^ b) >>> 0`. - -MIR gives each argument its own span (`Spanned`). Locating names and operators the -way calls are located needs the lowering passes that match on expression shapes, such as -`Expr::Ident`, `Expr::Member`, or `Expr::Binary`, to look through the location wrapper first. -Wrapping members and binaries naively changed what the formatting code spills into a variable -before its closure reads it, so the generated code changed. Prove the output text unchanged by -the snapshots, as #520 did, and measure the source map's growth on moq's translation. - -Public API: the translator's error spans.