diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/hir/src/semantics.rs | 92 | ||||
-rw-r--r-- | crates/hir/src/semantics/source_to_def.rs | 15 | ||||
-rw-r--r-- | crates/hir_def/src/child_by_source.rs | 4 | ||||
-rw-r--r-- | crates/hir_def/src/item_scope.rs | 17 | ||||
-rw-r--r-- | crates/hir_def/src/keys.rs | 3 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/collector.rs | 5 | ||||
-rw-r--r-- | crates/ide/src/expand_macro.rs | 34 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting.rs | 30 |
8 files changed, 170 insertions, 30 deletions
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index c7f2c02e4..2d08a7704 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs | |||
@@ -117,6 +117,16 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { | |||
117 | pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> { | 117 | pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> { |
118 | self.imp.expand(macro_call) | 118 | self.imp.expand(macro_call) |
119 | } | 119 | } |
120 | |||
121 | /// If `item` has an attribute macro attached to it, expands it. | ||
122 | pub fn expand_attr_macro(&self, item: &ast::Item) -> Option<SyntaxNode> { | ||
123 | self.imp.expand_attr_macro(item) | ||
124 | } | ||
125 | |||
126 | pub fn is_attr_macro_call(&self, item: &ast::Item) -> bool { | ||
127 | self.imp.is_attr_macro_call(item) | ||
128 | } | ||
129 | |||
120 | pub fn speculative_expand( | 130 | pub fn speculative_expand( |
121 | &self, | 131 | &self, |
122 | actual_macro_call: &ast::MacroCall, | 132 | actual_macro_call: &ast::MacroCall, |
@@ -332,6 +342,22 @@ impl<'db> SemanticsImpl<'db> { | |||
332 | Some(node) | 342 | Some(node) |
333 | } | 343 | } |
334 | 344 | ||
345 | fn expand_attr_macro(&self, item: &ast::Item) -> Option<SyntaxNode> { | ||
346 | let sa = self.analyze(item.syntax()); | ||
347 | let src = InFile::new(sa.file_id, item.clone()); | ||
348 | let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(src))?; | ||
349 | let file_id = macro_call_id.as_file(); | ||
350 | let node = self.db.parse_or_expand(file_id)?; | ||
351 | self.cache(node.clone(), file_id); | ||
352 | Some(node) | ||
353 | } | ||
354 | |||
355 | fn is_attr_macro_call(&self, item: &ast::Item) -> bool { | ||
356 | let sa = self.analyze(item.syntax()); | ||
357 | let src = InFile::new(sa.file_id, item.clone()); | ||
358 | self.with_ctx(|ctx| ctx.item_to_macro_call(src).is_some()) | ||
359 | } | ||
360 | |||
335 | fn speculative_expand( | 361 | fn speculative_expand( |
336 | &self, | 362 | &self, |
337 | actual_macro_call: &ast::MacroCall, | 363 | actual_macro_call: &ast::MacroCall, |
@@ -362,25 +388,57 @@ impl<'db> SemanticsImpl<'db> { | |||
362 | 388 | ||
363 | let token = successors(Some(InFile::new(sa.file_id, token)), |token| { | 389 | let token = successors(Some(InFile::new(sa.file_id, token)), |token| { |
364 | self.db.unwind_if_cancelled(); | 390 | self.db.unwind_if_cancelled(); |
365 | let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?; | 391 | |
366 | let tt = macro_call.token_tree()?; | 392 | for node in token.value.ancestors() { |
367 | if !tt.syntax().text_range().contains_range(token.value.text_range()) { | 393 | match_ast! { |
368 | return None; | 394 | match node { |
369 | } | 395 | ast::MacroCall(macro_call) => { |
370 | let file_id = sa.expand(self.db, token.with_value(¯o_call))?; | 396 | let tt = macro_call.token_tree()?; |
371 | let token = self | 397 | if !tt.syntax().text_range().contains_range(token.value.text_range()) { |
372 | .expansion_info_cache | 398 | return None; |
373 | .borrow_mut() | 399 | } |
374 | .entry(file_id) | 400 | let file_id = sa.expand(self.db, token.with_value(¯o_call))?; |
375 | .or_insert_with(|| file_id.expansion_info(self.db.upcast())) | 401 | let token = self |
376 | .as_ref()? | 402 | .expansion_info_cache |
377 | .map_token_down(token.as_ref())?; | 403 | .borrow_mut() |
378 | 404 | .entry(file_id) | |
379 | if let Some(parent) = token.value.parent() { | 405 | .or_insert_with(|| file_id.expansion_info(self.db.upcast())) |
380 | self.cache(find_root(&parent), token.file_id); | 406 | .as_ref()? |
407 | .map_token_down(token.as_ref())?; | ||
408 | |||
409 | if let Some(parent) = token.value.parent() { | ||
410 | self.cache(find_root(&parent), token.file_id); | ||
411 | } | ||
412 | |||
413 | return Some(token); | ||
414 | }, | ||
415 | ast::Item(item) => { | ||
416 | match self.with_ctx(|ctx| ctx.item_to_macro_call(token.with_value(item))) { | ||
417 | Some(call_id) => { | ||
418 | let file_id = call_id.as_file(); | ||
419 | let token = self | ||
420 | .expansion_info_cache | ||
421 | .borrow_mut() | ||
422 | .entry(file_id) | ||
423 | .or_insert_with(|| file_id.expansion_info(self.db.upcast())) | ||
424 | .as_ref()? | ||
425 | .map_token_down(token.as_ref())?; | ||
426 | |||
427 | if let Some(parent) = token.value.parent() { | ||
428 | self.cache(find_root(&parent), token.file_id); | ||
429 | } | ||
430 | |||
431 | return Some(token); | ||
432 | } | ||
433 | None => {} | ||
434 | } | ||
435 | }, | ||
436 | _ => {} | ||
437 | } | ||
438 | } | ||
381 | } | 439 | } |
382 | 440 | ||
383 | Some(token) | 441 | None |
384 | }) | 442 | }) |
385 | .last() | 443 | .last() |
386 | .unwrap(); | 444 | .unwrap(); |
diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index 9a5a2255f..22e196196 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs | |||
@@ -10,7 +10,7 @@ use hir_def::{ | |||
10 | ImplId, LifetimeParamId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, | 10 | ImplId, LifetimeParamId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, |
11 | UnionId, VariantId, | 11 | UnionId, VariantId, |
12 | }; | 12 | }; |
13 | use hir_expand::{name::AsName, AstId, MacroDefKind}; | 13 | use hir_expand::{name::AsName, AstId, MacroCallId, MacroDefKind}; |
14 | use rustc_hash::FxHashMap; | 14 | use rustc_hash::FxHashMap; |
15 | use smallvec::SmallVec; | 15 | use smallvec::SmallVec; |
16 | use stdx::impl_from; | 16 | use stdx::impl_from; |
@@ -145,16 +145,25 @@ impl SourceToDefCtx<'_, '_> { | |||
145 | Some((container, label_id)) | 145 | Some((container, label_id)) |
146 | } | 146 | } |
147 | 147 | ||
148 | pub(super) fn item_to_macro_call(&mut self, src: InFile<ast::Item>) -> Option<MacroCallId> { | ||
149 | let map = self.dyn_map(src.as_ref())?; | ||
150 | map[keys::ATTR_MACRO].get(&src).copied() | ||
151 | } | ||
152 | |||
148 | fn to_def<Ast: AstNode + 'static, ID: Copy + 'static>( | 153 | fn to_def<Ast: AstNode + 'static, ID: Copy + 'static>( |
149 | &mut self, | 154 | &mut self, |
150 | src: InFile<Ast>, | 155 | src: InFile<Ast>, |
151 | key: Key<Ast, ID>, | 156 | key: Key<Ast, ID>, |
152 | ) -> Option<ID> { | 157 | ) -> Option<ID> { |
153 | let container = self.find_container(src.as_ref().map(|it| it.syntax()))?; | 158 | self.dyn_map(src.as_ref())?[key].get(&src).copied() |
159 | } | ||
160 | |||
161 | fn dyn_map<Ast: AstNode + 'static>(&mut self, src: InFile<&Ast>) -> Option<&DynMap> { | ||
162 | let container = self.find_container(src.map(|it| it.syntax()))?; | ||
154 | let db = self.db; | 163 | let db = self.db; |
155 | let dyn_map = | 164 | let dyn_map = |
156 | &*self.cache.entry(container).or_insert_with(|| container.child_by_source(db)); | 165 | &*self.cache.entry(container).or_insert_with(|| container.child_by_source(db)); |
157 | dyn_map[key].get(&src).copied() | 166 | Some(dyn_map) |
158 | } | 167 | } |
159 | 168 | ||
160 | pub(super) fn type_param_to_def(&mut self, src: InFile<ast::TypeParam>) -> Option<TypeParamId> { | 169 | pub(super) fn type_param_to_def(&mut self, src: InFile<ast::TypeParam>) -> Option<TypeParamId> { |
diff --git a/crates/hir_def/src/child_by_source.rs b/crates/hir_def/src/child_by_source.rs index f2e809ca9..f22383e22 100644 --- a/crates/hir_def/src/child_by_source.rs +++ b/crates/hir_def/src/child_by_source.rs | |||
@@ -85,6 +85,10 @@ impl ChildBySource for ItemScope { | |||
85 | res[keys::CONST].insert(src, konst); | 85 | res[keys::CONST].insert(src, konst); |
86 | }); | 86 | }); |
87 | self.impls().for_each(|imp| add_impl(db, res, imp)); | 87 | self.impls().for_each(|imp| add_impl(db, res, imp)); |
88 | self.attr_macro_invocs().for_each(|(ast_id, call_id)| { | ||
89 | let item = ast_id.with_value(ast_id.to_node(db.upcast())); | ||
90 | res[keys::ATTR_MACRO].insert(item, call_id); | ||
91 | }); | ||
88 | 92 | ||
89 | fn add_module_def(db: &dyn DefDatabase, map: &mut DynMap, item: ModuleDefId) { | 93 | fn add_module_def(db: &dyn DefDatabase, map: &mut DynMap, item: ModuleDefId) { |
90 | match item { | 94 | match item { |
diff --git a/crates/hir_def/src/item_scope.rs b/crates/hir_def/src/item_scope.rs index 9014468ea..0f74f050d 100644 --- a/crates/hir_def/src/item_scope.rs +++ b/crates/hir_def/src/item_scope.rs | |||
@@ -4,11 +4,11 @@ | |||
4 | use std::collections::hash_map::Entry; | 4 | use std::collections::hash_map::Entry; |
5 | 5 | ||
6 | use base_db::CrateId; | 6 | use base_db::CrateId; |
7 | use hir_expand::name::Name; | 7 | use hir_expand::{name::Name, AstId, MacroCallId, MacroDefKind}; |
8 | use hir_expand::MacroDefKind; | ||
9 | use once_cell::sync::Lazy; | 8 | use once_cell::sync::Lazy; |
10 | use rustc_hash::{FxHashMap, FxHashSet}; | 9 | use rustc_hash::{FxHashMap, FxHashSet}; |
11 | use stdx::format_to; | 10 | use stdx::format_to; |
11 | use syntax::ast; | ||
12 | 12 | ||
13 | use crate::{ | 13 | use crate::{ |
14 | db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ConstId, ImplId, | 14 | db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ConstId, ImplId, |
@@ -53,6 +53,7 @@ pub struct ItemScope { | |||
53 | // FIXME: Macro shadowing in one module is not properly handled. Non-item place macros will | 53 | // FIXME: Macro shadowing in one module is not properly handled. Non-item place macros will |
54 | // be all resolved to the last one defined if shadowing happens. | 54 | // be all resolved to the last one defined if shadowing happens. |
55 | legacy_macros: FxHashMap<Name, MacroDefId>, | 55 | legacy_macros: FxHashMap<Name, MacroDefId>, |
56 | attr_macros: FxHashMap<AstId<ast::Item>, MacroCallId>, | ||
56 | } | 57 | } |
57 | 58 | ||
58 | pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| { | 59 | pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| { |
@@ -169,6 +170,16 @@ impl ItemScope { | |||
169 | self.legacy_macros.insert(name, mac); | 170 | self.legacy_macros.insert(name, mac); |
170 | } | 171 | } |
171 | 172 | ||
173 | pub(crate) fn add_attr_macro_invoc(&mut self, item: AstId<ast::Item>, call: MacroCallId) { | ||
174 | self.attr_macros.insert(item, call); | ||
175 | } | ||
176 | |||
177 | pub(crate) fn attr_macro_invocs( | ||
178 | &self, | ||
179 | ) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ { | ||
180 | self.attr_macros.iter().map(|(k, v)| (*k, *v)) | ||
181 | } | ||
182 | |||
172 | pub(crate) fn unnamed_trait_vis(&self, tr: TraitId) -> Option<Visibility> { | 183 | pub(crate) fn unnamed_trait_vis(&self, tr: TraitId) -> Option<Visibility> { |
173 | self.unnamed_trait_imports.get(&tr).copied() | 184 | self.unnamed_trait_imports.get(&tr).copied() |
174 | } | 185 | } |
@@ -307,6 +318,7 @@ impl ItemScope { | |||
307 | unnamed_consts, | 318 | unnamed_consts, |
308 | unnamed_trait_imports, | 319 | unnamed_trait_imports, |
309 | legacy_macros, | 320 | legacy_macros, |
321 | attr_macros, | ||
310 | } = self; | 322 | } = self; |
311 | types.shrink_to_fit(); | 323 | types.shrink_to_fit(); |
312 | values.shrink_to_fit(); | 324 | values.shrink_to_fit(); |
@@ -317,6 +329,7 @@ impl ItemScope { | |||
317 | unnamed_consts.shrink_to_fit(); | 329 | unnamed_consts.shrink_to_fit(); |
318 | unnamed_trait_imports.shrink_to_fit(); | 330 | unnamed_trait_imports.shrink_to_fit(); |
319 | legacy_macros.shrink_to_fit(); | 331 | legacy_macros.shrink_to_fit(); |
332 | attr_macros.shrink_to_fit(); | ||
320 | } | 333 | } |
321 | } | 334 | } |
322 | 335 | ||
diff --git a/crates/hir_def/src/keys.rs b/crates/hir_def/src/keys.rs index 89b3ed868..688cd9fcf 100644 --- a/crates/hir_def/src/keys.rs +++ b/crates/hir_def/src/keys.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | use std::marker::PhantomData; | 3 | use std::marker::PhantomData; |
4 | 4 | ||
5 | use hir_expand::{InFile, MacroDefId}; | 5 | use hir_expand::{InFile, MacroCallId, MacroDefId}; |
6 | use rustc_hash::FxHashMap; | 6 | use rustc_hash::FxHashMap; |
7 | use syntax::{ast, AstNode, AstPtr}; | 7 | use syntax::{ast, AstNode, AstPtr}; |
8 | 8 | ||
@@ -32,6 +32,7 @@ pub const LIFETIME_PARAM: Key<ast::LifetimeParam, LifetimeParamId> = Key::new(); | |||
32 | pub const CONST_PARAM: Key<ast::ConstParam, ConstParamId> = Key::new(); | 32 | pub const CONST_PARAM: Key<ast::ConstParam, ConstParamId> = Key::new(); |
33 | 33 | ||
34 | pub const MACRO: Key<ast::MacroCall, MacroDefId> = Key::new(); | 34 | pub const MACRO: Key<ast::MacroCall, MacroDefId> = Key::new(); |
35 | pub const ATTR_MACRO: Key<ast::Item, MacroCallId> = Key::new(); | ||
35 | 36 | ||
36 | /// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are | 37 | /// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are |
37 | /// equal if they point to exactly the same object. | 38 | /// equal if they point to exactly the same object. |
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index d0b1db5d1..d019ba3a9 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs | |||
@@ -1112,6 +1112,11 @@ impl DefCollector<'_> { | |||
1112 | return false; | 1112 | return false; |
1113 | } | 1113 | } |
1114 | } | 1114 | } |
1115 | |||
1116 | self.def_map.modules[directive.module_id] | ||
1117 | .scope | ||
1118 | .add_attr_macro_invoc(ast_id.ast_id, call_id); | ||
1119 | |||
1115 | resolved.push((directive.module_id, call_id, directive.depth)); | 1120 | resolved.push((directive.module_id, call_id, directive.depth)); |
1116 | res = ReachedFixedPoint::No; | 1121 | res = ReachedFixedPoint::No; |
1117 | return false; | 1122 | return false; |
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs index e0d01fa96..3f38e2145 100644 --- a/crates/ide/src/expand_macro.rs +++ b/crates/ide/src/expand_macro.rs | |||
@@ -3,8 +3,7 @@ use std::iter; | |||
3 | use hir::Semantics; | 3 | use hir::Semantics; |
4 | use ide_db::RootDatabase; | 4 | use ide_db::RootDatabase; |
5 | use syntax::{ | 5 | use syntax::{ |
6 | algo::find_node_at_offset, ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxKind::*, | 6 | ast, match_ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxKind::*, SyntaxNode, WalkEvent, T, |
7 | SyntaxNode, WalkEvent, T, | ||
8 | }; | 7 | }; |
9 | 8 | ||
10 | use crate::FilePosition; | 9 | use crate::FilePosition; |
@@ -28,16 +27,37 @@ pub struct ExpandedMacro { | |||
28 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { | 27 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { |
29 | let sema = Semantics::new(db); | 28 | let sema = Semantics::new(db); |
30 | let file = sema.parse(position.file_id); | 29 | let file = sema.parse(position.file_id); |
31 | let mac = find_node_at_offset::<ast::MacroCall>(file.syntax(), position.offset)?; | ||
32 | let name = mac.path()?.segment()?.name_ref()?; | ||
33 | 30 | ||
34 | let expanded = expand_macro_recur(&sema, &mac)?; | 31 | let tok = file.syntax().token_at_offset(position.offset).left_biased()?; |
32 | let mut expanded = None; | ||
33 | let mut name = None; | ||
34 | for node in tok.ancestors() { | ||
35 | match_ast! { | ||
36 | match node { | ||
37 | ast::MacroCall(mac) => { | ||
38 | name = Some(mac.path()?.segment()?.name_ref()?.to_string()); | ||
39 | expanded = expand_macro_recur(&sema, &mac); | ||
40 | break; | ||
41 | }, | ||
42 | ast::Item(item) => { | ||
43 | // FIXME: add the macro name | ||
44 | // FIXME: make this recursive too | ||
45 | name = Some("?".to_string()); | ||
46 | expanded = sema.expand_attr_macro(&item); | ||
47 | if expanded.is_some() { | ||
48 | break; | ||
49 | } | ||
50 | }, | ||
51 | _ => {} | ||
52 | } | ||
53 | } | ||
54 | } | ||
35 | 55 | ||
36 | // FIXME: | 56 | // FIXME: |
37 | // macro expansion may lose all white space information | 57 | // macro expansion may lose all white space information |
38 | // But we hope someday we can use ra_fmt for that | 58 | // But we hope someday we can use ra_fmt for that |
39 | let expansion = insert_whitespaces(expanded); | 59 | let expansion = insert_whitespaces(expanded?); |
40 | Some(ExpandedMacro { name: name.to_string(), expansion }) | 60 | Some(ExpandedMacro { name: name?, expansion }) |
41 | } | 61 | } |
42 | 62 | ||
43 | fn expand_macro_recur( | 63 | fn expand_macro_recur( |
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 79c2f4a1e..b03f1c71f 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -192,6 +192,7 @@ fn traverse( | |||
192 | let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); | 192 | let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); |
193 | 193 | ||
194 | let mut current_macro_call: Option<ast::MacroCall> = None; | 194 | let mut current_macro_call: Option<ast::MacroCall> = None; |
195 | let mut current_attr_macro_call = None; | ||
195 | let mut current_macro: Option<ast::Macro> = None; | 196 | let mut current_macro: Option<ast::Macro> = None; |
196 | let mut macro_highlighter = MacroHighlighter::default(); | 197 | let mut macro_highlighter = MacroHighlighter::default(); |
197 | let mut inside_attribute = false; | 198 | let mut inside_attribute = false; |
@@ -227,6 +228,19 @@ fn traverse( | |||
227 | } | 228 | } |
228 | _ => (), | 229 | _ => (), |
229 | } | 230 | } |
231 | match event.clone().map(|it| it.into_node().and_then(ast::Item::cast)) { | ||
232 | WalkEvent::Enter(Some(item)) => { | ||
233 | if sema.is_attr_macro_call(&item) { | ||
234 | current_attr_macro_call = Some(item); | ||
235 | } | ||
236 | } | ||
237 | WalkEvent::Leave(Some(item)) => { | ||
238 | if current_attr_macro_call == Some(item) { | ||
239 | current_attr_macro_call = None; | ||
240 | } | ||
241 | } | ||
242 | _ => (), | ||
243 | } | ||
230 | 244 | ||
231 | match event.clone().map(|it| it.into_node().and_then(ast::Macro::cast)) { | 245 | match event.clone().map(|it| it.into_node().and_then(ast::Macro::cast)) { |
232 | WalkEvent::Enter(Some(mac)) => { | 246 | WalkEvent::Enter(Some(mac)) => { |
@@ -286,6 +300,22 @@ fn traverse( | |||
286 | } | 300 | } |
287 | None => token.into(), | 301 | None => token.into(), |
288 | } | 302 | } |
303 | } else if current_attr_macro_call.is_some() { | ||
304 | let token = match element.clone().into_token() { | ||
305 | Some(it) => it, | ||
306 | _ => continue, | ||
307 | }; | ||
308 | let token = sema.descend_into_macros(token.clone()); | ||
309 | match token.parent() { | ||
310 | Some(parent) => { | ||
311 | // We only care Name and Name_ref | ||
312 | match (token.kind(), parent.kind()) { | ||
313 | (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), | ||
314 | _ => token.into(), | ||
315 | } | ||
316 | } | ||
317 | None => token.into(), | ||
318 | } | ||
289 | } else { | 319 | } else { |
290 | element.clone() | 320 | element.clone() |
291 | }; | 321 | }; |