diff --git a/quest/m3/README.md b/quest/m3/README.md index 3ff2ce178..20f254627 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -35,6 +35,5 @@ generic std gap is planned here even when moq never calls it. - [Fresh writes beside a zipped iterator that points at a part](/quest/m3/ts-fresh-shapes.md) - zipping slots with a `Vec<[T; N]>`, `vec![row; n]`, or `copied` of what the slot holds translates - [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 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-panic-hook.md b/quest/m3/ts-panic-hook.md index 7a01d1585..081ac0568 100644 --- a/quest/m3/ts-panic-hook.md +++ b/quest/m3/ts-panic-hook.md @@ -15,8 +15,12 @@ own too, such as an index out of bounds or an overflow, at the location std's `#[track_caller]` reports. The differential harness sets its own hook natively, so a case that sets one must leave the harness able to read the message. -Public API: the translator's output, and the runtime's `Panic`. - -## Required +The crate's own `#[track_caller]` functions already take their caller's `rt.Location` +(`ts/src/lower/caller.rs`); std's that map to the runtime, such as `unwrap`, `expect`, +indexing, and `RefCell::borrow_mut`, take none, as nothing reads it yet. Passing one to +each costs every hot call, so measure it on `just ts bench` and the moq codec, and weigh +passing it only where a hook is set. A panic inside std's own code, not under +`#[track_caller]`, is at a path in the native build's sysroot, which translation must name +as that build does. -- [A panic's Location](/quest/m3/ts-panic-location.md) - `Location::caller` and `#[track_caller]` translate +Public API: the translator's output, and the runtime's `Panic`. diff --git a/quest/m3/ts-panic-location.md b/quest/m3/ts-panic-location.md deleted file mode 100644 index 54ae8e753..000000000 --- a/quest/m3/ts-panic-location.md +++ /dev/null @@ -1,29 +0,0 @@ -# [L] A panic's Location - -## Goal - -[rust2-ts](/ts/README.md) translates `panic::Location`: `Location::caller` in the crate's -functions, `#[track_caller]` ones included, and `file`, `line`, `column`, `Display`, -`Debug`, and comparisons of what it gives, matching native Rust's values, proven by a -differential test. - -## Plan - -Split from the PR that translated the rest of `std::panic`, which refuses `Location` with a -span. Natively, `Location::caller` in a function without `#[track_caller]` is a constant of -its call's span, and in one with it, the location its caller passed, so a translation has to -thread that location through each call of such a function, as rustc's codegen does. - -Things to settle while building it: - -- The file path rustc reports, which must match the native build's. -- Calls that reach a `#[track_caller]` function indirectly: through a function pointer, a - trait object, or std's generic code calling the crate's trait impl. Refuse what can't - match, with a span. -- What MIR inlining does to the spans the translator reads. - -Public API: the translator's output. - -## Related - -- [A panic hook](/quest/m3/ts-panic-hook.md) - a hook reads each panic's `Location` diff --git a/ts/README.md b/ts/README.md index 44397174f..6acf9a64a 100644 --- a/ts/README.md +++ b/ts/README.md @@ -178,9 +178,16 @@ after one, as a wasm trap leaves it, and a caller that catches `rt.Panic` must n again. An abort without a panic, as std's for an `RwLock` locked again while held, throws `rt.Abort`, a `Panic` with std's abort message. So `catch_unwind` only ever returns `Ok`, `resume_unwind` throws `rt.Abort` without running the hook, and `panic_any` panics -with its payload if that is text, or `Box` as std's hook writes any other. A -`Location`, and with it `set_hook` and `take_hook`, is refused: translated panics carry -none. +with its payload if that is text, or `Box` as std's hook writes any other. +Translated panics carry no `Location`, so `set_hook` and `take_hook` are refused. + +`Location::caller` gives rustc's location: the file as the session names it, and line +and column. A function rustc passes its caller's location to, such as a `#[track_caller]` +one, takes it as an `rt.Location` last parameter, and so does a trait object's method +whose trait declares it so. A call the crate writes passes the call's location, or, in such +a function, its own caller's; a function pointer, or a trait object's method only the impl +tracks, passes the function's own, as rustc's shim does. One that std's code calls, +through its `Fn` impls, is refused, as its location is in std's source. Each integer type is its own type to `tsc`, named as Rust's: `rt.u8` is a `number` with a type-only brand, so a `u16` where a `u8` goes fails the type check, while a plain diff --git a/ts/fixtures/lang/panics/expected/core/bool.ts b/ts/fixtures/lang/panics/expected/core/bool.ts new file mode 100644 index 000000000..026013bde --- /dev/null +++ b/ts/fixtures/lang/panics/expected/core/bool.ts @@ -0,0 +1,12 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; + +export function then$142atvy(self: boolean, f: () => rt.Location): rt.Location | undefined { + if (!self) { + return undefined; + } + return f(); +} + +//# sourceMappingURL=bool.ts.map diff --git a/ts/fixtures/lang/panics/expected/core/cmp.ts b/ts/fixtures/lang/panics/expected/core/cmp.ts new file mode 100644 index 000000000..e94e93d02 --- /dev/null +++ b/ts/fixtures/lang/panics/expected/core/cmp.ts @@ -0,0 +1,13 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "../rt.ts"; + +export function min$1rvi5e9(v1: rt.Location, v2: rt.Location): rt.Location { + return rt.ordMin(v1, v2, rt.cmpLt(rt.locationCmp)); +} + +export function max$02i836o(v1: rt.Location, v2: rt.Location): rt.Location { + return rt.ordMax(v1, v2, rt.cmpLt(rt.locationCmp)); +} + +//# sourceMappingURL=cmp.ts.map diff --git a/ts/fixtures/lang/panics/expected/core/option.ts b/ts/fixtures/lang/panics/expected/core/option.ts index 0fb1b6278..a43f18933 100644 --- a/ts/fixtures/lang/panics/expected/core/option.ts +++ b/ts/fixtures/lang/panics/expected/core/option.ts @@ -9,4 +9,11 @@ export function unwrapOr$0r2bmpr(self: rt.u8 | undefined, default_: rt.u8): rt.u return self; } +export function mapOr$1e4a1e0(self: rt.Ordering | undefined, default_: rt.u8, f: (a0: rt.Ordering) => rt.u8): rt.u8 { + if (self === undefined) { + return default_; + } + return f(self); +} + //# sourceMappingURL=option.ts.map diff --git a/ts/fixtures/lang/panics/expected/lib.ts b/ts/fixtures/lang/panics/expected/lib.ts index 55b1b49fb..0b119cf93 100644 --- a/ts/fixtures/lang/panics/expected/lib.ts +++ b/ts/fixtures/lang/panics/expected/lib.ts @@ -2,7 +2,10 @@ import * as rt from "./rt.ts"; import type * as Any from "./core/any.Any.ts"; -import { wrapped, called, awaited, raised } from "./panics.ts"; +import { via$located_Tracked_u8, at$located_Tracked_u8, called as located_called, read, panicked } from "./located.ts"; +import * as Inherits from "./located.Inherits.ts"; +import * as Own from "./located.Own.ts"; +import { wrapped, called as panics_called, awaited, raised } from "./panics.ts"; import type * as Point from "./panics.Point.ts"; export function copy$panics_Point(v: Point.Point): Point.Point { @@ -23,6 +26,34 @@ export class of$String { } } +export class of$located_Inherits { + v: Inherits.Inherits; + + constructor(v: Inherits.Inherits) { + this.v = v; + } + + at$Tracked(caller: rt.Location): rt.Location { + return Inherits.at$located_Tracked(this.v, caller); + } + + via$Tracked(): rt.Location { + return Inherits.via$located_Tracked(this.v); + } +} + +export class of$located_Own { + v: Own.Own; + + constructor(v: Own.Own) { + this.v = v; + } + + at$Untracked(): rt.Location { + return Own.at$located_Untracked(this.v, { file: "fixtures/lang/panics/src/located.rs", line: 86, col: 5 }); + } +} + export class of$ref_str { v: string; @@ -37,6 +68,14 @@ export class of$u8 { constructor(v: rt.u8) { this.v = v; } + + at$Tracked(caller: rt.Location): rt.Location { + return at$located_Tracked_u8(this.v, caller); + } + + via$Tracked(): rt.Location { + return via$located_Tracked_u8(this.v); + } } /** @@ -44,7 +83,7 @@ export class of$u8 { * their count, so a case added at the end moves no other. */ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined][] { - return [["panics::wrapped", wrapped as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["panics::called", called as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["panics::awaited", awaited as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["panics::raised", raised as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; + return [["panics::wrapped", wrapped as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["panics::called", panics_called as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["panics::awaited", awaited as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["panics::raised", raised as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["located::called", located_called as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["located::read", read as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["located::panicked", panicked as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } //# sourceMappingURL=lib.ts.map diff --git a/ts/fixtures/lang/panics/expected/located.Held$read.ts b/ts/fixtures/lang/panics/expected/located.Held$read.ts new file mode 100644 index 000000000..96cfe6953 --- /dev/null +++ b/ts/fixtures/lang/panics/expected/located.Held$read.ts @@ -0,0 +1,26 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +export function fmt$read_fmt_Debug(self: Held, f: rt.Formatter): rt.Result { + const _3: string = "Held"; + const _4: string = "at"; + const _5: rt.Fmt = (f: rt.Formatter) => rt.fmtLocationDebug(self.at, f); + const _7: string = "maybe"; + const _8: rt.Fmt = (f: rt.Formatter) => rt.fmtOption(self.maybe, f, rt.fmtLocationDebug); + return rt.debugStructFinish(rt.debugStructField(rt.debugStructField(rt.fmtDebugStruct(f, _3), _4, _5), _7, _8)); +} + +export function eq$read_cmp_PartialEq(self: Held, other: Held): boolean { + if (!rt.locationEq(self.at, other.at)) { + return false; + } + return rt.eqOption(rt.locationEq)(self.maybe, other.maybe); +} + +export interface Held { + at: rt.Location; + maybe: rt.Location | undefined; +} + +//# sourceMappingURL=located.Held$read.ts.map diff --git a/ts/fixtures/lang/panics/expected/located.Inherits.ts b/ts/fixtures/lang/panics/expected/located.Inherits.ts new file mode 100644 index 000000000..8628ba0e9 --- /dev/null +++ b/ts/fixtures/lang/panics/expected/located.Inherits.ts @@ -0,0 +1,19 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { here } from "./located.ts"; + +/** A default, untracked, of the tracked method. */ +export function via$located_Tracked(self: Inherits): rt.Location { + return at$located_Tracked(self, { file: "fixtures/lang/panics/src/located.rs", line: 58, col: 14 }); +} + +/** Inherits the trait's `#[track_caller]`. */ +export interface Inherits { +} + +export function at$located_Tracked(_self: Inherits, caller: rt.Location): rt.Location { + return here(caller); +} + +//# sourceMappingURL=located.Inherits.ts.map diff --git a/ts/fixtures/lang/panics/expected/located.Own.ts b/ts/fixtures/lang/panics/expected/located.Own.ts new file mode 100644 index 000000000..6238ff25b --- /dev/null +++ b/ts/fixtures/lang/panics/expected/located.Own.ts @@ -0,0 +1,13 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +/** Only the impl tracks: a call through a trait object passes the impl's own location. */ +export interface Own { +} + +export function at$located_Untracked(_self: Own, caller: rt.Location): rt.Location { + return caller; +} + +//# sourceMappingURL=located.Own.ts.map diff --git a/ts/fixtures/lang/panics/expected/located.Places.ts b/ts/fixtures/lang/panics/expected/located.Places.ts new file mode 100644 index 000000000..b36e16e6a --- /dev/null +++ b/ts/fixtures/lang/panics/expected/located.Places.ts @@ -0,0 +1,13 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +/** Indexing that gives where it was indexed. */ +export interface Places { +} + +export function index$ops_Index_u8(_self: Places, _2: rt.u8, caller: rt.Location): rt.Location { + return caller; +} + +//# sourceMappingURL=located.Places.ts.map diff --git a/ts/fixtures/lang/panics/expected/located.Tracked.ts b/ts/fixtures/lang/panics/expected/located.Tracked.ts new file mode 100644 index 000000000..0f7f027d0 --- /dev/null +++ b/ts/fixtures/lang/panics/expected/located.Tracked.ts @@ -0,0 +1,8 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +export type Tracked = + | { at$Tracked: (a0: rt.Location) => rt.Location; via$Tracked: () => rt.Location }; + +//# sourceMappingURL=located.Tracked.ts.map diff --git a/ts/fixtures/lang/panics/expected/located.Untracked.ts b/ts/fixtures/lang/panics/expected/located.Untracked.ts new file mode 100644 index 000000000..e50dbc556 --- /dev/null +++ b/ts/fixtures/lang/panics/expected/located.Untracked.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +export type Untracked = { at$Untracked: () => rt.Location }; + +//# sourceMappingURL=located.Untracked.ts.map diff --git a/ts/fixtures/lang/panics/expected/located.ts b/ts/fixtures/lang/panics/expected/located.ts new file mode 100644 index 000000000..47fecf2e5 --- /dev/null +++ b/ts/fixtures/lang/panics/expected/located.ts @@ -0,0 +1,226 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { of$located_Inherits, of$located_Own, of$u8 } from "./lib.ts"; +import { then$142atvy } from "./core/bool.ts"; +import { min$1rvi5e9, max$02i836o } from "./core/cmp.ts"; +import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, mapOr$1e4a1e0 as option_mapOr$1e4a1e0 } from "./core/option.ts"; +import * as Held$read from "./located.Held$read.ts"; +import * as Inherits from "./located.Inherits.ts"; +import * as Own from "./located.Own.ts"; +import * as Places from "./located.Places.ts"; +import type * as Tracked from "./located.Tracked.ts"; +import type * as Untracked from "./located.Untracked.ts"; + +export function here(caller: rt.Location): rt.Location { + return caller; +} + +/** A chain, which passes on its own caller's location. */ +export function outer(caller: rt.Location): rt.Location { + return here(caller); +} + +export function twice(caller: rt.Location): [rt.Location, rt.Location] { + return [outer(caller), caller] as [rt.Location, rt.Location]; +} + +/** Not tracked: its own call of `here`. */ +export function plain(): rt.Location { + return here({ file: "fixtures/lang/panics/src/located.rs", line: 32, col: 5 }); +} + +export function picked(first: rt.u8, caller: rt.Location): rt.Location { + const _2: rt.u8 = rt.u8Rem(first, 3); + if (_2 === 0) { + return here(caller); + } + if (_2 === 1) { + return caller; + } + return plain(); +} + +/** A closure is no `#[track_caller]` function, so it gives its own calls'. */ +export function inClosure(_caller: rt.Location): [rt.Location, rt.Location] { + const _1: () => rt.Location = (): rt.Location => { + return { file: "fixtures/lang/panics/src/located.rs", line: 47, col: 18 }; + }; + const _2: () => rt.Location = (): rt.Location => { + return here({ file: "fixtures/lang/panics/src/located.rs", line: 48, col: 21 }); + }; + return [_1(), _2()] as [rt.Location, rt.Location]; +} + +/** A default, untracked, of the tracked method. */ +export function via$located_Tracked_u8(self: rt.u8): rt.Location { + return at$located_Tracked_u8(self, { file: "fixtures/lang/panics/src/located.rs", line: 58, col: 14 }); +} + +export function at$located_Tracked_u8(_self: rt.u8, caller: rt.Location): rt.Location { + return caller; +} + +export function generic$located_Inherits(t: Inherits.Inherits): rt.Location { + return Inherits.at$located_Tracked(t, { file: "fixtures/lang/panics/src/located.rs", line: 92, col: 7 }); +} + +export function generic$u8(t: rt.u8): rt.Location { + return at$located_Tracked_u8(t, { file: "fixtures/lang/panics/src/located.rs", line: 92, col: 7 }); +} + +/** Each location's `Display`, a line each. */ +export function lines(out: rt.ByteVec, at: rt.Location[]): void { + for (const _5 of at) { + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtLocationDisplay(_5, f)]; + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(rt.fmtFormat([args2[0], "\n"]))); + } +} + +/** Calls of `#[track_caller]` functions, and `Location::caller` itself. */ +export function called(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { + const _24: (a0: rt.u8) => rt.Location = (n: rt.u8): rt.Location => { + if (!(rt.u8Rem(n, 2) === 0)) { + return outer({ file: "fixtures/lang/panics/src/located.rs", line: 139, col: 61 }); + } + return here({ file: "fixtures/lang/panics/src/located.rs", line: 139, col: 45 }); + }; + const first: rt.u8 = option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data), 0); + const _7: rt.u8 = rt.u8Rem(kind, 6); + if (_7 === 0) { + const _11: [rt.Location, rt.Location] = twice({ file: "fixtures/lang/panics/src/located.rs", line: 131, col: 26 }); + lines(out, [{ file: "fixtures/lang/panics/src/located.rs", line: 134, col: 19 }, here({ file: "fixtures/lang/panics/src/located.rs", line: 134, col: 39 }), outer({ file: "fixtures/lang/panics/src/located.rs", line: 134, col: 47 }), _11[0], _11[1], plain(), picked(first, { file: "fixtures/lang/panics/src/located.rs", line: 134, col: 71 })]); + } else if (_7 === 1) { + const _23: [rt.Location, rt.Location] = inClosure({ file: "fixtures/lang/panics/src/located.rs", line: 138, col: 33 }); + lines(out, [_23[0], _23[1], _24(first), ((): rt.Location => { + return { file: "fixtures/lang/panics/src/located.rs", line: 140, col: 53 }; + })()]); + } else if (_7 === 2) { + const tracked: Tracked.Tracked = new of$u8(first); + const inherits: Tracked.Tracked = new of$located_Inherits({}); + const own: Untracked.Untracked = new of$located_Own({}); + lines(out, [at$located_Tracked_u8(first, { file: "fixtures/lang/panics/src/located.rs", line: 149, col: 27 }), via$located_Tracked_u8(first), Inherits.at$located_Tracked({}, { file: "fixtures/lang/panics/src/located.rs", line: 151, col: 30 }), generic$u8(first), generic$located_Inherits({}), tracked.at$Tracked({ file: "fixtures/lang/panics/src/located.rs", line: 154, col: 29 }), tracked.via$Tracked(), inherits.at$Tracked({ file: "fixtures/lang/panics/src/located.rs", line: 156, col: 30 }), own.at$Untracked(), Own.at$located_Untracked({}, { file: "fixtures/lang/panics/src/located.rs", line: 158, col: 25 }), Places.index$ops_Index_u8({}, first, { file: "fixtures/lang/panics/src/located.rs", line: 159, col: 28 })]); + } else if (_7 === 3) { + const g: () => rt.Location = (): rt.Location => { + return outer({ file: "fixtures/lang/panics/src/located.rs", line: 21, col: 1 }); + }; + const h: (a0: rt.u8) => rt.Location = (a: rt.u8): rt.Location => { + return picked(a, { file: "fixtures/lang/panics/src/located.rs", line: 36, col: 1 }); + }; + const fs: Array<() => rt.Location> = [(): rt.Location => { + return here({ file: "fixtures/lang/panics/src/located.rs", line: 15, col: 1 }); + }, plain]; + const own2: (a0: Own.Own) => rt.Location = (a2: Own.Own): rt.Location => { + return Own.at$located_Untracked(a2, { file: "fixtures/lang/panics/src/located.rs", line: 86, col: 5 }); + }; + const _73: rt.Location = ((): rt.Location => { + return here({ file: "fixtures/lang/panics/src/located.rs", line: 15, col: 1 }); + })(); + const _74: rt.Location = g(); + const _75: rt.Location = h(first); + const _78: rt.usize = rt.u8Rem(first, 2) as rt.usize; + if (!(_78 < 2)) { + rt.panic("index out of bounds: the len is " + 2 + " but the index is " + _78); + } + lines(out, [_73, _74, _75, fs[_78](), own2({})]); + } else if (_7 === 4) { + lines(out, [{ file: "fixtures/lang/panics/src/located.rs", line: 175, col: 32 }, { file: "fixtures/lang/panics/src/located.rs", line: 176, col: 31 }, THERE$called, { file: "fixtures/lang/panics/src/located.rs", line: 180, col: 44 }, here({ file: "fixtures/lang/panics/src/located.rs", line: 180, col: 49 })]); + } else { + lines(out, [here({ file: "fixtures/lang/panics/src/located.rs", line: 186, col: 23 }), here({ file: "fixtures/lang/panics/src/located.rs", line: 186, col: 34 }), here({ file: "fixtures/lang/panics/src/located.rs", line: 186, col: 45 })]); + const wide: string = "é😀 "; + const _105: rt.Location = here({ file: "fixtures/lang/panics/src/located.rs", line: 188, col: 49 }); + const _107: rt.Location = { file: "fixtures/lang/panics/src/located.rs", line: 188, col: 57 }; + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, wide), (f: rt.Formatter) => rt.fmtLocationDisplay(_105, f), (f: rt.Formatter) => rt.fmtLocationDisplay(_107, f)]; + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(rt.fmtFormat([args2[0], args2[1], args2[0], args2[2]]))); + } +} + +export const THERE$called: rt.Location = { file: "fixtures/lang/panics/src/located.rs", line: 177, col: 34 }; + +/** What a `Location` says of itself, and how locations compare. */ +export function read(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { + const first: rt.u8 = option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data), 0); + const a: rt.Location = here({ file: "fixtures/lang/panics/src/located.rs", line: 197, col: 13 }); + const b: rt.Location = picked(first, { file: "fixtures/lang/panics/src/located.rs", line: 198, col: 13 }); + const _9: rt.u8 = rt.u8Rem(kind, 4); + if (_9 === 0) { + let s: string = ""; + const _18: string = a.file; + const _20: rt.u32 = a.line; + const _22: rt.u32 = a.col; + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, _18), (f: rt.Formatter) => rt.u32Display(_20, f), (f: rt.Formatter) => rt.u32Display(_22, f)]; + const written = rt.fmtWriteString(s, [args2[0], "|", args2[1], "|", args2[2], "|"]); + s = written.s; + rt.resultUnwrap(written.result, rt.fmtErrorDebug); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtLocationDisplay(a, f), (f: rt.Formatter) => rt.fmtLocationDebug(a, f), (f: rt.Formatter) => rt.fmtLocationDebug(b, f)]; + const written2 = rt.fmtWriteString(s, [rt.fmtSpec({ align: ">", width: 60 }, args4[0]), "|", args4[1], "|", rt.fmtSpec({ alternate: true }, args4[2]), "|", rt.fmtSpec({ debugHex: "x" }, args4[2])]); + s = written2.s; + rt.resultUnwrap(written2.result, rt.fmtErrorDebug); + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(s)); + } else if (_9 === 1) { + const copy: rt.Location = a; + const same: rt.Location = here({ file: "fixtures/lang/panics/src/located.rs", line: 208, col: 24 }); + const _49: rt.u8 = rt.locationEq(a, b) ? 1 : 0; + const _53: rt.u8 = rt.locationEq(a, copy) ? 1 : 0; + const _56: rt.u8 = !rt.locationEq(a, same) ? 1 : 0; + const _60: rt.u8 = rt.locationCmp(a, b) === -1 ? 1 : 0; + const order = rt.locationCmp(a, same); + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(_49, _53, _56, _60, order !== undefined && order >= 0 ? 1 : 0, rt.locationCmp(a, b) & 0xff, option_mapOr$1e4a1e0(rt.locationCmp(a, same), 9, (o: rt.Ordering): rt.u8 => { + return o & 0xff; + }))), undefined, true); + lines(out, [max$02i836o(a, b), min$1rvi5e9(a, b)]); + } else if (_9 === 2) { + const _83: rt.Location = here({ file: "fixtures/lang/panics/src/located.rs", line: 221, col: 35 }); + const _84: rt.Location = outer({ file: "fixtures/lang/panics/src/located.rs", line: 221, col: 46 }); + const _85: rt.Location = picked(rt.u8WrappingAdd(first, 1), { file: "fixtures/lang/panics/src/located.rs", line: 221, col: 55 }); + const all: rt.Location[] = [b, _83, a, _84, _85]; + rt.sliceSort(all, rt.cmpLt(rt.locationCmp), 4, true); + rt.vecDedupBy(all, rt.locationEq); + lines(out, all); + const _96: rt.Iter = rt.iterSlice(all); + const set: rt.BTreeSet = rt.btreeExtendKeys(rt.btreeNew(rt.locationCmp) as rt.BTreeSet, _96, true, undefined, undefined, 4, true); + const _101: rt.Iter = rt.iterMap(rt.iterSlice(all), (l: rt.Location): rt.Location => { + return l; + }); + const hashed: rt.HashSet = rt.hashExtendKeys(rt.hashNew(rt.hashing(rt.locationHash, rt.locationEq)) as rt.HashSet, _101, undefined, undefined, 58720256); + rt.iterExtendBytes(out, rt.iterSlice(Uint8Array.of(rt.btreeLen(set) & 0xff, rt.hashLen(hashed) & 0xff, rt.hashContains(hashed, a) ? 1 : 0)), undefined, true); + } else { + const _117: rt.Location | undefined = then$142atvy(rt.u8Rem(first, 2) === 0, (): rt.Location => { + return here({ file: "fixtures/lang/panics/src/located.rs", line: 238, col: 49 }); + }); + const held: Held$read.Held = { at: a, maybe: _117 }; + const _127: rt.u8 = Held$read.eq$read_cmp_PartialEq(held, held) ? 1 : 0; + const args6: rt.Fmt[] = [(f: rt.Formatter) => Held$read.fmt$read_fmt_Debug(held, f), (f: rt.Formatter) => rt.u8Display(_127, f)]; + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(rt.fmtFormat([args6[0], "|", args6[1]]))); + } +} + +/** Panics whose message carries where a `#[track_caller]` function was called. */ +export function panicked(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { + const first: rt.u8 = option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data), 0); + if (rt.u8Rem(kind, 2) === 0) { + check$panicked(rt.u8Rem(first, 3) !== 0, { file: "fixtures/lang/panics/src/located.rs", line: 257, col: 14 }); + } else { + const f: (a0: boolean) => undefined = ((a: boolean): void => { + return check$panicked(a, { file: "fixtures/lang/panics/src/located.rs", line: 250, col: 5 }); + }) as (a0: boolean) => undefined; + const _15: (a0: boolean) => undefined = ((a2: boolean): void => { + return check$panicked(a2, { file: "fixtures/lang/panics/src/located.rs", line: 250, col: 5 }); + }) as (a0: boolean) => undefined; + const _18: rt.usize = rt.u8Rem(first, 2) as rt.usize; + if (!(_18 < 2)) { + rt.panic("index out of bounds: the len is " + 2 + " but the index is " + _18); + } + [_15, f][_18](rt.u8Rem(first, 3) !== 1); + } + rt.byteVecPush(out, first); +} + +export function check$panicked(ok: boolean, caller: rt.Location): void { + if (!ok) { + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtLocationDisplay(caller, f)]; + rt.panicFmt(["failed at ", args2[0]]); + } +} + +//# sourceMappingURL=located.ts.map diff --git a/ts/fixtures/lang/panics/src/lib.rs b/ts/fixtures/lang/panics/src/lib.rs index 45f096144..09204c1f1 100644 --- a/ts/fixtures/lang/panics/src/lib.rs +++ b/ts/fixtures/lang/panics/src/lib.rs @@ -3,6 +3,7 @@ #[path = "../../harness.rs"] pub mod harness; +mod located; mod panics; #[cfg(target_arch = "wasm32")] #[path = "../../../wasm.rs"] @@ -20,5 +21,8 @@ fn cases() -> Vec<(&'static str, Case)> { ("panics::called", panics::called), ("panics::awaited", panics::awaited), ("panics::raised", panics::raised), + ("located::called", located::called), + ("located::read", located::read), + ("located::panicked", located::panicked), ] } diff --git a/ts/fixtures/lang/panics/src/located.rs b/ts/fixtures/lang/panics/src/located.rs new file mode 100644 index 000000000..ac9ff87c2 --- /dev/null +++ b/ts/fixtures/lang/panics/src/located.rs @@ -0,0 +1,265 @@ +//! `panic::Location`: `Location::caller` in a function and in a `#[track_caller]` one, +//! through chains of them, closures, trait methods called directly, generically, through +//! `Index`, and through trait objects whose trait or only whose impl tracks its caller, +//! function pointers, constants, and macros; its accessors, `Display`, `Debug`, comparisons, and +//! hash; and a panic whose message carries one. + +use std::collections::{BTreeSet, HashSet}; +use std::fmt::Write; +use std::ops::Index; +use std::panic::Location; + +type Here = &'static Location<'static>; + +#[track_caller] +const fn here() -> Here { + Location::caller() +} + +/// A chain, which passes on its own caller's location. +#[track_caller] +fn outer() -> Here { + here() +} + +#[track_caller] +fn twice() -> (Here, Here) { + (outer(), Location::caller()) +} + +/// Not tracked: its own call of `here`. +fn plain() -> Here { + here() +} + +#[track_caller] +fn picked(first: u8) -> Here { + match first % 3 { + 0 => here(), + 1 => Location::caller(), + _ => plain(), + } +} + +/// A closure is no `#[track_caller]` function, so it gives its own calls'. +#[track_caller] +fn in_closure() -> (Here, Here) { + let own = || Location::caller(); + let called = || here(); + (own(), called()) +} + +trait Tracked { + #[track_caller] + fn at(&self) -> Here; + + /// A default, untracked, of the tracked method. + fn via(&self) -> Here { + self.at() + } +} + +impl Tracked for u8 { + fn at(&self) -> Here { + Location::caller() + } +} + +/// Inherits the trait's `#[track_caller]`. +struct Inherits; + +impl Tracked for Inherits { + fn at(&self) -> Here { + here() + } +} + +trait Untracked { + fn at(&self) -> Here; +} + +/// Only the impl tracks: a call through a trait object passes the impl's own location. +struct Own; + +impl Untracked for Own { + #[track_caller] + fn at(&self) -> Here { + Location::caller() + } +} + +fn generic(t: &T) -> Here { + t.at() +} + +/// Indexing that gives where it was indexed. +struct Places; + +impl Index for Places { + type Output = Location<'static>; + + #[track_caller] + fn index(&self, _: u8) -> &Location<'static> { + Location::caller() + } +} + +macro_rules! called { + () => { + here() + }; +} + +macro_rules! nested { + () => { + called!() + }; +} + +/// Each location's `Display`, a line each. +fn lines(out: &mut Vec, at: &[Here]) { + for at in at { + out.extend_from_slice(format!("{at}\n").as_bytes()); + } +} + +/// Calls of `#[track_caller]` functions, and `Location::caller` itself. +pub fn called(kind: u8, data: &[u8], out: &mut Vec) { + let first = data.first().copied().unwrap_or(0); + match kind % 6 { + 0 => { + let (a, b) = twice(); + lines( + out, + &[Location::caller(), here(), outer(), a, b, plain(), picked(first)], + ); + } + 1 => { + let (own, called) = in_closure(); + let f = |n: u8| if n % 2 == 0 { here() } else { outer() }; + lines(out, &[own, called, f(first), (|| Location::caller())()]); + } + 2 => { + let tracked: &dyn Tracked = &first; + let inherits: &dyn Tracked = &Inherits; + let own: &dyn Untracked = &Own; + lines( + out, + &[ + first.at(), + first.via(), + Inherits.at(), + generic(&first), + generic(&Inherits), + tracked.at(), + tracked.via(), + inherits.at(), + own.at(), + Own.at(), + &Places[first], + ], + ); + } + 3 => { + // A function pointer passes the function's own location. + let f: fn() -> Here = here; + let g = outer as fn() -> Here; + let h: fn(u8) -> Here = picked; + let fs: [fn() -> Here; 2] = [here, plain]; + let own: fn(&Own) -> Here = ::at; + lines(out, &[f(), g(), h(first), fs[usize::from(first % 2)](), own(&Own)]); + } + 4 => { + // A constant's and a static's are where its value calls, and a function item held + // in a variable is called where the call is. + const HERE: Here = Location::caller(); + const VIA: Here = here(); + static THERE: Here = Location::caller(); + let f = Location::caller; + let g = here; + lines(out, &[HERE, VIA, THERE, f(), g()]); + } + _ => { + // A macro's call is where the outermost macro was called; a format argument is + // where it is written, even past chars of other widths. + let wide = "é😀\u{3000}"; + let at = [called!(), nested!(), here()]; + lines(out, &at); + let s = format!("{wide}{}{wide}{}", here(), Location::caller()); + out.extend_from_slice(s.as_bytes()); + } + } +} + +/// What a `Location` says of itself, and how locations compare. +pub fn read(kind: u8, data: &[u8], out: &mut Vec) { + let first = data.first().copied().unwrap_or(0); + let a = here(); + let b = picked(first); + match kind % 4 { + 0 => { + let mut s = String::new(); + write!(s, "{}|{}|{}|", a.file(), a.line(), a.column()).unwrap(); + write!(s, "{a:>60}|{a:?}|{b:#?}|{b:x?}").unwrap(); + out.extend_from_slice(s.as_bytes()); + } + 1 => { + let copy: Location<'static> = *a; + let same = here(); + out.extend([ + u8::from(a == b), + u8::from(*a == copy), + u8::from(a != same), + u8::from(a < b), + u8::from(a.ge(&same)), + a.cmp(&b) as u8, + a.partial_cmp(&same).map_or(9, |o| o as u8), + ]); + lines(out, &[std::cmp::max(a, b), std::cmp::min(a, b)]); + } + 2 => { + let mut all = vec![b, here(), a, outer(), picked(first.wrapping_add(1))]; + all.sort(); + all.dedup(); + lines(out, &all); + let set: BTreeSet = all.iter().copied().collect(); + let hashed: HashSet> = all.iter().map(|l| **l).collect(); + out.extend([set.len() as u8, hashed.len() as u8, u8::from(hashed.contains(a))]); + } + _ => { + // Held in a struct and an `Option`, as a value of any type is. + #[derive(Debug, Clone, Copy, PartialEq)] + struct Held { + at: Here, + maybe: Option, + } + let held = Held { + at: a, + maybe: (first % 2 == 0).then(|| here()), + }; + let copy = held; + let s = format!("{held:?}|{}", u8::from(copy == held)); + out.extend_from_slice(s.as_bytes()); + } + } +} + +/// Panics whose message carries where a `#[track_caller]` function was called. +pub fn panicked(kind: u8, data: &[u8], out: &mut Vec) { + #[track_caller] + fn check(ok: bool) { + if !ok { + panic!("failed at {}", Location::caller()); + } + } + let first = data.first().copied().unwrap_or(0); + match kind % 2 { + 0 => check(first % 3 != 0), + _ => { + let f: fn(bool) = check; + let fs: [fn(bool); 2] = [check, f]; + fs[usize::from(first % 2)](first % 3 != 1); + } + } + out.push(first); +} diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 81d527c46..c8035ec5a 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -101,10 +101,20 @@ fixtures/reject/src/objects.rs:generic_object+0:23: `dyn objects::Scaled`, a fixtures/reject/src/objects.rs:generic_object+1:4: `dyn objects::Scaled`, a trait object of the generic trait `objects::Scaled`, which is not supported yet fixtures/reject/src/objects.rs:raw_dyn_iter+2:40: a coercion of `*mut std::ops::Range` to `*mut dyn std::iter::Iterator`, whose object would hold the value there as it is made, not as it is read, which is not supported yet fixtures/reject/src/objects.rs:same_named+2:30: `u8` as `dyn objects::second::Named`, whose class has another trait's method named `name$Named`, which is not supported yet -fixtures/reject/src/panics.rs:caller+1:2: a panic's `Location`, which needs each `#[track_caller]` function's caller, which is not supported yet -fixtures/reject/src/panics.rs:caller+1:33: a panic's `Location`, which needs each `#[track_caller]` function's caller, which is not supported yet +fixtures/reject/src/panics.rs:call+1:2: `panics::here`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source +fixtures/reject/src/panics.rs:call+1:2: a call through std's code of `panics::here`, a `#[track_caller]` function, whose caller is in std's source +fixtures/reject/src/panics.rs:caller+1:37: `panics::at`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source +fixtures/reject/src/panics.rs:caller+1:37: `panics::at`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source, in `std::option::Option::::map` +fixtures/reject/src/panics.rs:caller+1:37: a call through std's code of `panics::at`, a `#[track_caller]` function, whose caller is in std's source, in `std::option::Option::::map` +fixtures/reject/src/panics.rs:caller+2:38: `panics::at`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source +fixtures/reject/src/panics.rs:caller+3:22: `panics::here`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source fixtures/reject/src/panics.rs:hooked+1:13: `panic::take_hook`, whose hook reads each panic's `Location`, which panics don't carry yet fixtures/reject/src/panics.rs:hooked+2:2: `panic::set_hook`, whose hook reads each panic's `Location`, which panics don't carry yet +fixtures/reject/src/panics.rs:impl Drop for Counted+2:2: `::drop`, a `#[track_caller]` function std's code calls, whose caller is in std's source +fixtures/reject/src/panics.rs:impl Iterator for Counted+4:2: `::next`, a `#[track_caller]` function std's code calls, whose caller is in std's source +fixtures/reject/src/panics.rs:pointed+1:24: `std::panic::Location::<'a>::caller`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source +fixtures/reject/src/panics.rs:pointed+2:6: unsupported call to `std::panic::Location::<'_>::file_as_c_str` +fixtures/reject/src/panics.rs:pointed+2:22: unsupported call to `std::ffi::CStr::count_bytes` fixtures/reject/src/parts.rs:array_part+1:9: a raw pointer to `std::cell::UnsafeCell` in `parts::InArray`, which the crate may put another object in place of fixtures/reject/src/parts.rs:array_part_replaced+3:33: a reference through which a raw pointer may be made into `parts::InArray` in `[parts::InArray; 2]`, which the crate may put another object in place of fixtures/reject/src/parts.rs:element_replaced+2:20: a reference through which a raw pointer may be made into `std::cell::UnsafeCell` in `[std::cell::UnsafeCell; 2]`, which the crate may put another object in place of diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index d3d67ca5a..10a510a00 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -103,10 +103,20 @@ fixtures/reject/src/objects.rs:generic_object+0:23: `dyn objects::Scaled`, a fixtures/reject/src/objects.rs:generic_object+1:4: `dyn objects::Scaled`, a trait object of the generic trait `objects::Scaled`, which is not supported yet fixtures/reject/src/objects.rs:raw_dyn_iter+2:40: a coercion of `*mut std::ops::Range` to `*mut dyn std::iter::Iterator`, whose object would hold the value there as it is made, not as it is read, which is not supported yet fixtures/reject/src/objects.rs:same_named+2:30: `u8` as `dyn objects::second::Named`, whose class has another trait's method named `name$Named`, which is not supported yet -fixtures/reject/src/panics.rs:caller+1:2: a panic's `Location`, which needs each `#[track_caller]` function's caller, which is not supported yet -fixtures/reject/src/panics.rs:caller+1:33: a panic's `Location`, which needs each `#[track_caller]` function's caller, which is not supported yet +fixtures/reject/src/panics.rs:call+1:2: `panics::here`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source +fixtures/reject/src/panics.rs:call+1:2: a call through std's code of `panics::here`, a `#[track_caller]` function, whose caller is in std's source +fixtures/reject/src/panics.rs:caller+1:37: `panics::at`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source +fixtures/reject/src/panics.rs:caller+1:37: `panics::at`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source, in `std::option::Option::::map` +fixtures/reject/src/panics.rs:caller+1:37: a call through std's code of `panics::at`, a `#[track_caller]` function, whose caller is in std's source, in `std::option::Option::::map` +fixtures/reject/src/panics.rs:caller+2:38: `panics::at`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source +fixtures/reject/src/panics.rs:caller+3:22: `panics::here`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source fixtures/reject/src/panics.rs:hooked+1:13: `panic::take_hook`, whose hook reads each panic's `Location`, which panics don't carry yet fixtures/reject/src/panics.rs:hooked+2:2: `panic::set_hook`, whose hook reads each panic's `Location`, which panics don't carry yet +fixtures/reject/src/panics.rs:impl Drop for Counted+2:2: `::drop`, a `#[track_caller]` function std's code calls, whose caller is in std's source +fixtures/reject/src/panics.rs:impl Iterator for Counted+4:2: `::next`, a `#[track_caller]` function std's code calls, whose caller is in std's source +fixtures/reject/src/panics.rs:pointed+1:24: `std::panic::Location::<'a>::caller`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source +fixtures/reject/src/panics.rs:pointed+2:6: unsupported call to `std::panic::Location::<'_>::file_as_c_str` +fixtures/reject/src/panics.rs:pointed+2:22: unsupported call to `std::ffi::CStr::count_bytes` fixtures/reject/src/parts.rs:array_part+1:9: a raw pointer to `std::cell::UnsafeCell` in `parts::InArray`, which the crate may put another object in place of fixtures/reject/src/parts.rs:array_part_replaced+3:33: a reference through which a raw pointer may be made into `parts::InArray` in `[parts::InArray; 2]`, which the crate may put another object in place of fixtures/reject/src/parts.rs:element_replaced+2:20: a reference through which a raw pointer may be made into `std::cell::UnsafeCell` in `[std::cell::UnsafeCell; 2]`, which the crate may put another object in place of diff --git a/ts/fixtures/reject/src/panics.rs b/ts/fixtures/reject/src/panics.rs index f5a04074b..9c7358c80 100644 --- a/ts/fixtures/reject/src/panics.rs +++ b/ts/fixtures/reject/src/panics.rs @@ -1,10 +1,62 @@ -//! `std::panic` that needs where a panic or a call is: a `Location`, and the hook that reads -//! one. +//! `std::panic` that needs where a call is in std's source: a `#[track_caller]` function +//! that std's code calls, through its `Fn` impls, and the hook that reads each panic's +//! `Location`. + +use std::panic::Location; + +type Here = &'static Location<'static>; + +#[track_caller] +fn here() -> Here { + Location::caller() +} -/// The caller each `#[track_caller]` function is given. #[track_caller] -pub fn caller() -> u32 { - std::panic::Location::caller().line() +fn at(_: u8) -> Here { + Location::caller() +} + +fn call(f: impl Fn() -> Here) -> Here { + f() +} + +/// A `#[track_caller]` function std's code calls, which natively passes a location in +/// std's source. +pub fn caller(data: &[u8]) -> u32 { + let mapped = data.first().copied().map(at).map_or(0, Location::line); + let iterated = data.iter().copied().map(at).count() as u32; + mapped + iterated + call(here).column() +} + +/// `Location::caller` as a pointer, whose shim passes its location in std's source, and a +/// file as a C string, which has no JS value. +pub fn pointed() -> usize { + let f: fn() -> Here = Location::caller; + f().file_as_c_str().count_bytes() +} + +/// An iterator whose `next` std's adaptors call, and a value whose `drop` std's glue calls. +struct Counted(u8); + +impl Iterator for Counted { + type Item = u8; + + #[track_caller] + fn next(&mut self) -> Option { + self.0 = self.0.checked_sub(1)?; + Some(Location::caller().line() as u8) + } +} + +impl Drop for Counted { + #[track_caller] + fn drop(&mut self) { + self.0 = Location::caller().column() as u8; + } +} + +pub fn iterated(n: u8) -> u32 { + Counted(n).map(u32::from).sum() } /// A hook reads each panic's `Location`. diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index cf09f96f4..aa04d8a0a 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -89,6 +89,8 @@ roots = [ "objects::same_named", "panics::caller", "panics::hooked", + "panics::iterated", + "panics::pointed", "parts::array_part_replaced", "parts::element_replaced", "parts::field_part_replaced", diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index e594aacb7..ce6680475 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -22,6 +22,7 @@ export * from "./rt/float.ts"; export * from "./rt/hash.ts"; export * from "./rt/int.ts"; export * from "./rt/iter.ts"; +export * from "./rt/location.ts"; export * from "./rt/rand.ts"; export * from "./rt/sort.ts"; export * from "./rt/str.ts"; diff --git a/ts/runtime/rt/location.ts b/ts/runtime/rt/location.ts new file mode 100644 index 000000000..cb7c38443 --- /dev/null +++ b/ts/runtime/rt/location.ts @@ -0,0 +1,46 @@ +// `core::panic::Location`, for rust2-ts output: where a `#[track_caller]` function was +// called, which never changes once made, so values share it. `rt.ts` re-exports all of it. + +import { type FmtResult, type Formatter, fmtDebugStructFields, fmtPad, fmtStr, fmtWriteFmt } from "../rt.ts"; +import { cmp, cmpStr, type Ordering } from "./collections.ts"; +import { type Hasher, hash32, hashStr } from "./hash.ts"; +import { type u32, u32Debug, u32Display } from "./int.ts"; + +/** `core::panic::Location`: the file as rustc names it, and the line and column from 1. */ +export interface Location { + readonly file: string; + readonly line: u32; + readonly col: u32; +} + +/** `PartialEq::eq`: the same column, line, and file. */ +export function locationEq(a: Location, b: Location): boolean { + return a.col === b.col && a.line === b.line && a.file === b.file; +} + +/** `Ord::cmp`: by file, then line, then column. */ +export function locationCmp(a: Location, b: Location): Ordering { + return cmpStr(a.file, b.file) || cmp(a.line, b.line) || cmp(a.col, b.col); +} + +/** `Hash::hash`: its file, line, and column, as std's impl writes them. */ +export function locationHash(l: Location, s: Hasher): void { + hashStr(l.file, s); + hash32(l.line, s); + hash32(l.col, s); +} + +/** `Display`: `file:line:column`, a part at a time, which no width pads, as std's `write!` writes it. */ +export function fmtLocationDisplay(l: Location, f: Formatter): FmtResult { + return fmtWriteFmt(f, [(f) => fmtPad(f, l.file), ":", (f) => u32Display(l.line, f), ":", (f) => u32Display(l.col, f)]); +} + +/** `Debug`, as std's: a struct of the file, line, and column. */ +export function fmtLocationDebug(l: Location, f: Formatter): FmtResult { + return fmtDebugStructFields( + f, + "Location", + ["file", "line", "column"], + [(f) => fmtStr(l.file, f), (f) => u32Debug(l.line, f), (f) => u32Debug(l.col, f)], + ); +} diff --git a/ts/src/lower/body.rs b/ts/src/lower/body.rs index a7ee5ae2b..49a3ffd84 100644 --- a/ts/src/lower/body.rs +++ b/ts/src/lower/body.rs @@ -19,6 +19,9 @@ pub struct Body<'b, 'a, 'tcx> { /// Where the function hands back its `Ok`'s parts, by [`Translator::slotted`], until /// its own body takes it. pub slotted: Option>, + /// The function the call being lowered calls, if it takes its caller's location, and + /// the location the call passes, until a call of it takes it. + pub site: Option<(Instance<'tcx>, Expr)>, } impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { @@ -28,6 +31,7 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { names: FxHashSet::default(), parted: Vec::new(), slotted: None, + site: None, } } @@ -102,6 +106,10 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { if let ty::InstanceKind::Virtual(method, _) = instance.def && self.t.dispatches(instance) { + let mut args = args; + if super::caller::tracks(self.t, instance) { + args.push(self.caller_arg(instance, span)); + } return self.virtual_call(method, gargs.type_at(0), args, span); } // An `async` body's `Future::poll`, which its future has itself. @@ -116,8 +124,12 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { && self.t.lowers(self.t.typing_env_pub(), instance) { let values: Vec = args.into_iter().map(Expr::typed).collect(); - // An `async fn` that only makes its future: the future, made here. + // An `async fn` that only makes its future: the future, made here. Its body is + // the future's, so its caller's location is never read. if let Some(class) = super::mir::coroutine::of_async_fn(self.t, instance, span) { + if self.site.as_ref().is_some_and(|(at, _)| *at == instance) { + self.site = None; + } return Expr::New(Box::new(Expr::Sym(class)), values); } let call = self.direct_call(instance, values, span, out); @@ -262,10 +274,13 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { out: &mut Vec, ) -> Expr { let parted = self.t.parted(instance); - let values = match parted.is_empty() { + let mut values = match parted.is_empty() { true => values, false => self.slice_args(values, &parted, out), }; + if super::caller::tracks(self.t, instance) { + values.push(self.caller_arg(instance, span)); + } Expr::Sym(self.t.callee(instance, span)).call(values) } @@ -357,6 +372,17 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { } // A binding's export, or a function whose body translates, is named directly. if let Ok(Some(instance)) = Instance::try_resolve(tcx, self.t.typing_env_pub(), def_id, gargs) { + // Std's `Fn` impls call it from their own source, whose location it would take, as + // would `Location::caller` itself. + if super::caller::tracks(self.t, instance) || super::caller::is_caller(tcx, def_id) { + let path = tcx.def_path_str(def_id); + return self.err( + span, + format!( + "`{path}`, a `#[track_caller]` function, as a value std's code calls, whose caller is in std's source" + ), + ); + } let sym = match self.t.bound_fn(instance, span) { Some(sym) => Some(sym), // One that takes byte slices as their parts, hands back its `Ok`'s parts, or is a diff --git a/ts/src/lower/caller.rs b/ts/src/lower/caller.rs new file mode 100644 index 000000000..f0559b941 --- /dev/null +++ b/ts/src/lower/caller.rs @@ -0,0 +1,121 @@ +//! `#[track_caller]`, as rustc's codegen passes it: each function that requires its +//! caller's `panic::Location` takes it as a last parameter, and each call the crate +//! writes of one passes the location of the call, or, in a function that takes one +//! itself, the one its own caller passed. A function pointer to one, or a trait object's +//! method that only its impl tracks, passes the function's own location, as rustc's shim +//! does. A call through std's code passes a location in std's source, which translation +//! can't name, so it is refused. + +use rustc_hir::def_id::DefId; +use rustc_middle::ty::{Instance, InstanceKind, TyCtxt}; +use rustc_span::{RemapPathScopeComponents, Span}; + +use super::Translator; +use super::body::Body; +use super::library::{self, Std}; +use crate::ts::{Expr, Key, Param, StmtKind, Type}; + +/// The `rt.Location` rustc gives a call at `span`: where the outermost macro call it came +/// from is, in the file as the session names it, by line and column from 1. +pub fn location(tcx: TyCtxt<'_>, span: Span) -> Expr { + let topmost = span.ctxt().outer_expn().expansion_cause().unwrap_or(span); + let pos = tcx.sess.source_map().lookup_char_pos(topmost.lo()); + let file = pos + .file + .name + .display(RemapPathScopeComponents::MACRO) + .to_string_lossy() + .into_owned(); + object(file, pos.line, pos.col_display + 1) +} + +/// The `rt.Location` of `file`, `line`, and `col`. +pub fn object(file: String, line: impl ToString, col: impl ToString) -> Expr { + Expr::Object(vec![ + (Key::Name("file".into()), Expr::Str(file)), + (Key::Name("line".into()), Expr::lit(line.to_string())), + (Key::Name("col".into()), Expr::lit(col.to_string())), + ]) +} + +/// Whether a call of `instance` passes its caller's location: the crate's own, as std's +/// read it only to say where they panic, which translated panics do not carry. +pub fn tracks<'tcx>(t: &Translator<'_, 'tcx>, instance: Instance<'tcx>) -> bool { + t.crates.translates(instance.def_id()) && instance.def.requires_caller_location(t.tcx) +} + +/// Whether `def_id` is `Location::caller`, which gives the location its call passes. +pub fn is_caller(tcx: TyCtxt<'_>, def_id: DefId) -> bool { + library::inherent_std(tcx, def_id) == Some(Std::Location) && tcx.item_name(def_id).as_str() == "caller" +} + +/// Whether a call of the crate's trait method `def_id` through a trait object passes its +/// caller's location, as its declaration's `#[track_caller]` says. +pub fn tracks_virtual(t: &Translator<'_, '_>, def_id: DefId) -> bool { + t.crates.translates(def_id) && InstanceKind::Virtual(def_id, 0).requires_caller_location(t.tcx) +} + +impl<'tcx> Body<'_, '_, 'tcx> { + /// The location a call of `instance`, which takes its caller's, passes: the one the + /// crate's call of it gave. + pub fn caller_arg(&mut self, instance: Instance<'tcx>, span: Span) -> Expr { + match self.site.take() { + Some((at, location)) if at == instance => location, + site => { + self.site = site; + let path = self.t.tcx.def_path_str(instance.def_id()); + self.err( + span, + format!( + "a call through std's code of `{path}`, a `#[track_caller]` function, whose caller is in std's source" + ), + ) + } + } + } + + /// The function `instance` as a pointer, which passes its own location, as rustc's + /// shim does, or `None` if it takes none. + pub fn reified(&mut self, instance: Instance<'tcx>, span: Span) -> Option { + let tcx = self.t.tcx; + if !tracks(self.t, instance) { + return None; + } + let sig = self.t.sig(instance.def_id(), instance.args); + let params: Vec = (sig.inputs().iter()) + .map(|input| Param { + name: self.fresh("a"), + ty: Some(self.t.ty(*input, span)), + }) + .collect(); + let site = (instance, location(tcx, tcx.def_span(instance.def_id()))); + let outer = self.site.replace(site); + let mut body = Vec::new(); + let values = params.iter().map(|p| Expr::ident(&p.name)).collect(); + let call = self.call_def(instance.def_id(), instance.args, values, span, &mut body); + self.site = outer; + body.push(StmtKind::Return(Some(call.typed())).at(None)); + // Declared, so its `()` is `void`, which a value's type says is `undefined`. + let ret = self.t.ret_ty(sig.output(), span); + let arrow = Expr::Arrow(params, ret, body); + Some(match sig.output().is_unit() { + true => Expr::As(Box::new(arrow), self.t.fn_ty(sig.inputs(), sig.output(), span)), + false => arrow, + }) + } +} + +/// Refuse `instance`, which std's code calls, if it takes its caller's location, which is +/// then in std's source. +pub fn refuse_std_call<'tcx>(t: &mut Translator<'_, 'tcx>, instance: Instance<'tcx>) { + if tracks(t, instance) { + let path = t.tcx.def_path_str(instance.def_id()); + let msg = format!("`{path}`, a `#[track_caller]` function std's code calls, whose caller is in std's source"); + t.err(t.tcx.def_span(instance.def_id()), msg); + } +} + +/// The type of the parameter a `#[track_caller]` function takes its caller's location by. +pub fn param_ty() -> Type { + Type::Rt("Location", Vec::new()) +} diff --git a/ts/src/lower/collections.rs b/ts/src/lower/collections.rs index 0a7216c25..154680600 100644 --- a/ts/src/lower/collections.rs +++ b/ts/src/lower/collections.rs @@ -2547,7 +2547,10 @@ fn std_key<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>) -> bool { ty::Adt(..) if let Some((inner, _)) = cell_of(b, ty) => held(inner), ty::Adt(_, args) if derived_std(b, ty) => args.types().all(held), ty::Adt(..) if b.t.std_of(ty).is_some_and(Std::record) => true, - ty::Adt(..) => library::string(b.t.tcx, ty) || matches!(b.t.std_of(ty), Some(Std::Ordering | Std::Duration)), + ty::Adt(..) => { + library::string(b.t.tcx, ty) + || matches!(b.t.std_of(ty), Some(Std::Ordering | Std::Duration | Std::Location)) + } _ => false, } } @@ -2772,6 +2775,7 @@ pub fn cmp_fn<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, partial: bool, spa ty::Adt(..) if library::string(tcx, ty) => Expr::Rt("cmpStr".into()), ty::Adt(..) if b.t.std_of(ty) == Some(Std::Ordering) => Expr::Rt("cmp".into()), ty::Adt(..) if b.t.std_of(ty) == Some(Std::Duration) => Expr::Rt("durationCmp".into()), + ty::Adt(..) if b.t.std_of(ty) == Some(Std::Location) => Expr::Rt("locationCmp".into()), _ if library::nothing(tcx, ty) => Expr::Rt("cmpNothing".into()), ty::Tuple(tys) => { let fns = tys.iter().map(|t| cmp_fn(b, t, partial, span)).collect(); @@ -2847,6 +2851,7 @@ pub fn eq_fn<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, span: Span) -> Expr Expr::Rt("same".into()) } ty::Adt(..) if b.t.std_of(ty) == Some(Std::Duration) => Expr::Rt("durationEq".into()), + ty::Adt(..) if b.t.std_of(ty) == Some(Std::Location) => Expr::Rt("locationEq".into()), ty::Tuple(tys) => { let fns = tys.iter().map(|t| eq_fn(b, t, span)).collect(); Expr::rt("eqTuple", vec![Expr::Array(fns)]) @@ -3078,6 +3083,7 @@ fn hash_fn<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, hasher: Ty<'tcx>, spa // Its derived `Hash` writes its `i8` discriminant. ty::Adt(..) if b.t.std_of(ty) == Some(Std::Ordering) => Expr::Rt("hashI8".into()), ty::Adt(..) if b.t.std_of(ty) == Some(Std::Duration) => Expr::Rt("durationHash".into()), + ty::Adt(..) if b.t.std_of(ty) == Some(Std::Location) => Expr::Rt("locationHash".into()), _ if library::nothing(tcx, ty) => Expr::Rt("hashNothing".into()), ty::Tuple(tys) => { let fns = tys.iter().map(|t| hash_fn(b, t, hasher, span)).collect(); diff --git a/ts/src/lower/drop.rs b/ts/src/lower/drop.rs index 40f005fc1..287094064 100644 --- a/ts/src/lower/drop.rs +++ b/ts/src/lower/drop.rs @@ -648,6 +648,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } let mut steps = Vec::new(); if let Some(dtor) = adt.destructor(tcx) { + super::caller::refuse_std_call(self, Instance::mono(tcx, dtor.did)); let sym = self.instance(Instance::mono(tcx, dtor.did)); steps.push(StmtKind::Expr(Expr::Sym(sym).call(vec![Expr::This])).at(None)); } diff --git a/ts/src/lower/dynamic.rs b/ts/src/lower/dynamic.rs index 7b997d48f..f590483bf 100644 --- a/ts/src/lower/dynamic.rs +++ b/ts/src/lower/dynamic.rs @@ -228,7 +228,10 @@ impl<'tcx> Translator<'_, 'tcx> { let def_id = method.def_id; let sig = self.sig(def_id, method_args(tcx, def_id, ty)); // The methods it holds are declared, so their `()` is `void`. - let params = sig.inputs()[1..].iter().map(|t| self.ty(*t, span)).collect(); + let mut params: Vec = sig.inputs()[1..].iter().map(|t| self.ty(*t, span)).collect(); + if super::caller::tracks_virtual(self, def_id) { + params.push(super::caller::param_ty()); + } let func = Type::Func(params, Box::new(self.ret_ty(sig.output(), span))); let object = self.objects.get_mut(&principal).expect("a made object"); object.methods[index].ty = Some(func); @@ -329,14 +332,32 @@ impl<'tcx> Translator<'_, 'tcx> { return; }; let mut b = Body::new(self); - let params: Vec = sig.inputs()[1..] + let mut params: Vec = sig.inputs()[1..] .iter() .map(|input| Param { name: b.fresh("a"), ty: Some(b.t.ty(*input, span)), }) .collect(); - let values = params.iter().map(|p| Expr::ident(&p.name)); + let values: Vec = params.iter().map(|p| Expr::ident(&p.name)).collect(); + // The impl takes the location the object's caller passed, or, if only it tracks its + // caller, its own, as rustc's shim passes. + if let Ok(Some(imp)) = Instance::try_resolve(tcx, b.t.typing_env_pub(), method.def_id, args) + && super::caller::tracks(b.t, imp) + { + let location = match super::caller::tracks_virtual(b.t, method.def_id) { + true => { + let caller = b.fresh("caller"); + params.push(Param { + name: caller.clone(), + ty: Some(super::caller::param_ty()), + }); + Expr::ident(caller) + } + false => super::caller::location(tcx, tcx.def_span(imp.def_id())), + }; + b.site = Some((imp, location)); + } let mut body = Vec::new(); let call = b.call_def( method.def_id, diff --git a/ts/src/lower/fmt.rs b/ts/src/lower/fmt.rs index 5bb0b6e38..20644b790 100644 --- a/ts/src/lower/fmt.rs +++ b/ts/src/lower/fmt.rs @@ -560,6 +560,8 @@ pub fn std_impl<'tcx>( (Std::Utf8Chunk, Trait::Debug) => rt("utf8ChunkDebug", vec![value, f]), (Std::Duration, Trait::Debug) => Expr::rt("durationDebug", vec![value, f]), (Std::Layout, Trait::Debug) => rt("layoutDebug", vec![value, f]), + (Std::Location, Trait::Debug) => rt("locationDebug", vec![value, f]), + (Std::Location, Trait::Display) => rt("locationDisplay", vec![value, f]), (Std::LayoutError, Trait::Debug) => rt("layoutErrorDebug", vec![value, f]), (Std::LayoutError, Trait::Display) => rt("layoutErrorDisplay", vec![value, f]), (Std::Bytes, Trait::Debug) => Expr::rt("bytesDebug", vec![value, f]), diff --git a/ts/src/lower/iter.rs b/ts/src/lower/iter.rs index e87805d4d..0daa3e282 100644 --- a/ts/src/lower/iter.rs +++ b/ts/src/lower/iter.rs @@ -510,6 +510,7 @@ fn own_collect<'tcx>( }); let vec = Ty::new_adt(tcx, tcx.adt_def(vec), args); let instance = own_instance(b, source, "Iterator", "collect", &[vec])?; + super::caller::refuse_std_call(b.t, instance); Some((vec, Expr::Sym(b.t.instance(instance)))) } @@ -558,6 +559,7 @@ fn own_methods<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, owned: bool, span ); continue; } + super::caller::refuse_std_call(b.t, instance); let mut sym = Expr::Sym(b.t.instance(instance)); let args: &[&str] = match *name { "size_hint" | "len" | "count" | "last" | "next_back" => &[], @@ -642,6 +644,7 @@ fn through_place<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, f: Expr, args: fn own_back<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, iter: Expr) -> Option { let instance = own_method(b, ty, "DoubleEndedIterator", "next_back")?; let item = b.t.iter_item(ty.peel_refs())?; + super::caller::refuse_std_call(b.t, instance); let sym = b.t.instance(instance); let none = b.t.none_arg(item); let option = Expr::Sym(sym).call(vec![iter]); @@ -652,6 +655,7 @@ fn own_back<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, iter: Expr) -> Optio pub fn next<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, iter: Expr) -> Expr { match own_next(b, ty) { Some(instance) => { + super::caller::refuse_std_call(b.t, instance); let sym = b.t.instance(instance); Expr::Sym(sym).call(vec![iter]) } @@ -766,6 +770,7 @@ fn peeled_into_iter<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, value: Expr, not_bound(b, ty, span); return Some(match own_next(b, ty) { Some(instance) => { + super::caller::refuse_std_call(b.t, instance); let next = Expr::Sym(b.t.instance(instance)); // One owned whose `next` reads its place is moved into a place of its own. let placed = owned && held(b, ty); diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index ff734f269..b3c658785 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -189,6 +189,8 @@ pub enum Std { Duration, /// `rt.Layout`: its size and alignment, never changed once made. Layout, + /// `rt.Location`: a panic's file, line, and column, never changed once made. + Location, /// `rt.LayoutError`. LayoutError, /// `bytes::Bytes`: an `rt.ByteSlice`, as `&[u8]` is. @@ -320,6 +322,7 @@ impl Std { { ["str", "error", "Utf8Error"] => Some(Self::Utf8Error), ["alloc", "layout", "Layout"] => Some(Self::Layout), + ["panic", "location", "Location"] => Some(Self::Location), ["alloc", "layout", "LayoutError"] => Some(Self::LayoutError), ["num", "error", "ParseIntError"] => Some(Self::ParseIntError), ["num", "error", "IntErrorKind"] @@ -509,22 +512,6 @@ pub fn wrapped<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { } } -/// Why a `Location` is refused. -pub const LOCATION: &str = - "a panic's `Location`, which needs each `#[track_caller]` function's caller, which is not supported yet"; - -/// Whether `did` is std's `panic::Location`. -pub fn location(tcx: TyCtxt<'_>, did: DefId) -> bool { - matches!( - std_path(tcx, did) - .iter() - .map(String::as_str) - .collect::>() - .as_slice(), - ["panic", "location", "Location"] - ) -} - /// What `ty` holds if it is an `AssertUnwindSafe`. pub fn unwind_safe<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { match ty.kind() { @@ -2711,12 +2698,14 @@ fn mapping<'tcx>( // so nothing drops it. ("panic", "catch_unwind") => return Some(Expr::result(true, args.pop()?.call(Vec::new()))), ("panic", "resume_unwind") => Expr::rt("resumeUnwind", Vec::new()), - ("Location", _) - if impl_self(tcx, call.def_id) - .is_some_and(|t| matches!(t.kind(), ty::Adt(adt, _) if location(tcx, adt.did()))) => - { - return Some(b.err(span, LOCATION)); - } + // The location the call passes, as a `#[track_caller]` function's call does. + ("Location", _) if inherent_std(tcx, call.def_id) != Some(Std::Location) => return None, + ("Location", "caller") => { + let instance = Instance::try_resolve(tcx, b.t.typing_env_pub(), call.def_id, call.args).ok()??; + b.caller_arg(instance, span) + } + ("Location", "file" | "line") => args.pop()?.member(name.as_str()), + ("Location", "column") => args.pop()?.member("col"), ("panicking", "set_hook" | "take_hook") => { let why = format!("`panic::{name}`, whose hook reads each panic's `Location`, which panics don't carry yet"); diff --git a/ts/src/lower/mir/mod.rs b/ts/src/lower/mir/mod.rs index 4ffeb0286..629b0fb9e 100644 --- a/ts/src/lower/mir/mod.rs +++ b/ts/src/lower/mir/mod.rs @@ -60,6 +60,14 @@ pub fn function<'a, 'tcx>(t: &mut Translator<'a, 'tcx>, instance: Instance<'tcx> b.slotted = slotted.clone(); let mut fx = Fx::new(&mut b, &mir, Env::Plain, span); let mut params = fx.params(); + if crate::lower::caller::tracks(fx.b.t, instance) { + let caller = fx.b.fresh("caller"); + params.push(Param { + name: caller.clone(), + ty: Some(crate::lower::caller::param_ty()), + }); + fx.caller = Some(caller); + } let body = fx.lower(); fx.mark_unread_parts(&mut params, &body); let ret_span = fx.local_span(mir::RETURN_PLACE); @@ -340,6 +348,8 @@ pub struct Fx<'m, 'b, 'a, 'tcx> { pub mir: &'m mir::Body<'tcx>, pub tcx: TyCtxt<'tcx>, env: Env, + /// The parameter a `#[track_caller]` function takes its caller's location by. + caller: Option, slots: IndexVec>, /// Where each local assigned once is assigned, by a statement. def_at: IndexVec>, @@ -533,6 +543,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { remade: FxHashSet::default(), part_params: Vec::new(), slot, + caller: None, slotted_calls: FxHashMap::default(), returning: FxHashSet::default(), narrowing, @@ -2682,6 +2693,25 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { .collect() } + /// The function a call of `func` at `span` calls, if it takes its caller's location, + /// and the location the call passes: its own caller's, if this function takes one, or + /// else the call's own. + fn site(&self, func: &Operand<'tcx>, span: Span) -> Option<(Instance<'tcx>, Expr)> { + let ty::FnDef(def_id, args) = *func.ty(&self.mir.local_decls, self.tcx).kind() else { + return None; + }; + let env = self.b.t.typing_env_pub(); + let instance = Instance::try_resolve(self.tcx, env, def_id, args.skip_binder()).ok()??; + if !(crate::lower::caller::is_caller(self.tcx, def_id) || crate::lower::caller::tracks(self.b.t, instance)) { + return None; + } + let location = match &self.caller { + Some(caller) => Expr::ident(caller), + None => crate::lower::caller::location(self.tcx, span), + }; + Some((instance, location)) + } + fn terminator(&mut self, term: &mir::Terminator<'tcx>, out: &mut Vec) -> Exit { let span = term.source_info.span; match &term.kind { @@ -2768,7 +2798,17 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { fn_span, .. } => { + // Where rustc's codegen says the call is: from the callee's name on. + self.b.site = self.site(func, *fn_span); self.call(func, args, *destination, *fn_span, out); + if let Some((instance, _)) = self.b.site.take() + && self.b.t.crates.translates(instance.def_id()) + { + let path = self.tcx.def_path_str(instance.def_id()); + let msg = + format!("a call of `{path}`, a `#[track_caller]` function, that passes no caller's location"); + self.err(span, msg); + } match target { Some(_) if self.returning.contains(&self.at) => { self.flush(out); diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index 137643ef1..147c7bbd5 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -8,7 +8,7 @@ use rustc_abi::{BackendRepr, FieldIdx, Size, VariantIdx}; use rustc_data_structures::fx::FxHashSet; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, DefIndex}; -use rustc_middle::mir::interpret::{AllocId, GlobalAlloc, GlobalId, Scalar, alloc_range}; +use rustc_middle::mir::interpret::{AllocId, ConstAllocation, GlobalAlloc, GlobalId, Scalar, alloc_range}; use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::{self, ConstValue, Local, Operand, Place, PlaceTy, ProjectionElem}; use rustc_middle::ty::{self, Instance, Ty, TyCtxt, TypeVisitableExt}; @@ -1616,6 +1616,13 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { ); }; let unnamed = format!("a constant of type `{ty}`, a pointer into memory translation can't name"); + // A `Location`, as `Location::caller` gives a constant. + if self.b.t.std_of(*inner) == Some(Std::Location) { + return match self.location_const(alloc, offset, *inner) { + Some(location) => location, + None => self.err(span, unnamed), + }; + } let ty::Array(elem, len) = inner.kind() else { return self.err(span, unnamed); }; @@ -1649,6 +1656,35 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { } } + /// The `Location` of type `ty` at `at` in `alloc`: its file, the `str` another + /// allocation holds, and its line and column. + fn location_const(&self, alloc: ConstAllocation<'tcx>, at: Size, ty: Ty<'tcx>) -> Option { + let tcx = self.tcx; + let layout = tcx.layout_of(self.b.t.typing_env_pub().as_query_input(ty)).ok()?; + let field = |i: usize| at + layout.fields.offset(i); + let read = |at: Size, size: Size, ptr: bool| alloc.inner().read_scalar(&tcx, alloc_range(at, size), ptr).ok(); + let size = tcx.data_layout.pointer_size(); + let Scalar::Ptr(file, _) = read(field(0), size, true)? else { + return None; + }; + let len = read(field(0) + size, size, false)? + .to_target_usize(&tcx) + .discard_err()?; + let u32_size = Size::from_bytes(4); + let line = read(field(1), u32_size, false)?.to_u32().discard_err()?; + let col = read(field(2), u32_size, false)?.to_u32().discard_err()?; + let (prov, start) = file.prov_and_relative_offset(); + let GlobalAlloc::Memory(text) = tcx.global_alloc(prov.alloc_id()) else { + return None; + }; + let start = start.bytes_usize(); + let bytes = text + .inner() + .inspect_with_uninit_and_ptr_outside_interpreter(start..start + len as usize); + let file = std::str::from_utf8(bytes).ok()?.to_string(); + Some(crate::lower::caller::object(file, line, col)) + } + /// The value a `ty` that is no aggregate holds at `offset` in `alloc_id`, as rustc would /// give it outside of memory: nothing, a scalar, or a slice from the start of another. fn loaded(&self, alloc_id: AllocId, offset: Size, ty: Ty<'tcx>) -> Option { @@ -1694,7 +1730,11 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { self.bare_closure(Ty::new_closure(tcx, def_id, instance.args), span) } ty::InstanceKind::Item(def_id) | ty::InstanceKind::Shim(ty::ShimKind::Reify(def_id, _)) => { - self.b.fn_value(def_id, instance.args, span) + let item = Instance::new_raw(def_id, instance.args); + match self.b.reified(item, span) { + Some(reified) => reified, + None => self.b.fn_value(def_id, instance.args, span), + } } // A closure coerced to a function pointer calls it once. ty::InstanceKind::Shim(ty::ShimKind::ClosureOnce { .. }) => match instance.args.type_at(0).kind() { diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 3677ce642..63e6e0519 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -426,6 +426,15 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let value = self.operand(op, span, out); self.raw_unsize(value, from, span) } + // A `#[track_caller]` function's pointer passes the function's own location. + PointerCoercion::ReifyFnPointer(_) + if let ty::FnDef(def_id, args) = *from.kind() + && let Ok(Some(item)) = + Instance::try_resolve(self.tcx, self.b.t.typing_env_pub(), def_id, args.skip_binder()) + && let Some(reified) = self.b.reified(item, span) => + { + reified + } PointerCoercion::Unsize | PointerCoercion::ReifyFnPointer(_) | PointerCoercion::ClosureFnPointer(_) diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index 159baa4e2..b5a270392 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -9,6 +9,7 @@ mod alloc; mod bind; pub use bind::check_module; mod body; +mod caller; mod collections; mod consts; mod copy; @@ -1782,6 +1783,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { library::Std::RandomState => Type::Rt("RandomState", vec![]), library::Std::Duration => Type::Rt("Duration", vec![]), library::Std::Layout => Type::Rt("Layout", vec![]), + library::Std::Location => Type::Rt("Location", vec![]), library::Std::LayoutError => Type::Rt("LayoutError", vec![]), library::Std::Bytes => Type::Rt("ByteSlice", vec![]), library::Std::BytesMut => Type::Rt("BytesMut", vec![]), @@ -1817,10 +1819,6 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { { return Type::Rt("Iter", vec![self.ty(item, span)]); } - if library::location(tcx, adt.did()) { - self.unrepresented(span, library::LOCATION.into()); - return Type::Builtin("unknown"); - } if !self.crates.translates(adt.did()) { self.unrepresented( span, diff --git a/ts/src/program.rs b/ts/src/program.rs index dda019a87..997456f3a 100644 --- a/ts/src/program.rs +++ b/ts/src/program.rs @@ -24,6 +24,7 @@ pub const RUNTIME: &[(&str, &str)] = &[ ("rt/hash.ts", include_str!("../runtime/rt/hash.ts")), ("rt/int.ts", include_str!("../runtime/rt/int.ts")), ("rt/iter.ts", include_str!("../runtime/rt/iter.ts")), + ("rt/location.ts", include_str!("../runtime/rt/location.ts")), ("rt/rand.ts", include_str!("../runtime/rt/rand.ts")), ("rt/sort.ts", include_str!("../runtime/rt/sort.ts")), ("rt/str.ts", include_str!("../runtime/rt/str.ts")),