Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #22831 from ada4a/unquerygroup-hirdatabase
internal: Migrate `HirDatabase` away from `#[query_group]`; remove the latter
| -rw-r--r-- | Cargo.lock | 14 | ||||
| -rw-r--r-- | crates/base-db/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/base-db/src/lib.rs | 3 | ||||
| -rw-r--r-- | crates/hir-def/src/resolver.rs | 4 | ||||
| -rw-r--r-- | crates/hir-ty/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/hir-ty/src/db.rs | 268 | ||||
| -rw-r--r-- | crates/hir-ty/src/dyn_compatibility.rs | 1 | ||||
| -rw-r--r-- | crates/hir-ty/src/mir/lower/tests.rs | 6 | ||||
| -rw-r--r-- | crates/hir/src/lib.rs | 12 | ||||
| -rw-r--r-- | crates/query-group-macro/Cargo.toml | 24 | ||||
| -rw-r--r-- | crates/query-group-macro/src/lib.rs | 256 | ||||
| -rw-r--r-- | crates/query-group-macro/src/queries.rs | 111 | ||||
| -rw-r--r-- | crates/query-group-macro/tests/hello_world.rs | 54 | ||||
| -rw-r--r-- | crates/query-group-macro/tests/logger_db.rs | 68 | ||||
| -rw-r--r-- | crates/query-group-macro/tests/multiple_dbs.rs | 27 | ||||
| -rw-r--r-- | crates/query-group-macro/tests/old_and_new.rs | 109 | ||||
| -rw-r--r-- | crates/query-group-macro/tests/result.rs | 41 | ||||
| -rw-r--r-- | crates/query-group-macro/tests/supertrait.rs | 20 | ||||
| -rw-r--r-- | crates/query-group-macro/tests/tuples.rs | 32 |
19 files changed, 168 insertions, 884 deletions
diff --git a/Cargo.lock b/Cargo.lock index aa3fe4b100..0e12d80b7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,7 +95,6 @@ dependencies = [ "indexmap", "intern", "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "query-group-macro", "rustc-hash 2.1.2", "salsa", "salsa-macros", @@ -926,7 +925,6 @@ dependencies = [ "oorandom", "petgraph", "project-model", - "query-group-macro", "ra-ap-rustc_abi", "ra-ap-rustc_ast_ir", "ra-ap-rustc_index", @@ -2039,18 +2037,6 @@ dependencies = [ ] [[package]] -name = "query-group-macro" -version = "0.0.0" -dependencies = [ - "expect-test", - "proc-macro2", - "quote", - "salsa", - "salsa-macros", - "syn", -] - -[[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/crates/base-db/Cargo.toml b/crates/base-db/Cargo.toml index 55dfcbc7e5..fe0c288441 100644 --- a/crates/base-db/Cargo.toml +++ b/crates/base-db/Cargo.toml @@ -17,7 +17,6 @@ la-arena.workspace = true dashmap.workspace = true salsa.workspace = true salsa-macros.workspace = true -query-group.workspace = true rustc-hash.workspace = true triomphe.workspace = true semver.workspace = true diff --git a/crates/base-db/src/lib.rs b/crates/base-db/src/lib.rs index b3c7f84249..7f915827b5 100644 --- a/crates/base-db/src/lib.rs +++ b/crates/base-db/src/lib.rs @@ -66,7 +66,6 @@ pub use crate::{ }, }; use dashmap::{DashMap, mapref::entry::Entry}; -pub use query_group; use rustc_hash::{FxHashSet, FxHasher}; use salsa::{Durability, Setter}; pub use semver::{BuildMetadata, Prerelease, Version, VersionReq}; @@ -265,7 +264,7 @@ pub struct SourceRootInput { } #[salsa_macros::db] -pub trait SourceDatabase: salsa::Database { +pub trait SourceDatabase: salsa::Database + std::fmt::Debug { /// Text of the file. fn file_text(&self, file_id: vfs::FileId) -> FileText; diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 724926f824..63ff384de0 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -1379,7 +1379,7 @@ impl HasResolver for TypeAliasId { impl HasResolver for ImplId { fn resolver(self, db: &dyn SourceDatabase) -> Resolver<'_> { - self.lookup(db).container.resolver(db).push_generic_params_scope(db, self.into()) + lookup_resolver(db, self).push_generic_params_scope(db, self.into()) } } @@ -1450,7 +1450,7 @@ impl HasResolver for ExpressionStoreOwnerId { impl HasResolver for EnumVariantId { fn resolver(self, db: &dyn SourceDatabase) -> Resolver<'_> { - self.lookup(db).parent.resolver(db) + lookup_resolver(db, self) } } diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml index e8eda74e40..7c5ad21bc7 100644 --- a/crates/hir-ty/Cargo.toml +++ b/crates/hir-ty/Cargo.toml @@ -29,7 +29,6 @@ triomphe.workspace = true typed-arena = "2.0.2" indexmap.workspace = true rustc_apfloat = "0.2.3" -query-group.workspace = true salsa.workspace = true salsa-macros.workspace = true petgraph.workspace = true diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs index 7e66e938a0..9944079cd3 100644 --- a/crates/hir-ty/src/db.rs +++ b/crates/hir-ty/src/db.rs @@ -37,260 +37,308 @@ use crate::{ traits::{ParamEnvAndCrate, StoredParamEnvAndCrate}, }; -#[query_group::query_group] -pub trait HirDatabase: SourceDatabase + std::fmt::Debug { +#[salsa::db] +pub trait HirDatabase: SourceDatabase + 'static { + /// Manual implementation of upcasting from `dyn SourceDatabase` to `dyn HirDatabase`. + /// + /// This function is needed because Rust can't perform this upcasting automatically + /// in the general case, as `Self` could be unsized. + fn as_dyn(&self) -> &dyn HirDatabase; + // region:mir // FIXME: Collapse `mir_body_for_closure` into `mir_body` // and `monomorphized_mir_body_for_closure` into `monomorphized_mir_body` - #[salsa::transparent] fn mir_body<'db>( &'db self, def: InferBodyId<'db>, ) -> Result<&'db MirBody<'db>, MirLowerError<'db>> { - crate::mir::mir_body_query(self, def).map_err(|err| err.clone()) + let db = self.as_dyn(); + crate::mir::mir_body_query(db, def).map_err(|err| err.clone()) } - #[salsa::transparent] fn mir_body_for_closure<'db>( &'db self, def: InternedClosureId<'db>, ) -> Result<&'db MirBody<'db>, MirLowerError<'db>> { - crate::mir::mir_body_for_closure_query(self, def).map_err(|err| err.clone()) + let db = self.as_dyn(); + crate::mir::mir_body_for_closure_query(db, def).map_err(|err| err.clone()) } - #[salsa::transparent] fn monomorphized_mir_body<'db>( &'db self, def: InferBodyId<'db>, subst: StoredGenericArgs, env: StoredParamEnvAndCrate, ) -> Result<&'db MirBody<'db>, MirLowerError<'db>> { - crate::mir::monomorphized_mir_body_query(self, def, subst, env).map_err(|err| err.clone()) + let db = self.as_dyn(); + crate::mir::monomorphized_mir_body_query(db, def, subst, env).map_err(|err| err.clone()) } - #[salsa::transparent] fn monomorphized_mir_body_for_closure<'db>( &'db self, def: InternedClosureId<'db>, subst: StoredGenericArgs, env: StoredParamEnvAndCrate, ) -> Result<&'db MirBody<'db>, MirLowerError<'db>> { - crate::mir::monomorphized_mir_body_for_closure_query(self, def, subst, env) + let db = self.as_dyn(); + crate::mir::monomorphized_mir_body_for_closure_query(db, def, subst, env) .map_err(|err| err.clone()) } - #[salsa::invoke(crate::consteval::const_eval)] - #[salsa::transparent] fn const_eval<'db>( &'db self, def: ConstId, subst: GenericArgs<'db>, trait_env: Option<ParamEnvAndCrate<'db>>, - ) -> Result<Allocation<'db>, ConstEvalError<'db>>; + ) -> Result<Allocation<'db>, ConstEvalError<'db>> { + let db = self.as_dyn(); + crate::consteval::const_eval(db, def, subst, trait_env) + } - #[salsa::invoke(crate::consteval::anon_const_eval)] - #[salsa::transparent] fn anon_const_eval<'db>( &'db self, def: AnonConstId<'db>, subst: GenericArgs<'db>, trait_env: Option<ParamEnvAndCrate<'db>>, - ) -> Result<Allocation<'db>, ConstEvalError<'db>>; + ) -> Result<Allocation<'db>, ConstEvalError<'db>> { + let db = self.as_dyn(); + crate::consteval::anon_const_eval(db, def, subst, trait_env) + } - #[salsa::invoke(crate::consteval::const_eval_static)] - #[salsa::transparent] fn const_eval_static<'db>( &'db self, def: StaticId, - ) -> Result<Allocation<'db>, ConstEvalError<'db>>; + ) -> Result<Allocation<'db>, ConstEvalError<'db>> { + let db = self.as_dyn(); + crate::consteval::const_eval_static(db, def) + } - #[salsa::invoke(crate::consteval::const_eval_discriminant_variant)] - #[salsa::transparent] fn const_eval_discriminant<'db>( &'db self, def: EnumVariantId, - ) -> Result<i128, ConstEvalError<'db>>; + ) -> Result<i128, ConstEvalError<'db>> { + let db = self.as_dyn(); + crate::consteval::const_eval_discriminant_variant(db, def) + } - #[salsa::invoke(crate::method_resolution::lookup_impl_method_query)] - #[salsa::transparent] fn lookup_impl_method<'db>( &'db self, env: ParamEnvAndCrate<'db>, func: FunctionId, fn_subst: GenericArgs<'db>, - ) -> (Either<FunctionId, (BuiltinDeriveImplId, BuiltinDeriveImplMethod)>, GenericArgs<'db>); + ) -> (Either<FunctionId, (BuiltinDeriveImplId, BuiltinDeriveImplMethod)>, GenericArgs<'db>) + { + let db = self.as_dyn(); + crate::method_resolution::lookup_impl_method_query(db, env, func, fn_subst) + } // endregion:mir - #[salsa::invoke(crate::layout::layout_of_adt_query)] - #[salsa::transparent] fn layout_of_adt( &self, def: AdtId, args: StoredGenericArgs, trait_env: StoredParamEnvAndCrate, - ) -> Result<Arc<Layout>, LayoutError>; + ) -> Result<Arc<Layout>, LayoutError> { + let db = self.as_dyn(); + crate::layout::layout_of_adt_query(db, def, args, trait_env) + } - #[salsa::invoke(crate::layout::layout_of_ty_query)] - #[salsa::transparent] fn layout_of_ty( &self, ty: StoredTy, env: StoredParamEnvAndCrate, - ) -> Result<Arc<Layout>, LayoutError>; + ) -> Result<Arc<Layout>, LayoutError> { + let db = self.as_dyn(); + crate::layout::layout_of_ty_query(db, ty, env) + } - #[salsa::transparent] fn target_data_layout(&self, krate: Crate) -> Result<&TargetDataLayout, TargetLoadError> { - crate::layout::target_data_layout_query(self, krate).map_err(|err| err.clone()) + let db = self.as_dyn(); + crate::layout::target_data_layout_query(db, krate).map_err(|err| err.clone()) } - #[salsa::invoke(crate::dyn_compatibility::dyn_compatibility_of_trait_query)] - fn dyn_compatibility_of_trait(&self, trait_: TraitId) -> Option<DynCompatibilityViolation>; + fn dyn_compatibility_of_trait(&self, trait_: TraitId) -> Option<DynCompatibilityViolation> { + let db = self.as_dyn(); + crate::dyn_compatibility::dyn_compatibility_of_trait_query(db, trait_) + } - #[salsa::invoke(crate::lower::ty_query)] - #[salsa::transparent] - fn ty<'db>(&'db self, def: TyDefId) -> EarlyBinder<'db, Ty<'db>>; + fn ty<'db>(&'db self, def: TyDefId) -> EarlyBinder<'db, Ty<'db>> { + let db = self.as_dyn(); + crate::lower::ty_query(db, def) + } - #[salsa::invoke(crate::lower::type_for_type_alias_with_diagnostics)] - #[salsa::transparent] fn type_for_type_alias_with_diagnostics<'db>( &'db self, def: TypeAliasId, - ) -> &'db TyLoweringResult<'db, StoredEarlyBinder<StoredTy>>; + ) -> &'db TyLoweringResult<'db, StoredEarlyBinder<StoredTy>> { + let db = self.as_dyn(); + crate::lower::type_for_type_alias_with_diagnostics(db, def) + } /// Returns the type of the value of the given constant, or `None` if the `ValueTyDefId` is /// a `StructId` or `EnumVariantId` with a record constructor. - #[salsa::invoke(crate::lower::value_ty)] - #[salsa::transparent] - fn value_ty<'db>(&'db self, def: ValueTyDefId) -> Option<EarlyBinder<'db, Ty<'db>>>; + fn value_ty<'db>(&'db self, def: ValueTyDefId) -> Option<EarlyBinder<'db, Ty<'db>>> { + let db = self.as_dyn(); + crate::lower::value_ty(db, def) + } - #[salsa::invoke(crate::lower::type_for_const)] - #[salsa::transparent] - fn type_for_const<'db>(&'db self, def: ConstId) -> EarlyBinder<'db, Ty<'db>>; + fn type_for_const<'db>(&'db self, def: ConstId) -> EarlyBinder<'db, Ty<'db>> { + let db = self.as_dyn(); + crate::lower::type_for_const(db, def) + } - #[salsa::invoke(crate::lower::type_for_const_with_diagnostics)] - #[salsa::transparent] fn type_for_const_with_diagnostics<'db>( &'db self, def: ConstId, - ) -> &'db TyLoweringResult<'db, StoredEarlyBinder<StoredTy>>; + ) -> &'db TyLoweringResult<'db, StoredEarlyBinder<StoredTy>> { + let db = self.as_dyn(); + crate::lower::type_for_const_with_diagnostics(db, def) + } - #[salsa::invoke(crate::lower::type_for_static)] - #[salsa::transparent] - fn type_for_static<'db>(&'db self, def: StaticId) -> EarlyBinder<'db, Ty<'db>>; + fn type_for_static<'db>(&'db self, def: StaticId) -> EarlyBinder<'db, Ty<'db>> { + let db = self.as_dyn(); + crate::lower::type_for_static(db, def) + } - #[salsa::invoke(crate::lower::type_for_static_with_diagnostics)] - #[salsa::transparent] fn type_for_static_with_diagnostics<'db>( &'db self, def: StaticId, - ) -> &'db TyLoweringResult<'db, StoredEarlyBinder<StoredTy>>; + ) -> &'db TyLoweringResult<'db, StoredEarlyBinder<StoredTy>> { + let db = self.as_dyn(); + crate::lower::type_for_static_with_diagnostics(db, def) + } - #[salsa::invoke(crate::lower::impl_self_ty_with_diagnostics)] - #[salsa::transparent] fn impl_self_ty_with_diagnostics<'db>( &'db self, def: ImplId, - ) -> &'db TyLoweringResult<'db, StoredEarlyBinder<StoredTy>>; + ) -> &'db TyLoweringResult<'db, StoredEarlyBinder<StoredTy>> { + let db = self.as_dyn(); + crate::lower::impl_self_ty_with_diagnostics(db, def) + } - #[salsa::invoke(crate::lower::impl_self_ty_query)] - #[salsa::transparent] - fn impl_self_ty<'db>(&'db self, def: ImplId) -> EarlyBinder<'db, Ty<'db>>; + fn impl_self_ty<'db>(&'db self, def: ImplId) -> EarlyBinder<'db, Ty<'db>> { + let db = self.as_dyn(); + crate::lower::impl_self_ty_query(db, def) + } - #[salsa::invoke(crate::lower::const_param_types_with_diagnostics)] - #[salsa::transparent] fn const_param_types_with_diagnostics<'db>( &'db self, def: GenericDefId, - ) -> &'db TyLoweringResult<'db, ArenaMap<LocalTypeOrConstParamId, StoredTy>>; + ) -> &'db TyLoweringResult<'db, ArenaMap<LocalTypeOrConstParamId, StoredTy>> { + let db = self.as_dyn(); + crate::lower::const_param_types_with_diagnostics(db, def) + } - #[salsa::invoke(crate::lower::const_param_types)] - #[salsa::transparent] - fn const_param_types(&self, def: GenericDefId) -> &ArenaMap<LocalTypeOrConstParamId, StoredTy>; + fn const_param_types(&self, def: GenericDefId) -> &ArenaMap<LocalTypeOrConstParamId, StoredTy> { + let db = self.as_dyn(); + crate::lower::const_param_types(db, def) + } - #[salsa::invoke(crate::lower::const_param_ty)] - #[salsa::transparent] - fn const_param_ty<'db>(&'db self, def: ConstParamId) -> Ty<'db>; + fn const_param_ty<'db>(&'db self, def: ConstParamId) -> Ty<'db> { + let db = self.as_dyn(); + crate::lower::const_param_ty(db, def) + } - #[salsa::invoke(crate::lower::impl_trait_with_diagnostics)] - #[salsa::transparent] fn impl_trait_with_diagnostics<'db>( &'db self, def: ImplId, - ) -> &'db Option<TyLoweringResult<'db, StoredEarlyBinder<StoredTraitRef>>>; + ) -> &'db Option<TyLoweringResult<'db, StoredEarlyBinder<StoredTraitRef>>> { + let db = self.as_dyn(); + crate::lower::impl_trait_with_diagnostics(db, def) + } - #[salsa::invoke(crate::lower::impl_trait_query)] - #[salsa::transparent] - fn impl_trait<'db>(&'db self, def: ImplId) -> Option<EarlyBinder<'db, TraitRef<'db>>>; + fn impl_trait<'db>(&'db self, def: ImplId) -> Option<EarlyBinder<'db, TraitRef<'db>>> { + let db = self.as_dyn(); + crate::lower::impl_trait_query(db, def) + } - #[salsa::invoke(crate::lower::field_types_with_diagnostics)] - #[salsa::transparent] fn field_types_with_diagnostics<'db>( &'db self, var: VariantId, - ) -> &'db TyLoweringResult<'db, ArenaMap<LocalFieldId, FieldType>>; + ) -> &'db TyLoweringResult<'db, ArenaMap<LocalFieldId, FieldType>> { + let db = self.as_dyn(); + crate::lower::field_types_with_diagnostics(db, var) + } - #[salsa::invoke(crate::lower::field_types_query)] - #[salsa::transparent] - fn field_types(&self, var: VariantId) -> &ArenaMap<LocalFieldId, FieldType>; + fn field_types(&self, var: VariantId) -> &ArenaMap<LocalFieldId, FieldType> { + let db = self.as_dyn(); + crate::lower::field_types_query(db, var) + } - #[salsa::invoke(crate::lower::callable_item_signature)] - #[salsa::transparent] fn callable_item_signature<'db>( &'db self, def: CallableDefId, - ) -> EarlyBinder<'db, PolyFnSig<'db>>; + ) -> EarlyBinder<'db, PolyFnSig<'db>> { + let db = self.as_dyn(); + crate::lower::callable_item_signature(db, def) + } - #[salsa::invoke(crate::lower::callable_item_signature_with_diagnostics)] - #[salsa::transparent] fn callable_item_signature_with_diagnostics<'db>( &'db self, def: CallableDefId, - ) -> &'db TyLoweringResult<'db, StoredEarlyBinder<StoredPolyFnSig>>; + ) -> &'db TyLoweringResult<'db, StoredEarlyBinder<StoredPolyFnSig>> { + let db = self.as_dyn(); + crate::lower::callable_item_signature_with_diagnostics(db, def) + } - #[salsa::invoke(crate::lower::trait_environment)] - #[salsa::transparent] - fn trait_environment<'db>(&'db self, def: GenericDefId) -> ParamEnv<'db>; + fn trait_environment<'db>(&'db self, def: GenericDefId) -> ParamEnv<'db> { + let db = self.as_dyn(); + crate::lower::trait_environment(db, def) + } - #[salsa::invoke(crate::lower::generic_defaults_with_diagnostics)] - #[salsa::transparent] fn generic_defaults_with_diagnostics<'db>( &'db self, def: GenericDefId, - ) -> &'db TyLoweringResult<'db, GenericDefaults>; + ) -> &'db TyLoweringResult<'db, GenericDefaults> { + let db = self.as_dyn(); + crate::lower::generic_defaults_with_diagnostics(db, def) + } /// This returns an empty list if no parameter has default. /// /// The binders of the returned defaults are only up to (not including) this parameter. - #[salsa::invoke(crate::lower::generic_defaults)] - #[salsa::transparent] - fn generic_defaults(&self, def: GenericDefId) -> GenericDefaultsRef<'_>; + fn generic_defaults(&self, def: GenericDefId) -> GenericDefaultsRef<'_> { + let db = self.as_dyn(); + crate::lower::generic_defaults(db, def) + } - #[salsa::invoke(crate::lower::type_alias_bounds_with_diagnostics)] - #[salsa::transparent] fn type_alias_bounds_with_diagnostics<'db>( &'db self, type_alias: TypeAliasId, - ) -> &'db TyLoweringResult<'db, TypeAliasBounds<StoredEarlyBinder<StoredClauses>>>; + ) -> &'db TyLoweringResult<'db, TypeAliasBounds<StoredEarlyBinder<StoredClauses>>> { + let db = self.as_dyn(); + crate::lower::type_alias_bounds_with_diagnostics(db, type_alias) + } - #[salsa::invoke(crate::lower::type_alias_bounds)] - #[salsa::transparent] fn type_alias_bounds<'db>( &'db self, type_alias: TypeAliasId, - ) -> EarlyBinder<'db, &'db [Clause<'db>]>; + ) -> EarlyBinder<'db, &'db [Clause<'db>]> { + let db = self.as_dyn(); + crate::lower::type_alias_bounds(db, type_alias) + } - #[salsa::invoke(crate::lower::type_alias_self_bounds)] - #[salsa::transparent] fn type_alias_self_bounds<'db>( &'db self, type_alias: TypeAliasId, - ) -> EarlyBinder<'db, &'db [Clause<'db>]>; + ) -> EarlyBinder<'db, &'db [Clause<'db>]> { + let db = self.as_dyn(); + crate::lower::type_alias_self_bounds(db, type_alias) + } - #[salsa::invoke(crate::variance::variances_of)] - #[salsa::transparent] - fn variances_of<'db>(&'db self, def: GenericDefId) -> VariancesOf<'db>; + fn variances_of<'db>(&'db self, def: GenericDefId) -> VariancesOf<'db> { + let db = self.as_dyn(); + crate::variance::variances_of(db, def) + } +} + +#[salsa::db] +impl<T: SourceDatabase> HirDatabase for T { + fn as_dyn(&self) -> &dyn HirDatabase { + self + } } #[test] diff --git a/crates/hir-ty/src/dyn_compatibility.rs b/crates/hir-ty/src/dyn_compatibility.rs index 9ee39b3abe..3920a84639 100644 --- a/crates/hir-ty/src/dyn_compatibility.rs +++ b/crates/hir-ty/src/dyn_compatibility.rs @@ -122,6 +122,7 @@ where ControlFlow::Continue(()) } +#[salsa::tracked] pub fn dyn_compatibility_of_trait_query( db: &dyn HirDatabase, trait_: TraitId, diff --git a/crates/hir-ty/src/mir/lower/tests.rs b/crates/hir-ty/src/mir/lower/tests.rs index d42072afa4..8eb0a02694 100644 --- a/crates/hir-ty/src/mir/lower/tests.rs +++ b/crates/hir-ty/src/mir/lower/tests.rs @@ -6,7 +6,7 @@ use crate::{InferBodyId, db::HirDatabase, setup_tracing, test_db::TestDB}; fn lower_mir(#[rust_analyzer::rust_fixture] ra_fixture: &str) { let _tracing = setup_tracing(); let (db, file_ids) = TestDB::with_many_files(ra_fixture); - crate::attach_db(&db, || { + crate::attach_db(db.as_dyn(), || { let file_id = *file_ids.last().unwrap(); let module_id = db.module_for_file(file_id.file_id(&db)); let def_map = module_id.def_map(&db); @@ -54,7 +54,7 @@ fn foo() { fn check_borrowck(#[rust_analyzer::rust_fixture] ra_fixture: &str) { let _tracing = setup_tracing(); let (db, file_ids) = TestDB::with_many_files(ra_fixture); - crate::attach_db(&db, || { + crate::attach_db(db.as_dyn(), || { let file_id = *file_ids.last().unwrap(); let module_id = db.module_for_file(file_id.file_id(&db)); let def_map = module_id.def_map(&db); @@ -78,7 +78,7 @@ fn check_borrowck(#[rust_analyzer::rust_fixture] ra_fixture: &str) { } for body in bodies { - let _ = InferBodyId::from(body).borrowck(&db); + let _ = InferBodyId::from(body).borrowck(db.as_dyn()); } }) } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index a029ed4031..ab7d88eeef 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1065,15 +1065,9 @@ impl Module { db.impl_self_ty_with_diagnostics(impl_id).diagnostics(), source_map, ); - push_ty_diagnostics( - db, - acc, - db.impl_trait_with_diagnostics(impl_id) - .as_ref() - .map(|it| it.diagnostics()) - .unwrap_or_default(), - source_map, - ); + if let Some(it) = db.impl_trait_with_diagnostics(impl_id) { + push_ty_diagnostics(db, acc, it.diagnostics(), source_map); + } for &(_, item) in impl_id.impl_items(db).items.iter() { AssocItem::from(item).diagnostics(db, acc, style_lints); diff --git a/crates/query-group-macro/Cargo.toml b/crates/query-group-macro/Cargo.toml deleted file mode 100644 index 5991120a30..0000000000 --- a/crates/query-group-macro/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "query-group-macro" -version = "0.0.0" -repository.workspace = true -description = "A macro mimicking the `#[salsa::query_group]` macro for migrating to new Salsa" - -authors.workspace = true -edition.workspace = true -license.workspace = true -rust-version.workspace = true - -[lib] -doctest = false -proc-macro = true - -[dependencies] -proc-macro2 = "1.0" -quote = "1.0" -syn = { version = "2.0", features = ["full", "extra-traits", "visit-mut"] } - -[dev-dependencies] -expect-test = "1.5.1" -salsa.workspace = true -salsa-macros.workspace = true diff --git a/crates/query-group-macro/src/lib.rs b/crates/query-group-macro/src/lib.rs deleted file mode 100644 index 9f7459066d..0000000000 --- a/crates/query-group-macro/src/lib.rs +++ /dev/null @@ -1,256 +0,0 @@ -//! A macro that mimics the old Salsa-style `#[query_group]` macro. - -use std::vec; - -use proc_macro::TokenStream; -use proc_macro2::Span; -use queries::{Queries, TrackedQuery, Transparent}; -use quote::{ToTokens, format_ident, quote}; -use syn::parse::ParseStream; -use syn::spanned::Spanned; -use syn::visit_mut::VisitMut; -use syn::{Attribute, FnArg, ItemTrait, Path, TraitItem, parse_quote, parse_quote_spanned}; - -mod queries; - -#[proc_macro_attribute] -pub fn query_group(args: TokenStream, input: TokenStream) -> TokenStream { - match query_group_impl(args, input.clone()) { - Ok(tokens) => tokens, - Err(e) => token_stream_with_error(input, e), - } -} - -struct SalsaAttr { - name: String, - tts: TokenStream, - span: Span, -} - -impl std::fmt::Debug for SalsaAttr { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(fmt, "{:?}", self.name) - } -} - -impl TryFrom<syn::Attribute> for SalsaAttr { - type Error = syn::Attribute; - - fn try_from(attr: syn::Attribute) -> Result<SalsaAttr, syn::Attribute> { - if is_not_salsa_attr_path(attr.path()) { - return Err(attr); - } - - let span = attr.span(); - - let name = attr.path().segments[1].ident.to_string(); - let tts = match attr.meta { - syn::Meta::Path(path) => path.into_token_stream(), - syn::Meta::List(ref list) => { - let tts = list - .into_token_stream() - .into_iter() - .skip(attr.path().to_token_stream().into_iter().count()); - proc_macro2::TokenStream::from_iter(tts) - } - syn::Meta::NameValue(nv) => nv.into_token_stream(), - } - .into(); - - Ok(SalsaAttr { name, tts, span }) - } -} - -fn is_not_salsa_attr_path(path: &syn::Path) -> bool { - path.segments.first().map(|s| s.ident != "salsa").unwrap_or(true) || path.segments.len() != 2 -} - -fn filter_attrs(attrs: Vec<Attribute>) -> (Vec<Attribute>, Vec<SalsaAttr>) { - let mut other = vec![]; - let mut salsa = vec![]; - // Leave non-salsa attributes untouched. These are - // attributes that don't start with `salsa::` or don't have - // exactly two segments in their path. - for attr in attrs { - match SalsaAttr::try_from(attr) { - Ok(it) => salsa.push(it), - Err(it) => other.push(it), - } - } - (other, salsa) -} - -#[derive(Debug, Clone, PartialEq, Eq)] -enum QueryKind { - TrackedWithSalsaStruct, - Transparent, -} - -pub(crate) fn query_group_impl( - _args: proc_macro::TokenStream, - input: proc_macro::TokenStream, -) -> Result<proc_macro::TokenStream, syn::Error> { - let mut item_trait = syn::parse::<ItemTrait>(input)?; - - let supertraits = &item_trait.supertraits; - - let db_attr: Attribute = parse_quote! { - #[salsa_macros::db] - }; - item_trait.attrs.push(db_attr); - - let trait_name_ident = &item_trait.ident.clone(); - let input_struct_name = format_ident!("{}Data", trait_name_ident); - let create_data_ident = format_ident!("create_data_{}", trait_name_ident); - - let mut trait_methods = vec![]; - - for item in &mut item_trait.items { - if let syn::TraitItem::Fn(method) = item { - let signature = &method.sig; - - let (_attrs, salsa_attrs) = filter_attrs(method.attrs.clone()); - - let mut query_kind = QueryKind::TrackedWithSalsaStruct; - let mut invoke = None; - - let params: Vec<FnArg> = signature.inputs.clone().into_iter().collect(); - let pat_and_tys = params - .into_iter() - .filter(|fn_arg| matches!(fn_arg, FnArg::Typed(_))) - .map(|fn_arg| match fn_arg { - FnArg::Typed(pat_type) => pat_type, - FnArg::Receiver(_) => unreachable!("this should have been filtered out"), - }) - .collect::<Vec<syn::PatType>>(); - - for SalsaAttr { name, tts, span } in salsa_attrs { - match name.as_str() { - "invoke" => { - let path = syn::parse::<Parenthesized<Path>>(tts)?; - invoke = Some(path.0.clone()); - if query_kind != QueryKind::Transparent { - query_kind = QueryKind::TrackedWithSalsaStruct; - } - } - "tracked" if method.default.is_some() => { - query_kind = QueryKind::TrackedWithSalsaStruct; - } - "transparent" => { - query_kind = QueryKind::Transparent; - } - _ => return Err(syn::Error::new(span, format!("unknown attribute `{name}`"))), - } - } - - let syn::ReturnType::Type(_, _) = signature.output.clone() else { - return Err(syn::Error::new(signature.span(), "Queries must have a return type")); - }; - - if let Some(block) = &mut method.default { - SelfToDbRewriter.visit_block_mut(block); - } - - match (query_kind, invoke) { - (QueryKind::TrackedWithSalsaStruct, invoke) => { - let method = TrackedQuery { - trait_name: trait_name_ident.clone(), - signature: signature.clone(), - pat_and_tys: pat_and_tys.clone(), - invoke, - default: method.default.take(), - }; - - trait_methods.push(Queries::TrackedQuery(method)) - } - (QueryKind::Transparent, invoke) => { - let method = Transparent { - signature: method.sig.clone(), - pat_and_tys: pat_and_tys.clone(), - invoke, - default: method.default.take(), - }; - trait_methods.push(Queries::Transparent(method)); - } - } - } - } - - let input_struct = quote! { - #[salsa_macros::input] - pub(crate) struct #input_struct_name {} - }; - - let create_data_method = quote! { - #[allow(non_snake_case)] - #[salsa_macros::tracked] - fn #create_data_ident(db: &dyn #trait_name_ident) -> #input_struct_name { - #input_struct_name::new(db) - } - }; - - let trait_impl = quote! { - #[salsa_macros::db] - impl<DB> #trait_name_ident for DB - where - DB: #supertraits, - { - #(#trait_methods)* - } - }; - RemoveAttrsFromTraitMethods.visit_item_trait_mut(&mut item_trait); - - let out = quote! { - #item_trait - - #trait_impl - - #input_struct - - #create_data_method - } - .into(); - - Ok(out) -} - -/// Parenthesis helper -pub(crate) struct Parenthesized<T>(pub(crate) T); - -impl<T> syn::parse::Parse for Parenthesized<T> -where - T: syn::parse::Parse, -{ - fn parse(input: ParseStream<'_>) -> syn::Result<Self> { - let content; - syn::parenthesized!(content in input); - content.parse::<T>().map(Parenthesized) - } -} - -struct RemoveAttrsFromTraitMethods; - -impl VisitMut for RemoveAttrsFromTraitMethods { - fn visit_item_trait_mut(&mut self, i: &mut syn::ItemTrait) { - for item in &mut i.items { - if let TraitItem::Fn(trait_item_fn) = item { - trait_item_fn.attrs = vec![]; - } - } - } -} - -pub(crate) fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream { - tokens.extend(TokenStream::from(error.into_compile_error())); - tokens -} - -struct SelfToDbRewriter; - -impl VisitMut for SelfToDbRewriter { - fn visit_expr_path_mut(&mut self, i: &mut syn::ExprPath) { - if i.path.is_ident("self") { - i.path = parse_quote_spanned!(i.path.span() => db); - } - } -} diff --git a/crates/query-group-macro/src/queries.rs b/crates/query-group-macro/src/queries.rs deleted file mode 100644 index 935d65bb24..0000000000 --- a/crates/query-group-macro/src/queries.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! The IR of the `#[query_group]` macro. - -use quote::{ToTokens, format_ident, quote, quote_spanned}; -use syn::{Ident, PatType, Path, spanned::Spanned}; - -pub(crate) struct TrackedQuery { - pub(crate) trait_name: Ident, - pub(crate) signature: syn::Signature, - pub(crate) pat_and_tys: Vec<PatType>, - pub(crate) invoke: Option<Path>, - pub(crate) default: Option<syn::Block>, -} - -impl ToTokens for TrackedQuery { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let sig = &self.signature; - let trait_name = &self.trait_name; - - let ret = &sig.output; - - let invoke = match &self.invoke { - Some(path) => path.to_token_stream(), - None => sig.ident.to_token_stream(), - }; - - let fn_ident = &sig.ident; - let shim: Ident = format_ident!("{}_shim", fn_ident); - - let pat_and_tys = &self.pat_and_tys; - let params = self - .pat_and_tys - .iter() - .map(|pat_type| pat_type.pat.clone()) - .collect::<Vec<Box<syn::Pat>>>(); - - let invoke_block = match &self.default { - Some(default) => quote! { #default }, - None => { - let invoke_params: proc_macro2::TokenStream = quote! {db, #(#params),*}; - quote_spanned! { invoke.span() => {#invoke(#invoke_params)}} - } - }; - - let method = quote! { - #sig { - #[salsa_macros::tracked] - fn #shim<'db>( - db: &'db dyn #trait_name, - #(#pat_and_tys),* - ) #ret - #invoke_block - - #shim(self, #(#params),*) - } - }; - - method.to_tokens(tokens); - } -} - -pub(crate) struct Transparent { - pub(crate) signature: syn::Signature, - pub(crate) pat_and_tys: Vec<PatType>, - pub(crate) invoke: Option<Path>, - pub(crate) default: Option<syn::Block>, -} - -impl ToTokens for Transparent { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let sig = &self.signature; - - let ty = self - .pat_and_tys - .iter() - .map(|pat_type| pat_type.pat.clone()) - .collect::<Vec<Box<syn::Pat>>>(); - - let invoke = match &self.invoke { - Some(path) => path.to_token_stream(), - None => sig.ident.to_token_stream(), - }; - - let method = match &self.default { - Some(default) => quote! { - #sig { let db = self; #default } - }, - None => quote! { - #sig { - #invoke(self, #(#ty),*) - } - }, - }; - - method.to_tokens(tokens); - } -} - -#[allow(clippy::large_enum_variant)] -pub(crate) enum Queries { - TrackedQuery(TrackedQuery), - Transparent(Transparent), -} - -impl ToTokens for Queries { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - match self { - Queries::TrackedQuery(tracked_query) => tracked_query.to_tokens(tokens), - Queries::Transparent(transparent) => transparent.to_tokens(tokens), - } - } -} diff --git a/crates/query-group-macro/tests/hello_world.rs b/crates/query-group-macro/tests/hello_world.rs deleted file mode 100644 index 9e99b6d7fd..0000000000 --- a/crates/query-group-macro/tests/hello_world.rs +++ /dev/null @@ -1,54 +0,0 @@ -use expect_test::expect; -use query_group_macro::query_group; - -mod logger_db; -use logger_db::LoggerDb; - -#[salsa::input(singleton)] -struct InputString { - inner: String, -} - -#[query_group] -pub trait HelloWorldDatabase: salsa::Database { - // unadorned query - fn length_query_with_no_params(&self) -> usize; - - // not a query. should not invoked - #[salsa::transparent] - fn transparent_length(&self, key: ()) -> usize; -} - -fn length_query_with_no_params(db: &dyn HelloWorldDatabase) -> usize { - InputString::get(db).inner(db).len() -} - -fn transparent_length(db: &dyn HelloWorldDatabase, _key: ()) -> usize { - InputString::get(db).inner(db).len() -} - -#[test] -fn unadorned_query() { - let db = LoggerDb::default(); - - InputString::new(&db, String::from("Hello, world!")); - let len = db.length_query_with_no_params(); - - assert_eq!(len, 13); - db.assert_logs(expect![[r#" - [ - "salsa_event(WillCheckCancellation)", - "salsa_event(WillExecute { database_key: length_query_with_no_params_shim(Id(400)) })", - ]"#]]); -} - -#[test] -fn transparent() { - let db = LoggerDb::default(); - - InputString::new(&db, String::from("Hello, world!")); - let len = db.transparent_length(()); - - assert_eq!(len, 13); - db.assert_logs(expect!["[]"]); -} diff --git a/crates/query-group-macro/tests/logger_db.rs b/crates/query-group-macro/tests/logger_db.rs deleted file mode 100644 index 71af63a0d3..0000000000 --- a/crates/query-group-macro/tests/logger_db.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::sync::{Arc, Mutex}; - -#[salsa_macros::db] -#[derive(Clone)] -pub(crate) struct LoggerDb { - storage: salsa::Storage<Self>, - logger: Logger, -} - -impl Default for LoggerDb { - fn default() -> Self { - let logger = Logger::default(); - Self { - storage: salsa::Storage::new(Some(Box::new({ - let logger = logger.clone(); - move |event| match event.kind { - salsa::EventKind::WillExecute { .. } - | salsa::EventKind::WillCheckCancellation - | salsa::EventKind::DidValidateMemoizedValue { .. } - | salsa::EventKind::WillDiscardStaleOutput { .. } - | salsa::EventKind::DidDiscard { .. } => { - logger.logs.lock().unwrap().push(format!("salsa_event({:?})", event.kind)); - } - _ => {} - } - }))), - logger, - } - } -} - -#[derive(Default, Clone)] -struct Logger { - logs: Arc<Mutex<Vec<String>>>, -} - -#[salsa_macros::db] -impl salsa::Database for LoggerDb {} - -impl LoggerDb { - /// Log an event from inside a tracked function. - pub(crate) fn push_log(&self, string: String) { - self.logger.logs.lock().unwrap().push(string); - } - - /// Asserts what the (formatted) logs should look like, - /// clearing the logged events. This takes `&mut self` because - /// it is meant to be run from outside any tracked functions. - pub(crate) fn assert_logs(&self, expected: expect_test::Expect) { - let logs = std::mem::take(&mut *self.logger.logs.lock().unwrap()); - expected.assert_eq(&format!("{logs:#?}")); - } -} - -/// Test the logger database. -/// -/// This test isn't very interesting, but it *does* remove a dead code warning. -#[test] -fn test_logger_db() { - let db = LoggerDb::default(); - db.push_log("test".to_string()); - db.assert_logs(expect_test::expect![ - r#" - [ - "test", - ]"# - ]); -} diff --git a/crates/query-group-macro/tests/multiple_dbs.rs b/crates/query-group-macro/tests/multiple_dbs.rs deleted file mode 100644 index 678a7ce5d7..0000000000 --- a/crates/query-group-macro/tests/multiple_dbs.rs +++ /dev/null @@ -1,27 +0,0 @@ -use query_group_macro::query_group; - -#[salsa::input(singleton)] -struct InputString { - inner: String, -} - -#[query_group] -pub trait DatabaseOne: salsa::Database { - // unadorned query - #[salsa::transparent] - fn length(&self, key: ()) -> usize; -} - -#[query_group] -pub trait DatabaseTwo: DatabaseOne { - #[salsa::transparent] - fn second_length(&self, key: ()) -> usize; -} - -fn length(db: &dyn DatabaseOne, _key: ()) -> usize { - InputString::get(db).inner(db).len() -} - -fn second_length(db: &dyn DatabaseTwo, _key: ()) -> usize { - InputString::get(db).inner(db).len() -} diff --git a/crates/query-group-macro/tests/old_and_new.rs b/crates/query-group-macro/tests/old_and_new.rs deleted file mode 100644 index cc57ba7845..0000000000 --- a/crates/query-group-macro/tests/old_and_new.rs +++ /dev/null @@ -1,109 +0,0 @@ -use expect_test::expect; - -mod logger_db; -use logger_db::LoggerDb; -use query_group_macro::query_group; - -#[salsa_macros::input] -struct Input { - str: String, -} - -#[query_group] -trait PartialMigrationDatabase: salsa::Database { - fn length_query(&self, input: Input) -> usize; - - // renamed/invoke query - #[salsa::invoke(invoke_length_query_actual)] - fn invoke_length_query(&self, input: Input) -> usize; - - // invoke tracked function - #[salsa::invoke(invoke_length_tracked_actual)] - fn invoke_length_tracked(&self, input: Input) -> usize; -} - -fn length_query(db: &dyn PartialMigrationDatabase, input: Input) -> usize { - input.str(db).len() -} - -fn invoke_length_query_actual(db: &dyn PartialMigrationDatabase, input: Input) -> usize { - input.str(db).len() -} - -#[salsa_macros::tracked] -fn invoke_length_tracked_actual(db: &dyn PartialMigrationDatabase, input: Input) -> usize { - input.str(db).len() -} - -#[test] -fn unadorned_query() { - let db = LoggerDb::default(); - - let input = Input::new(&db, String::from("Hello, world!")); - let len = db.length_query(input); - - assert_eq!(len, 13); - db.assert_logs(expect![[r#" - [ - "salsa_event(WillCheckCancellation)", - "salsa_event(WillExecute { database_key: length_query_shim(Id(0)) })", - ]"#]]); -} - -#[test] -fn invoke_query() { - let db = LoggerDb::default(); - - let input = Input::new(&db, String::from("Hello, world!")); - let len = db.invoke_length_query(input); - - assert_eq!(len, 13); - db.assert_logs(expect![[r#" - [ - "salsa_event(WillCheckCancellation)", - "salsa_event(WillExecute { database_key: invoke_length_query_shim(Id(0)) })", - ]"#]]); -} - -// todo: does this even make sense? -#[test] -fn invoke_tracked_query() { - let db = LoggerDb::default(); - - let input = Input::new(&db, String::from("Hello, world!")); - let len = db.invoke_length_tracked(input); - - assert_eq!(len, 13); - db.assert_logs(expect![[r#" - [ - "salsa_event(WillCheckCancellation)", - "salsa_event(WillExecute { database_key: invoke_length_tracked_shim(Id(0)) })", - "salsa_event(WillCheckCancellation)", - "salsa_event(WillExecute { database_key: invoke_length_tracked_actual(Id(0)) })", - ]"#]]); -} - -#[test] -fn new_salsa_baseline() { - let db = LoggerDb::default(); - - #[salsa_macros::input] - struct Input { - str: String, - } - - #[salsa_macros::tracked] - fn new_salsa_length_query(db: &dyn PartialMigrationDatabase, input: Input) -> usize { - input.str(db).len() - } - - let input = Input::new(&db, String::from("Hello, world!")); - let len = new_salsa_length_query(&db, input); - - assert_eq!(len, 13); - db.assert_logs(expect![[r#" - [ - "salsa_event(WillCheckCancellation)", - "salsa_event(WillExecute { database_key: new_salsa_length_query(Id(0)) })", - ]"#]]); -} diff --git a/crates/query-group-macro/tests/result.rs b/crates/query-group-macro/tests/result.rs deleted file mode 100644 index 7e5e8d3a5b..0000000000 --- a/crates/query-group-macro/tests/result.rs +++ /dev/null @@ -1,41 +0,0 @@ -mod logger_db; -use expect_test::expect; -use logger_db::LoggerDb; - -use query_group_macro::query_group; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Error; - -#[salsa::input(singleton)] -struct InputString { - inner: String, -} - -#[query_group] -pub trait ResultDatabase: salsa::Database { - #[salsa::transparent] - fn length(&self, key: ()) -> Result<usize, Error>; - - #[salsa::transparent] - fn length2(&self, key: ()) -> Result<usize, Error>; -} - -fn length(db: &dyn ResultDatabase, _key: ()) -> Result<usize, Error> { - Ok(InputString::get(db).inner(db).len()) -} - -fn length2(db: &dyn ResultDatabase, _key: ()) -> Result<usize, Error> { - Ok(InputString::get(db).inner(db).len()) -} - -#[test] -fn test_queries_with_results() { - let db = LoggerDb::default(); - let input = "hello"; - _ = InputString::new(&db, input.to_owned()); - assert_eq!(db.length(()), Ok(input.len())); - assert_eq!(db.length2(()), Ok(input.len())); - - db.assert_logs(expect!["[]"]); -} diff --git a/crates/query-group-macro/tests/supertrait.rs b/crates/query-group-macro/tests/supertrait.rs deleted file mode 100644 index 63a5dbd16c..0000000000 --- a/crates/query-group-macro/tests/supertrait.rs +++ /dev/null @@ -1,20 +0,0 @@ -use query_group_macro::query_group; - -#[salsa_macros::db] -pub trait SourceDb: salsa::Database { - /// Text of the file. - fn file_text(&self, id: usize) -> String; -} - -#[query_group] -pub trait RootDb: SourceDb { - #[salsa::transparent] - fn parse(&self, id: usize) -> String; -} - -fn parse(db: &dyn RootDb, id: usize) -> String { - // this is the test: does the following compile? - db.file_text(id); - - String::new() -} diff --git a/crates/query-group-macro/tests/tuples.rs b/crates/query-group-macro/tests/tuples.rs deleted file mode 100644 index ab9d1746f7..0000000000 --- a/crates/query-group-macro/tests/tuples.rs +++ /dev/null @@ -1,32 +0,0 @@ -use query_group_macro::query_group; - -mod logger_db; -use expect_test::expect; -use logger_db::LoggerDb; - -#[salsa::input(singleton)] -struct InputString { - inner: String, -} - -#[query_group] -pub trait HelloWorldDatabase: salsa::Database { - #[salsa::transparent] - fn length_query(&self, key: ()) -> (usize, usize); -} - -fn length_query(db: &dyn HelloWorldDatabase, _key: ()) -> (usize, usize) { - let len = InputString::get(db).inner(db).len(); - (len, len) -} - -#[test] -fn query() { - let db = LoggerDb::default(); - - _ = InputString::new(&db, String::from("Hello, world!")); - let len = db.length_query(()); - - assert_eq!(len, (13, 13)); - db.assert_logs(expect!["[]"]); -} |