cached 3.0.0

Generic cache implementations and simplified function memoization
Documentation
/*!
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);
}