Skip to content

Better output printing.#143

Merged
Liam-DeVoe merged 10 commits intomainfrom
DRMacIver/named-draws
Apr 7, 2026
Merged

Better output printing.#143
Liam-DeVoe merged 10 commits intomainfrom
DRMacIver/named-draws

Conversation

@DRMacIver
Copy link
Copy Markdown
Member

This introduces draw_named as the primitive for when we are actually printing, which provides much nicer output for values.

It then makes the hegel::test macro automatically rewrite draw() to draw_named where it is safe to do so.

The result is that if you have a test that looks something like:

use hegel::{generators as gs};

#[hegel::test]
fn my_test(tc: hegel::TestCase){
    let mut running_total = 0;

    let iters = tc.draw(gs::integers().min_value(0).max_value(10));

    for _ in 0..iters {
        let summand = tc.draw(gs::integers().min_value(0).max_value(100));
        running_total += summand;
        assert!(running_total <= 100);
    }
}

You will see output like:

let iters = 2;
let summand_1 = 1;
let summand_2 = 100;
thread 'my_test' (2) panicked at examples/new_format.rs:12:9:
assertion failed: running_total <= 100

If you were to write it like this without the intermediate variable:

use hegel::{generators as gs};

#[hegel::test]
fn my_test(tc: hegel::TestCase){
    let mut running_total = 0;

    let iters = tc.draw(gs::integers().min_value(0).max_value(10));

    for _ in 0..iters {
        running_total += tc.draw(gs::integers().min_value(0).max_value(100));
        assert!(running_total <= 100);
    }
}

You would see:

let iters = 2;
let unnamed_1 = 1;
let unnamed_2 = 100;
thread 'my_test' (2) panicked at examples/new_format.rs:11:9:
assertion failed: running_total <= 100

Users are able to name things directly if they want. It's a bit unergonomic but not awful. e.g.

use hegel::{generators as gs};

#[hegel::test]
fn my_test(tc: hegel::TestCase){
    let mut running_total = 0;

    let iters = tc.draw(gs::integers().min_value(0).max_value(10));

    for _ in 0..iters {
        running_total += tc.draw_named(gs::integers().min_value(0).max_value(100), "summand", true);
        assert!(running_total <= 100);
    }
}

is equivalent to the first example.

@DRMacIver DRMacIver marked this pull request as ready for review March 27, 2026 10:58
@Liam-DeVoe
Copy link
Copy Markdown
Member

Liam-DeVoe commented Mar 30, 2026

My initial reaction: I think this is too much magic right now and we will regret going down this path. Doing AST-rewriting into draw_named for better output is clever, but I'd prefer to start with rewriting into something less magic, for example printing the file and line number in a format amenable to jump-to-definition cmd+click in an editor.

@DRMacIver
Copy link
Copy Markdown
Member Author

My initial reaction: I think this is too much magic right now and we will regret going down this path. Doing AST-rewriting into draw_named for better output is clever, but I'd prefer to start with rewriting into something less magic, for example printing the file and line number in a format amenable to jump-to-definition cmd+click in an editor.

I think this is a relatively modest amount of magic for a huge ergonomics improvement. It is a well-defined rewrite between two public facing APIs that fails gracefully when it cannot be applied, and in exchange it gives you stable names for all your drawn values, which is most of the upside of the up front method that we lost with the inline method.

I think it's also a precondition for good usability on #144, so I'm extremely keen on getting it in.

@DRMacIver
Copy link
Copy Markdown
Member Author

To put it into context: I think this is less magic than we routinely perform in Hypothesis, and it gives us better ergonomics than the equivalent features in Hypothesis.

@DRMacIver DRMacIver force-pushed the DRMacIver/named-draws branch 4 times, most recently from 253e769 to 27698f8 Compare April 4, 2026 01:44
DRMacIver and others added 7 commits April 5, 2026 17:34
This introduces draw_named as the primitive for when we are actually
printing, which provides much nicer output for values.

It then makes the hegel::test macro automatically rewrite draw()
to draw_named where it is safe to do so.
- Remove explicit 'a lifetime in is_tc_draw_binding (clippy::needless_lifetimes)
- Move NOTE text outside ```no_run code fence in draw() doc comment
- Allow clippy::let_and_return in closure test (needed for macro rewrite testing)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The candidate += 1 path in record_named_draw was only exercised by a
TempRustProject test (separate process), so it didn't count for
library coverage. This inline test directly exercises the skip logic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@DRMacIver DRMacIver force-pushed the DRMacIver/named-draws branch from 27698f8 to 8fbf6d5 Compare April 5, 2026 16:34
@Liam-DeVoe
Copy link
Copy Markdown
Member

I'm going through this now, but one thought at the top of my mind - I don't want draw_named to be part of our public API surface yet. I'd prefer it to be __draw_named so it doesn't show up in autocomplete when someone types tc.draw.

Copy link
Copy Markdown
Member

@Liam-DeVoe Liam-DeVoe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGMT! Excited to get this in.

I renamed draw_named to __draw_named, because I don't want to make this API public yet. Happy to debate that when you're back 🙂

Did a huge cleanup pass of the tests. They now assert stronger statements about the actual printed lines.

Fixed one bug - we previously had this test:

#[test]
fn test_macro_top_level_same_name_panics() {
    let code = r#"
use hegel::generators as gs;

#[hegel::test(test_cases = 1)]
fn test_dup(tc: hegel::TestCase) {
    let x = tc.draw(gs::booleans());
    let x = tc.draw(gs::booleans());
}
"#;
    TempRustProject::new()
        .test_file("test_dup.rs", code)
        .expect_failure(r#"draw_named.*"_x".*more than once"#)
        .cargo_test(&["--test", "test_dup"]);
}

which should clearly be passing instead with names x_1 and x_2.

@Liam-DeVoe Liam-DeVoe merged commit 0bfc3a1 into main Apr 7, 2026
14 checks passed
@Liam-DeVoe Liam-DeVoe deleted the DRMacIver/named-draws branch April 7, 2026 02:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants