Skip to content

Commit 7cb7e7a

Browse files
authored
[3/n] [daft-derive] attempt to better annotate semantic errors
Add some span info to try and annotate semantic errors better (such as highlighting the name of the field that's not diffable). This isn't perfect for now because we still produce some call-site errors, but at least we produce one error that's attributed directly to the field name so rust-analyzer should be happy. I tried adding more bounds but ran into rust-lang/rust#48214. Reviewers: andrewjstone Reviewed By: andrewjstone Pull Request: #60
1 parent f4fc719 commit 7cb7e7a

File tree

8 files changed

+197
-14
lines changed

8 files changed

+197
-14
lines changed

.github/workflows/ci.yml

+34-3
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,38 @@ jobs:
3838
build-and-test:
3939
name: Build and test
4040
runs-on: ${{ matrix.os }}
41+
strategy:
42+
matrix:
43+
os: [ubuntu-latest]
44+
fail-fast: false
45+
env:
46+
RUSTFLAGS: -D warnings
47+
CARGO_TERM_COLOR: always
48+
steps:
49+
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
50+
- name: Install toolchain in rust-toolchain.toml
51+
# GitHub Actions has an older rustup as of 2025-03-07 which doesn't accept
52+
# `rustup toolchain install` with no arguments. Update rustup first.
53+
run: rustup self update && rustup toolchain install
54+
- uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
55+
- uses: taiki-e/install-action@cargo-hack
56+
- uses: taiki-e/install-action@just
57+
- uses: taiki-e/install-action@nextest
58+
- name: Build
59+
run: just powerset build
60+
- name: Run tests
61+
run: just powerset nextest run
62+
- name: Doctests
63+
run: just powerset test --doc
64+
65+
build-and-test-msrv:
66+
name: Build and test (MSRV)
67+
runs-on: ${{ matrix.os }}
4168
strategy:
4269
matrix:
4370
os: [ubuntu-latest]
4471
# 1.81 is the MSRV
45-
rust-version: ["1.81", stable]
72+
rust-version: ["1.81"]
4673
fail-fast: false
4774
env:
4875
RUSTFLAGS: -D warnings
@@ -54,7 +81,11 @@ jobs:
5481
- uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
5582
- uses: taiki-e/install-action@cargo-hack
5683
- uses: taiki-e/install-action@just
84+
- uses: taiki-e/install-action@nextest
5785
- name: Build
5886
run: just powerset build
59-
- name: Test
60-
run: just powerset test
87+
# We don't run ui_test on the MSRV since compiler output varies by version.
88+
- name: Test without ui_test
89+
run: just powerset nextest run -E 'not binary(ui_test)'
90+
- name: Doctests
91+
run: just powerset test --doc

Justfile

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
set positional-arguments
2+
13
# Note: help messages should be 1 line long as required by just.
24

35
# Print a help message.
@@ -8,7 +10,7 @@ help:
810
powerset *args:
911
# Group third-party implementation features to avoid a full combinatorial
1012
# explosion -- we assume that they build independent of each other.
11-
cargo hack --feature-powerset --workspace {{args}} --group-features newtype-uuid1,oxnet01,uuid1 --ignore-unknown-features
13+
cargo hack --feature-powerset --workspace "$@" --group-features newtype-uuid1,oxnet01,uuid1 --ignore-unknown-features
1214

1315
# Build docs for crates and direct dependencies
1416
rustdoc *args:

daft-derive/src/internals/imp.rs

+10-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use super::error_store::{ErrorSink, ErrorStore};
22
use proc_macro2::{Span, TokenStream};
3-
use quote::{quote, ToTokens};
3+
use quote::{quote, quote_spanned, ToTokens};
44
use syn::{
5-
parse_quote, parse_str, visit::Visit, Attribute, Data, DataStruct,
6-
DeriveInput, Expr, Field, Fields, GenericParam, Generics, Index, Lifetime,
7-
LifetimeParam, Path, Token, WhereClause, WherePredicate,
5+
parse_quote, parse_quote_spanned, parse_str, spanned::Spanned,
6+
visit::Visit, Attribute, Data, DataStruct, DeriveInput, Expr, Field,
7+
Fields, GenericParam, Generics, Index, Lifetime, LifetimeParam, Path,
8+
Token, WhereClause, WherePredicate,
89
};
910

1011
pub struct DeriveDiffableOutput {
@@ -563,11 +564,11 @@ impl DiffFields {
563564
let mut f = f.clone();
564565

565566
f.ty = if config.mode == FieldMode::Leaf {
566-
parse_quote! {
567+
parse_quote_spanned! {f.span()=>
567568
#daft_crate::Leaf<&#lt #ty>
568569
}
569570
} else {
570-
parse_quote! {
571+
parse_quote_spanned! {f.span()=>
571572
<#ty as #daft_crate::Diffable>::Diff<#lt>
572573
}
573574
};
@@ -591,7 +592,7 @@ impl DiffFields {
591592
trait_bound: &syn::TraitBound,
592593
) -> WhereClause {
593594
let predicates = self.types().map(|ty| -> WherePredicate {
594-
parse_quote! {
595+
parse_quote_spanned! {ty.span()=>
595596
#ty: #trait_bound
596597
}
597598
});
@@ -627,14 +628,14 @@ fn generate_field_diffs(
627628
}
628629
};
629630
if config.mode == FieldMode::Leaf {
630-
quote! {
631+
quote_spanned! {f.span()=>
631632
#field_name: #daft_crate::Leaf {
632633
before: &self.#field_name,
633634
after: &other.#field_name
634635
}
635636
}
636637
} else {
637-
quote! {
638+
quote_spanned! {f.span()=>
638639
#field_name: #daft_crate::Diffable::diff(
639640
&self.#field_name,
640641
&other.#field_name

daft-derive/tests/fixtures/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ These fixtures ensure that:
2020

2121
Each file in `invalid` is automatically picked up by the snapshot and UI tests.
2222

23-
Like with valid fixtures, `snapshot_test.rs` tests all macro invocations annotated with `#[derive(Diffable)]`.
23+
Like with valid fixtures, `snapshot_test.rs` tests all macro invocations annotated with `#[derive(Diffable)]`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
struct MyStructDiff<'__daft> {
2+
a: <i32 as ::daft::Diffable>::Diff<'__daft>,
3+
b: <NonDiffable as ::daft::Diffable>::Diff<'__daft>,
4+
}
5+
impl<'__daft> ::std::fmt::Debug for MyStructDiff<'__daft>
6+
where
7+
<i32 as ::daft::Diffable>::Diff<'__daft>: ::std::fmt::Debug,
8+
<NonDiffable as ::daft::Diffable>::Diff<'__daft>: ::std::fmt::Debug,
9+
{
10+
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
11+
f.debug_struct(stringify!(MyStructDiff))
12+
.field(stringify!(a), &self.a)
13+
.field(stringify!(b), &self.b)
14+
.finish()
15+
}
16+
}
17+
impl<'__daft> ::std::cmp::PartialEq for MyStructDiff<'__daft>
18+
where
19+
<i32 as ::daft::Diffable>::Diff<'__daft>: ::std::cmp::PartialEq,
20+
<NonDiffable as ::daft::Diffable>::Diff<'__daft>: ::std::cmp::PartialEq,
21+
{
22+
fn eq(&self, other: &Self) -> bool {
23+
self.a == other.a && self.b == other.b
24+
}
25+
}
26+
impl<'__daft> ::std::cmp::Eq for MyStructDiff<'__daft>
27+
where
28+
<i32 as ::daft::Diffable>::Diff<'__daft>: ::std::cmp::Eq,
29+
<NonDiffable as ::daft::Diffable>::Diff<'__daft>: ::std::cmp::Eq,
30+
{}
31+
impl ::daft::Diffable for MyStruct {
32+
type Diff<'__daft> = MyStructDiff<'__daft> where Self: '__daft;
33+
fn diff<'__daft>(&'__daft self, other: &'__daft Self) -> MyStructDiff<'__daft> {
34+
Self::Diff {
35+
a: ::daft::Diffable::diff(&self.a, &other.a),
36+
b: ::daft::Diffable::diff(&self.b, &other.b),
37+
}
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use daft::Diffable;
2+
3+
struct NonDiffable {}
4+
5+
#[derive(Diffable)]
6+
struct MyStruct {
7+
a: i32,
8+
b: NonDiffable,
9+
}
10+
11+
fn main() {
12+
// MyStruct should still exist, even though the Diffable impl has errors.
13+
let _ = MyStruct { a: 0, b: NonDiffable {} };
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
error[E0277]: the trait bound `NonDiffable: Diffable` is not satisfied
2+
--> tests/fixtures/invalid/struct-field-not-diffable.rs:8:5
3+
|
4+
8 | b: NonDiffable,
5+
| ^ the trait `Diffable` is not implemented for `NonDiffable`
6+
|
7+
= help: the following other types implement trait `Diffable`:
8+
&'a T
9+
()
10+
(A, B)
11+
(A, B, C)
12+
(A, B, C, D)
13+
(A, B, C, D, E)
14+
(A, B, C, D, E, F)
15+
(A, B, C, D, E, F, G)
16+
and $N others
17+
18+
error[E0277]: the trait bound `NonDiffable: Diffable` is not satisfied
19+
--> tests/fixtures/invalid/struct-field-not-diffable.rs:5:10
20+
|
21+
5 | #[derive(Diffable)]
22+
| ^^^^^^^^ the trait `Diffable` is not implemented for `NonDiffable`
23+
|
24+
= help: the following other types implement trait `Diffable`:
25+
&'a T
26+
()
27+
(A, B)
28+
(A, B, C)
29+
(A, B, C, D)
30+
(A, B, C, D, E)
31+
(A, B, C, D, E, F)
32+
(A, B, C, D, E, F, G)
33+
and $N others
34+
= note: this error originates in the derive macro `Diffable` (in Nightly builds, run with -Z macro-backtrace for more info)
35+
36+
error[E0277]: the trait bound `NonDiffable: Diffable` is not satisfied in `MyStructDiff<'__daft>`
37+
--> tests/fixtures/invalid/struct-field-not-diffable.rs:5:10
38+
|
39+
5 | #[derive(Diffable)]
40+
| ^^^^^^^^ within `MyStructDiff<'__daft>`, the trait `Diffable` is not implemented for `NonDiffable`
41+
|
42+
= help: the following other types implement trait `Diffable`:
43+
&'a T
44+
()
45+
(A, B)
46+
(A, B, C)
47+
(A, B, C, D)
48+
(A, B, C, D, E)
49+
(A, B, C, D, E, F)
50+
(A, B, C, D, E, F, G)
51+
and $N others
52+
note: required because it appears within the type `MyStructDiff<'__daft>`
53+
--> tests/fixtures/invalid/struct-field-not-diffable.rs:5:10
54+
|
55+
5 | #[derive(Diffable)]
56+
| ^^^^^^^^
57+
note: required by a bound in `daft::Diffable::Diff`
58+
--> $WORKSPACE/daft/src/diffable.rs
59+
|
60+
| / type Diff<'daft>
61+
| | where
62+
| | Self: 'daft;
63+
| |____________________^ required by this bound in `Diffable::Diff`
64+
= note: this error originates in the derive macro `Diffable` (in Nightly builds, run with -Z macro-backtrace for more info)
65+
66+
error[E0277]: the trait bound `NonDiffable: Diffable` is not satisfied in `MyStructDiff<'__daft>`
67+
--> tests/fixtures/invalid/struct-field-not-diffable.rs:5:10
68+
|
69+
5 | #[derive(Diffable)]
70+
| ^^^^^^^^ within `MyStructDiff<'__daft>`, the trait `Diffable` is not implemented for `NonDiffable`
71+
|
72+
= help: the following other types implement trait `Diffable`:
73+
&'a T
74+
()
75+
(A, B)
76+
(A, B, C)
77+
(A, B, C, D)
78+
(A, B, C, D, E)
79+
(A, B, C, D, E, F)
80+
(A, B, C, D, E, F, G)
81+
and $N others
82+
note: required because it appears within the type `MyStructDiff<'__daft>`
83+
--> tests/fixtures/invalid/struct-field-not-diffable.rs:5:10
84+
|
85+
5 | #[derive(Diffable)]
86+
| ^^^^^^^^
87+
= note: the return type of a function must have a statically known size
88+
= note: this error originates in the derive macro `Diffable` (in Nightly builds, run with -Z macro-backtrace for more info)

rust-toolchain.toml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# We use a specific toolchain revision to ensure our tests - which
2+
# occasionally depend on specific compiler output - remain stable
3+
# for all developers until the toolchain is explicitly advanced.
4+
# The intent is to keep this updated as new stable versions are relased.
5+
6+
[toolchain]
7+
channel = "1.85.0"
8+
profile = "default"

0 commit comments

Comments
 (0)