1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/*!
Trybuild compile-fail goldens for the 3.0 macro changes.
These cover the new attribute validations:
- `ttl` + `ttl_millis`, `ttl` + `ttl_secs`, and `ttl_secs` + `ttl_millis`
mutual exclusion (the 3-way exclusion) on all three macros.
- the old `ttl = <integer>` whole-seconds form rejected with the migration
message pointing at `ttl_secs`/`ttl_millis`, on all three macros.
- `ttl_millis = 0` rejection (#149) on all three macros.
- `expires` + `ttl_millis` mutual exclusion (#149) on all three macros.
- an unparseable `ttl` Duration expression on all three macros.
- an unparseable `force_refresh` expression on all three macros (#146).
- a generic (type-param) function and a generic `in_impl` method without
`key`/`convert` (the generic rejection, #80) on both `#[cached]` and
`#[concurrent_cached]`.
- a const-generic function without `key`/`convert` on both `#[cached]` and
`#[concurrent_cached]` (const params have the same static-naming problem as
type params and are now rejected with the same message).
- `in_impl = true` on an associated function without a `self` receiver (the
`in_impl`-requires-self rejection) on all three macros.
- a `create` block combined with `ttl_millis` (the create-conflict rejection,
#149) on both `#[cached]` and `#[concurrent_cached]`.
All of the compile-fail cases fire during macro expansion before any
feature-gated store type is emitted, so `proc_macro` alone is sufficient (no
`time_stores` needed). The one compile-pass case expands fully and is gated on
`time_stores`.
*/
#![cfg(feature = "proc_macro")]
#[test]
fn compile_fail_v3_macros() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/cached_ttl_and_ttl_millis_exclusive.rs");
t.compile_fail("tests/ui/cached_ttl_millis_zero.rs");
t.compile_fail("tests/ui/once_ttl_millis_zero.rs");
t.compile_fail("tests/ui/concurrent_cached_ttl_millis_zero.rs");
t.compile_fail("tests/ui/once_ttl_and_ttl_millis_exclusive.rs");
t.compile_fail("tests/ui/concurrent_cached_ttl_and_ttl_millis_exclusive.rs");
t.compile_fail("tests/ui/cached_ttl_ttl_secs_exclusive.rs");
t.compile_fail("tests/ui/once_ttl_ttl_secs_exclusive.rs");
t.compile_fail("tests/ui/concurrent_cached_ttl_ttl_secs_exclusive.rs");
t.compile_fail("tests/ui/cached_ttl_secs_and_ttl_millis_exclusive.rs");
t.compile_fail("tests/ui/once_ttl_secs_and_ttl_millis_exclusive.rs");
t.compile_fail("tests/ui/concurrent_cached_ttl_secs_and_ttl_millis_exclusive.rs");
t.compile_fail("tests/ui/cached_ttl_unparseable_duration.rs");
t.compile_fail("tests/ui/once_ttl_unparseable_duration.rs");
t.compile_fail("tests/ui/concurrent_cached_ttl_unparseable_duration.rs");
t.compile_fail("tests/ui/cached_ttl_integer_migration.rs");
t.compile_fail("tests/ui/once_ttl_integer_migration.rs");
t.compile_fail("tests/ui/concurrent_cached_ttl_integer_migration.rs");
t.compile_fail("tests/ui/cached_expires_and_ttl_millis_exclusive.rs");
t.compile_fail("tests/ui/once_expires_and_ttl_millis_exclusive.rs");
t.compile_fail("tests/ui/concurrent_cached_expires_and_ttl_millis_exclusive.rs");
t.compile_fail("tests/ui/cached_force_refresh_unparseable.rs");
t.compile_fail("tests/ui/once_force_refresh_unparseable.rs");
t.compile_fail("tests/ui/concurrent_cached_force_refresh_unparseable.rs");
t.compile_fail("tests/ui/cached_generic_requires_convert.rs");
t.compile_fail("tests/ui/cached_const_generic_requires_convert.rs");
t.compile_fail("tests/ui/cached_in_impl_generic_requires_convert.rs");
t.compile_fail("tests/ui/concurrent_cached_generic_requires_convert.rs");
t.compile_fail("tests/ui/concurrent_cached_const_generic_requires_convert.rs");
t.compile_fail("tests/ui/concurrent_cached_in_impl_generic_requires_convert.rs");
t.compile_fail("tests/ui/cached_ttl_millis_create_conflict.rs");
t.compile_fail("tests/ui/cached_refresh_create_conflict.rs");
t.compile_fail("tests/ui/concurrent_cached_ttl_millis_create_conflict.rs");
t.compile_fail("tests/ui/cached_in_impl_requires_self.rs");
t.compile_fail("tests/ui/once_in_impl_requires_self.rs");
t.compile_fail("tests/ui/concurrent_cached_in_impl_requires_self.rs");
// Item 2: `name` must be a valid Rust identifier
t.compile_fail("tests/ui/cached_name_invalid_ident.rs");
t.compile_fail("tests/ui/once_name_invalid_ident.rs");
t.compile_fail("tests/ui/concurrent_cached_name_invalid_ident.rs");
// Item 2 edge cases: leading digit and reserved keyword are also rejected
// (same spanned message) and must not reach `Ident::new` (which panics on
// a keyword).
t.compile_fail("tests/ui/cached_name_leading_digit.rs");
t.compile_fail("tests/ui/cached_name_keyword.rs");
// Item 11: `ShardHasher: Clone` supertrait - a non-Clone custom hasher is rejected.
t.compile_fail("tests/ui/sharded_non_clone_shard_hasher.rs");
// Negative surface for the concurrent trait split: non-TTL sharded stores do not
// implement `ConcurrentCacheTtl`, so `set_ttl` does not exist on them even under
// the prelude glob.
t.compile_fail("tests/ui/sharded_unbound_no_set_ttl.rs");
// Item 9: `#[cached]`-only attributes rejected on other macros
t.compile_fail("tests/ui/once_sync_lock_unsupported.rs");
t.compile_fail("tests/ui/once_unsync_reads_unsupported.rs");
t.compile_fail("tests/ui/concurrent_cached_sync_writes_buckets_unsupported.rs");
t.compile_fail("tests/ui/concurrent_cached_sync_lock_unsupported.rs");
t.compile_fail("tests/ui/concurrent_cached_unsync_reads_unsupported.rs");
// Item #1: explicit sync_writes = "by_key" combined with result_fallback errors.
t.compile_fail("tests/ui/cached_result_fallback_sync_writes_by_key.rs");
// Item #2: malformed unquoted convert block (syntax error) rejected.
t.compile_fail("tests/ui/cached_convert_malformed_unquoted.rs");
// Item #2: map_error = 5 (non-closure expression) rejected.
t.compile_fail("tests/ui/concurrent_cached_map_error_non_closure.rs");
// map_error = async |e| ... (async closure) rejected with a pointed message
// instead of the opaque downstream FnOnce bound failure.
t.compile_fail("tests/ui/concurrent_cached_map_error_async_closure.rs");
// G1: generic `#[once]` whose value type names a function type parameter.
t.compile_fail("tests/ui/once_generic_value_type_rejected.rs");
// G1: value type names the param *nested* inside another generic (`Vec<T>`) -
// a genuine whole-ident match that the substring->whole-ident fix must keep
// catching (not a false-rejection).
t.compile_fail("tests/ui/once_generic_value_type_nested_rejected.rs");
// G1: value type names a function *const* parameter (`[u8; N]`); the walk
// descends into the bracket group to find `N`.
t.compile_fail("tests/ui/once_generic_const_value_type_rejected.rs");
// G2: `name` beginning with `__cached` is reserved on all three macros.
t.compile_fail("tests/ui/cached_name_reserved_prefix.rs");
t.compile_fail("tests/ui/once_name_reserved_prefix.rs");
t.compile_fail("tests/ui/concurrent_cached_name_reserved_prefix.rs");
// Raw-ident fix: the reserved-`__cached`-prefix check runs on the STRIPPED name,
// so `name = "r#__cachedfoo"` must still be rejected with the reserved-prefix
// error on all three macros (and must NOT panic via `Ident::new_raw`).
t.compile_fail("tests/ui/cached_name_reserved_raw_prefix.rs");
t.compile_fail("tests/ui/once_name_reserved_raw_prefix.rs");
t.compile_fail("tests/ui/concurrent_cached_name_reserved_raw_prefix.rs");
// D11: `sync_writes_buckets` is inert on `#[once]` (no `by_key` support).
t.compile_fail("tests/ui/once_sync_writes_buckets_inert.rs");
// `sync_writes_buckets` on `#[cached]` without `sync_writes = "by_key"` is
// inert and must be rejected rather than silently ignored.
t.compile_fail("tests/ui/cached_sync_writes_buckets_inert.rs");
// T1: `force_refresh = true` (bare bool) is a compile error on `#[once]` and
// `#[concurrent_cached]`: the bare bool reaches `expr_to_block` which panics
// because `parse_quote! { true }` is not a valid Stmt without a semicolon.
// Pin the current behavior so any change to accept or reject bare bools is
// immediately visible.
t.compile_fail("tests/ui/once_force_refresh_bare_bool.rs");
t.compile_fail("tests/ui/concurrent_cached_force_refresh_bare_bool.rs");
// T1: `result_fallback = true` with `sync_writes = false` (Disabled) must compile.
// Unlike the compile-fail cases above, this one expands fully and uses
// `ttl_secs` (result_fallback requires a ttl/expires/ty), so it needs the
// `time_stores` feature; covered by the time_stores/default/async CI targets.
#[cfg(feature = "time_stores")]
t.pass("tests/ui/cached_result_fallback_sync_writes_disabled.rs");
// 0043a (non-`Clone` return type -> exactly ONE error) and 0043b (the three
// attribute errors respanned at the offending attribute) are pinned by the
// goldens of fixtures already registered above and in `tests/cached.rs`:
// `cached_return_type_requires_clone`,
// `cached_with_cached_flag_return_type_requires_clone`,
// `once_return_type_requires_clone` (error count), `cached_size_attr_removed`,
// `concurrent_cached_size_attr_removed`, `cached_ttl_ttl_secs_exclusive` (and
// the other `ttl*_exclusive` files above), `cached_generic_requires_convert`,
// `concurrent_cached_generic_requires_convert` (caret position). They are not
// re-registered here: trybuild would compile each fixture a second time for no
// added coverage.
}
/// 0042: the missing-feature guards for the `disk = true` / `redis = true` store
/// selectors on `#[concurrent_cached]`.
///
/// Each guard is a `compile_error!` only when its feature is OFF; with the feature
/// on it expands to nothing and the fixture compiles, which would make
/// `compile_fail` report a false failure. Every fixture below is therefore gated
/// on the absence of the feature it is about, the same way
/// `tests/v3_ui_async_guard.rs` gates the `async` guard. Consequences:
///
/// - the `redb_store` and `redis_store` fixtures run on every target that leaves
/// those features off (`default`, `time-stores`, `async`, `proc-macro`, ...);
/// - the async-redis fixtures split by the `async` feature, because the `async`
/// guard fires alongside the redis guard when `async` is off and changes the
/// expected stderr. The `async`-on target isolates the redis guard (one error);
/// the `async`-off target is where the two guards' relative ORDER is
/// observable, and is the regression guard for the mis-ordering.
///
/// Regenerate these goldens once per configuration:
/// `TRYBUILD=overwrite cargo test --features "proc_macro,time_stores"` and
/// `TRYBUILD=overwrite cargo test --features "proc_macro,time_stores,async"`.
#[test]
fn compile_fail_backend_feature_guards() {
let t = trybuild::TestCases::new();
// `disk = true` without `redb_store`: names `redb_store`.
#[cfg(not(feature = "redb_store"))]
t.compile_fail("tests/ui/concurrent_cached_disk_requires_redb_store.rs");
// `redis = true` on a sync fn without any redis feature: names `redis_store`
// (plus the runtime features, for the async case).
#[cfg(not(feature = "redis_store"))]
t.compile_fail("tests/ui/concurrent_cached_redis_requires_redis_store.rs");
// `redis = true` on an async fn: names the redis runtime features. With
// `async` ON this is the only diagnostic - no `async` / `async_core` anywhere.
#[cfg(all(feature = "async", not(feature = "redis_store")))]
t.compile_fail("tests/ui/concurrent_cached_async_redis_requires_redis_runtime.rs");
// Same program with `async` OFF: both guards fire, and the golden pins the
// redis guard FIRST.
#[cfg(all(not(feature = "async"), not(feature = "redis_store")))]
t.compile_fail("tests/ui/concurrent_cached_async_redis_guard_order.rs");
// `TestCases` runs its queue on drop; with every fixture cfg'd out (an
// all-features build) the queue is empty, the test is a no-op, and the
// explicit `drop` keeps `t` from tripping `unused_variables`.
drop(t);
}