From cace49e9a79a5fe44cda63964412c5bdce7ee90d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 22:26:47 +0300 Subject: Decouple --- crates/ra_hir/src/code_model.rs | 2 +- crates/ra_hir/src/db.rs | 21 ++++++-------- crates/ra_hir/src/ty/method_resolution.rs | 46 +++++++++++++++---------------- crates/ra_hir/src/ty/traits.rs | 16 +++++------ crates/ra_hir/src/ty/traits/chalk.rs | 2 +- 5 files changed, 42 insertions(+), 45 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index a842dfed6..b4b47057d 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -945,7 +945,7 @@ impl ImplBlock { } pub fn for_trait(db: &impl HirDatabase, krate: Crate, trait_: Trait) -> Vec { let impls = db.impls_in_crate(krate.crate_id); - impls.lookup_impl_blocks_for_trait(trait_).map(Self::from).collect() + impls.lookup_impl_blocks_for_trait(trait_.id).map(Self::from).collect() } pub fn target_trait(&self, db: &impl DefDatabase) -> Option { diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index 3b5aa7516..31b21ca84 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -2,6 +2,7 @@ use std::sync::Arc; +use hir_def::{GenericDefId, LocalStructFieldId, TraitId, VariantId}; use ra_arena::map::ArenaMap; use ra_db::{salsa, CrateId}; @@ -12,19 +13,15 @@ use crate::{ CallableDef, FnSig, GenericPredicate, InferenceResult, Substs, Ty, TyDefId, TypeCtor, ValueTyDefId, }, - Crate, DefWithBody, ImplBlock, Trait, + Crate, DefWithBody, ImplBlock, }; -pub use hir_def::{ - db::{ - BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQuery, CrateLangItemsQuery, - DefDatabase, DefDatabaseStorage, DocumentationQuery, EnumDataQuery, ExprScopesQuery, - FunctionDataQuery, GenericParamsQuery, ImplDataQuery, InternDatabase, - InternDatabaseStorage, LangItemQuery, ModuleLangItemsQuery, RawItemsQuery, - RawItemsWithSourceMapQuery, StaticDataQuery, StructDataQuery, TraitDataQuery, - TypeAliasDataQuery, - }, - GenericDefId, LocalStructFieldId, VariantId, +pub use hir_def::db::{ + BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQuery, CrateLangItemsQuery, + DefDatabase, DefDatabaseStorage, DocumentationQuery, EnumDataQuery, ExprScopesQuery, + FunctionDataQuery, GenericParamsQuery, ImplDataQuery, InternDatabase, InternDatabaseStorage, + LangItemQuery, ModuleLangItemsQuery, RawItemsQuery, RawItemsWithSourceMapQuery, + StaticDataQuery, StructDataQuery, TraitDataQuery, TypeAliasDataQuery, }; pub use hir_expand::db::{ AstDatabase, AstDatabaseStorage, AstIdMapQuery, MacroArgQuery, MacroDefQuery, MacroExpandQuery, @@ -66,7 +63,7 @@ pub trait HirDatabase: DefDatabase { fn impls_in_crate(&self, krate: CrateId) -> Arc; #[salsa::invoke(crate::ty::traits::impls_for_trait_query)] - fn impls_for_trait(&self, krate: Crate, trait_: Trait) -> Arc<[ImplBlock]>; + fn impls_for_trait(&self, krate: CrateId, trait_: TraitId) -> Arc<[ImplBlock]>; /// This provides the Chalk trait solver instance. Because Chalk always /// works from a specific crate, this query is keyed on the crate; and diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index f1bc638ee..fdc87a28d 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -6,9 +6,10 @@ use std::sync::Arc; use arrayvec::ArrayVec; use hir_def::{ - lang_item::LangItemTarget, resolver::HasResolver, resolver::Resolver, AssocItemId, AstItemDef, - HasModule, ImplId, TraitId, + lang_item::LangItemTarget, resolver::HasResolver, resolver::Resolver, type_ref::Mutability, + AssocItemId, AstItemDef, HasModule, ImplId, TraitId, }; +use hir_expand::name::Name; use ra_db::CrateId; use ra_prof::profile; use rustc_hash::FxHashMap; @@ -17,7 +18,7 @@ use crate::{ db::HirDatabase, ty::primitive::{FloatBitness, Uncertain}, ty::{utils::all_super_traits, Ty, TypeCtor}, - AssocItem, Crate, Function, Mutability, Name, Trait, + AssocItem, Function, }; use super::{autoderef, Canonical, InEnvironment, TraitEnvironment, TraitRef}; @@ -87,8 +88,8 @@ impl CrateImplBlocks { fingerprint.and_then(|f| self.impls.get(&f)).into_iter().flatten().copied() } - pub fn lookup_impl_blocks_for_trait(&self, tr: Trait) -> impl Iterator + '_ { - self.impls_by_trait.get(&tr.id).into_iter().flatten().copied() + pub fn lookup_impl_blocks_for_trait(&self, tr: TraitId) -> impl Iterator + '_ { + self.impls_by_trait.get(&tr).into_iter().flatten().copied() } pub fn all_impls<'a>(&'a self) -> impl Iterator + 'a { @@ -96,14 +97,18 @@ impl CrateImplBlocks { } } -fn def_crates(db: &impl HirDatabase, cur_crate: Crate, ty: &Ty) -> Option> { +fn def_crates( + db: &impl HirDatabase, + cur_crate: CrateId, + ty: &Ty, +) -> Option> { // Types like slice can have inherent impls in several crates, (core and alloc). // The corresponding impls are marked with lang items, so we can use them to find the required crates. macro_rules! lang_item_crate { ($($name:expr),+ $(,)?) => {{ let mut v = ArrayVec::<[LangItemTarget; 2]>::new(); $( - v.extend(db.lang_item(cur_crate.crate_id, $name.into())); + v.extend(db.lang_item(cur_crate, $name.into())); )+ v }}; @@ -112,7 +117,7 @@ fn def_crates(db: &impl HirDatabase, cur_crate: Crate, ty: &Ty) -> Option match a_ty.ctor { TypeCtor::Adt(def_id) => { - return Some(std::iter::once(def_id.module(db).krate.into()).collect()) + return Some(std::iter::once(def_id.module(db).krate).collect()) } TypeCtor::Bool => lang_item_crate!("bool"), TypeCtor::Char => lang_item_crate!("char"), @@ -136,7 +141,7 @@ fn def_crates(db: &impl HirDatabase, cur_crate: Crate, ty: &Ty) -> Option Some(it), _ => None, }) - .map(|it| it.module(db).krate.into()) + .map(|it| it.module(db).krate) .collect(); Some(res) } @@ -193,14 +198,9 @@ pub(crate) fn iterate_method_candidates( let environment = TraitEnvironment::lower(db, resolver); let ty = InEnvironment { value: ty.clone(), environment }; for derefed_ty in autoderef::autoderef(db, resolver.krate(), ty) { - if let Some(result) = iterate_inherent_methods( - &derefed_ty, - db, - name, - mode, - krate.into(), - &mut callback, - ) { + if let Some(result) = + iterate_inherent_methods(&derefed_ty, db, name, mode, krate, &mut callback) + { return Some(result); } if let Some(result) = iterate_trait_method_candidates( @@ -283,11 +283,11 @@ fn iterate_inherent_methods( db: &impl HirDatabase, name: Option<&Name>, mode: LookupMode, - krate: Crate, + krate: CrateId, mut callback: impl FnMut(&Ty, AssocItem) -> Option, ) -> Option { for krate in def_crates(db, krate, &ty.value)? { - let impls = db.impls_in_crate(krate.crate_id); + let impls = db.impls_in_crate(krate); for impl_block in impls.lookup_impl_blocks(&ty.value) { for &item in db.impl_data(impl_block).items.iter() { @@ -327,7 +327,7 @@ pub(crate) fn implements_trait( ty: &Canonical, db: &impl HirDatabase, resolver: &Resolver, - krate: Crate, + krate: CrateId, trait_: TraitId, ) -> bool { if ty.value.inherent_trait() == Some(trait_) { @@ -337,7 +337,7 @@ pub(crate) fn implements_trait( } let env = TraitEnvironment::lower(db, resolver); let goal = generic_implements_goal(db, env, trait_, ty.clone()); - let solution = db.trait_solve(krate, goal); + let solution = db.trait_solve(krate.into(), goal); solution.is_some() } @@ -348,11 +348,11 @@ impl Ty { pub fn iterate_impl_items( self, db: &impl HirDatabase, - krate: Crate, + krate: CrateId, mut callback: impl FnMut(AssocItem) -> Option, ) -> Option { for krate in def_crates(db, krate, &self)? { - let impls = db.impls_in_crate(krate.crate_id); + let impls = db.impls_in_crate(krate); for impl_block in impls.lookup_impl_blocks(&self) { for &item in db.impl_data(impl_block).items.iter() { diff --git a/crates/ra_hir/src/ty/traits.rs b/crates/ra_hir/src/ty/traits.rs index a91c2476b..637e21e9c 100644 --- a/crates/ra_hir/src/ty/traits.rs +++ b/crates/ra_hir/src/ty/traits.rs @@ -2,13 +2,13 @@ use std::sync::{Arc, Mutex}; use chalk_ir::{cast::Cast, family::ChalkIr}; -use hir_def::{expr::ExprId, DefWithBodyId}; +use hir_def::{expr::ExprId, DefWithBodyId, TraitId}; use log::debug; -use ra_db::{impl_intern_key, salsa}; +use ra_db::{impl_intern_key, salsa, CrateId}; use ra_prof::profile; use rustc_hash::FxHashSet; -use crate::{db::HirDatabase, Crate, ImplBlock, Trait, TypeAlias}; +use crate::{db::HirDatabase, Crate, ImplBlock, TypeAlias}; use super::{Canonical, GenericPredicate, HirDisplay, ProjectionTy, TraitRef, Ty, TypeWalk}; @@ -77,8 +77,8 @@ pub(crate) fn trait_solver_query( /// Collects impls for the given trait in the whole dependency tree of `krate`. pub(crate) fn impls_for_trait_query( db: &impl HirDatabase, - krate: Crate, - trait_: Trait, + krate: CrateId, + trait_: TraitId, ) -> Arc<[ImplBlock]> { let mut impls = FxHashSet::default(); // We call the query recursively here. On the one hand, this means we can @@ -86,10 +86,10 @@ pub(crate) fn impls_for_trait_query( // will only ever get called for a few crates near the root of the tree (the // ones the user is editing), so this may actually be a waste of memory. I'm // doing it like this mainly for simplicity for now. - for dep in krate.dependencies(db) { - impls.extend(db.impls_for_trait(dep.krate, trait_).iter()); + for dep in db.crate_graph().dependencies(krate) { + impls.extend(db.impls_for_trait(dep.crate_id, trait_).iter()); } - let crate_impl_blocks = db.impls_in_crate(krate.crate_id); + let crate_impl_blocks = db.impls_in_crate(krate); impls.extend(crate_impl_blocks.lookup_impl_blocks_for_trait(trait_).map(ImplBlock::from)); impls.into_iter().collect() } diff --git a/crates/ra_hir/src/ty/traits/chalk.rs b/crates/ra_hir/src/ty/traits/chalk.rs index 4b0f4f56c..d879382a0 100644 --- a/crates/ra_hir/src/ty/traits/chalk.rs +++ b/crates/ra_hir/src/ty/traits/chalk.rs @@ -448,7 +448,7 @@ where let trait_: TraitId = from_chalk(self.db, trait_id); let mut result: Vec<_> = self .db - .impls_for_trait(self.krate, trait_.into()) + .impls_for_trait(self.krate.crate_id, trait_.into()) .iter() .copied() .map(Impl::ImplBlock) -- cgit v1.2.3 From bed6869865ccfc6e72be26cb2041d83ab5cdbe3c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 22:56:07 +0300 Subject: Cleanup --- crates/ra_hir/src/code_model.rs | 49 +++++++++-- crates/ra_hir/src/source_binder.rs | 4 +- crates/ra_hir/src/ty/lower.rs | 2 +- crates/ra_hir/src/ty/method_resolution.rs | 101 +++++++++------------- crates/ra_hir_def/src/resolver.rs | 4 +- crates/ra_ide_api/src/completion/complete_path.rs | 2 +- 6 files changed, 85 insertions(+), 77 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index b4b47057d..54da937ea 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -28,7 +28,8 @@ use crate::{ expr::{BindingAnnotation, Body, BodySourceMap, ExprValidator, Pat, PatId}, ty::display::HirFormatter, ty::{ - self, InEnvironment, InferenceResult, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk, + self, InEnvironment, InferenceResult, TraitEnvironment, TraitRef, Ty, TyDefId, TypeCtor, + TypeWalk, }, CallableDef, Either, HirDisplay, Name, Source, }; @@ -498,12 +499,9 @@ impl Adt { let subst = db.generic_defaults(self.into()); subst.iter().any(|ty| ty == &Ty::Unknown) } - pub fn ty(self, db: &impl HirDatabase) -> Ty { - match self { - Adt::Struct(it) => it.ty(db), - Adt::Union(it) => it.ty(db), - Adt::Enum(it) => it.ty(db), - } + pub fn ty(self, db: &impl HirDatabase) -> Type { + let id = AdtId::from(self); + Type::from_def(db, id.module(db).krate, id) } pub fn module(self, db: &impl DefDatabase) -> Module { @@ -795,8 +793,8 @@ impl TypeAlias { db.type_alias_data(self.id).type_ref.clone() } - pub fn ty(self, db: &impl HirDatabase) -> Ty { - db.ty(self.id.into()) + pub fn ty(self, db: &impl HirDatabase) -> Type { + Type::from_def(db, self.id.lookup(db).module(db).krate, self.id) } pub fn name(self, db: &impl DefDatabase) -> Name { @@ -989,6 +987,17 @@ pub struct Type { } impl Type { + fn from_def( + db: &impl HirDatabase, + krate: CrateId, + def: impl HasResolver + Into, + ) -> Type { + let resolver = def.resolver(db); + let environment = TraitEnvironment::lower(db, &resolver); + let ty = db.ty(def.into()); + Type { krate, ty: InEnvironment { value: ty, environment } } + } + pub fn is_bool(&self) -> bool { match &self.ty.value { Ty::Apply(a_ty) => match a_ty.ctor { @@ -1097,6 +1106,28 @@ impl Type { .map(move |ty| self.derived(ty)) } + // This would be nicer if it just returned an iterator, but that runs into + // lifetime problems, because we need to borrow temp `CrateImplBlocks`. + pub fn iterate_impl_items( + self, + db: &impl HirDatabase, + krate: Crate, + mut callback: impl FnMut(AssocItem) -> Option, + ) -> Option { + for krate in self.ty.value.def_crates(db, krate.crate_id)? { + let impls = db.impls_in_crate(krate); + + for impl_block in impls.lookup_impl_blocks(&self.ty.value) { + for &item in db.impl_data(impl_block).items.iter() { + if let Some(result) = callback(item.into()) { + return Some(result); + } + } + } + } + None + } + // FIXME: remove pub fn into_ty(self) -> Ty { self.ty.value diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index 7c4ebd4b4..9f3e6c43f 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -389,14 +389,14 @@ impl SourceAnalyzer { pub fn iterate_path_candidates( &self, db: &impl HirDatabase, - ty: Ty, + ty: &Type, name: Option<&Name>, callback: impl FnMut(&Ty, AssocItem) -> Option, ) -> Option { // There should be no inference vars in types passed here // FIXME check that? // FIXME replace Unknown by bound vars here - let canonical = crate::ty::Canonical { value: ty, num_vars: 0 }; + let canonical = crate::ty::Canonical { value: ty.ty.value.clone(), num_vars: 0 }; method_resolution::iterate_method_candidates( &canonical, db, diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index 709492d21..1c0f71adc 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -189,7 +189,7 @@ impl Ty { Ty::Param { idx, name } } TypeNs::SelfType(impl_block) => ImplBlock::from(impl_block).target_ty(db), - TypeNs::AdtSelfType(adt) => Adt::from(adt).ty(db), + TypeNs::AdtSelfType(adt) => db.ty(adt.into()), TypeNs::AdtId(it) => Ty::from_hir_path_inner(db, resolver, resolved_segment, it.into()), TypeNs::BuiltinType(it) => { diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index fdc87a28d..92645e2a5 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -97,14 +97,15 @@ impl CrateImplBlocks { } } -fn def_crates( - db: &impl HirDatabase, - cur_crate: CrateId, - ty: &Ty, -) -> Option> { - // Types like slice can have inherent impls in several crates, (core and alloc). - // The corresponding impls are marked with lang items, so we can use them to find the required crates. - macro_rules! lang_item_crate { +impl Ty { + pub(crate) fn def_crates( + &self, + db: &impl HirDatabase, + cur_crate: CrateId, + ) -> Option> { + // Types like slice can have inherent impls in several crates, (core and alloc). + // The corresponding impls are marked with lang items, so we can use them to find the required crates. + macro_rules! lang_item_crate { ($($name:expr),+ $(,)?) => {{ let mut v = ArrayVec::<[LangItemTarget; 2]>::new(); $( @@ -114,38 +115,38 @@ fn def_crates( }}; } - let lang_item_targets = match ty { - Ty::Apply(a_ty) => match a_ty.ctor { - TypeCtor::Adt(def_id) => { - return Some(std::iter::once(def_id.module(db).krate).collect()) - } - TypeCtor::Bool => lang_item_crate!("bool"), - TypeCtor::Char => lang_item_crate!("char"), - TypeCtor::Float(Uncertain::Known(f)) => match f.bitness { - // There are two lang items: one in libcore (fXX) and one in libstd (fXX_runtime) - FloatBitness::X32 => lang_item_crate!("f32", "f32_runtime"), - FloatBitness::X64 => lang_item_crate!("f64", "f64_runtime"), + let lang_item_targets = match self { + Ty::Apply(a_ty) => match a_ty.ctor { + TypeCtor::Adt(def_id) => { + return Some(std::iter::once(def_id.module(db).krate).collect()) + } + TypeCtor::Bool => lang_item_crate!("bool"), + TypeCtor::Char => lang_item_crate!("char"), + TypeCtor::Float(Uncertain::Known(f)) => match f.bitness { + // There are two lang items: one in libcore (fXX) and one in libstd (fXX_runtime) + FloatBitness::X32 => lang_item_crate!("f32", "f32_runtime"), + FloatBitness::X64 => lang_item_crate!("f64", "f64_runtime"), + }, + TypeCtor::Int(Uncertain::Known(i)) => lang_item_crate!(i.ty_to_string()), + TypeCtor::Str => lang_item_crate!("str_alloc", "str"), + TypeCtor::Slice => lang_item_crate!("slice_alloc", "slice"), + TypeCtor::RawPtr(Mutability::Shared) => lang_item_crate!("const_ptr"), + TypeCtor::RawPtr(Mutability::Mut) => lang_item_crate!("mut_ptr"), + _ => return None, }, - TypeCtor::Int(Uncertain::Known(i)) => lang_item_crate!(i.ty_to_string()), - TypeCtor::Str => lang_item_crate!("str_alloc", "str"), - TypeCtor::Slice => lang_item_crate!("slice_alloc", "slice"), - TypeCtor::RawPtr(Mutability::Shared) => lang_item_crate!("const_ptr"), - TypeCtor::RawPtr(Mutability::Mut) => lang_item_crate!("mut_ptr"), _ => return None, - }, - _ => return None, - }; - let res = lang_item_targets - .into_iter() - .filter_map(|it| match it { - LangItemTarget::ImplBlockId(it) => Some(it), - _ => None, - }) - .map(|it| it.module(db).krate) - .collect(); - Some(res) + }; + let res = lang_item_targets + .into_iter() + .filter_map(|it| match it { + LangItemTarget::ImplBlockId(it) => Some(it), + _ => None, + }) + .map(|it| it.module(db).krate) + .collect(); + Some(res) + } } - /// Look up the method with the given name, returning the actual autoderefed /// receiver type (but without autoref applied yet). pub(crate) fn lookup_method( @@ -286,7 +287,7 @@ fn iterate_inherent_methods( krate: CrateId, mut callback: impl FnMut(&Ty, AssocItem) -> Option, ) -> Option { - for krate in def_crates(db, krate, &ty.value)? { + for krate in ty.value.def_crates(db, krate)? { let impls = db.impls_in_crate(krate); for impl_block in impls.lookup_impl_blocks(&ty.value) { @@ -342,30 +343,6 @@ pub(crate) fn implements_trait( solution.is_some() } -impl Ty { - // This would be nicer if it just returned an iterator, but that runs into - // lifetime problems, because we need to borrow temp `CrateImplBlocks`. - pub fn iterate_impl_items( - self, - db: &impl HirDatabase, - krate: CrateId, - mut callback: impl FnMut(AssocItem) -> Option, - ) -> Option { - for krate in def_crates(db, krate, &self)? { - let impls = db.impls_in_crate(krate); - - for impl_block in impls.lookup_impl_blocks(&self) { - for &item in db.impl_data(impl_block).items.iter() { - if let Some(result) = callback(item.into()) { - return Some(result); - } - } - } - } - None - } -} - /// This creates Substs for a trait with the given Self type and type variables /// for all other parameters, to query Chalk with it. fn generic_implements_goal( diff --git a/crates/ra_hir_def/src/resolver.rs b/crates/ra_hir_def/src/resolver.rs index c40f41717..5155365cc 100644 --- a/crates/ra_hir_def/src/resolver.rs +++ b/crates/ra_hir_def/src/resolver.rs @@ -484,7 +484,7 @@ impl Resolver { } } -pub trait HasResolver { +pub trait HasResolver: Copy { /// Builds a resolver for type references inside this def. fn resolver(self, db: &impl DefDatabase) -> Resolver; } @@ -502,7 +502,7 @@ impl HasResolver for TraitId { } } -impl> HasResolver for T { +impl + Copy> HasResolver for T { fn resolver(self, db: &impl DefDatabase) -> Resolver { let def = self.into(); def.module(db) diff --git a/crates/ra_ide_api/src/completion/complete_path.rs b/crates/ra_ide_api/src/completion/complete_path.rs index 63e25e0bf..89e0009a1 100644 --- a/crates/ra_ide_api/src/completion/complete_path.rs +++ b/crates/ra_ide_api/src/completion/complete_path.rs @@ -50,7 +50,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) { hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db), _ => unreachable!(), }; - ctx.analyzer.iterate_path_candidates(ctx.db, ty.clone(), None, |_ty, item| { + ctx.analyzer.iterate_path_candidates(ctx.db, &ty, None, |_ty, item| { match item { hir::AssocItem::Function(func) => { if !func.has_self_param(ctx.db) { -- cgit v1.2.3