diff options
author | Aleksey Kladov <[email protected]> | 2020-02-26 12:04:22 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-02-26 12:04:22 +0000 |
commit | 5c64ad27e041bcdb281c0a751720ceb3a6369d04 (patch) | |
tree | 12d89798f61b276f8bd640db07276a7d4e92b1c2 /crates/ra_ide/src/completion | |
parent | 04deae3dba7c9b7054f7a1d64e4b93a05aecc132 (diff) | |
parent | c3a4c4429de83450654795534e64e878a774a088 (diff) |
Merge pull request #3222 from matklad/identity
Introduce Semantics API
Diffstat (limited to 'crates/ra_ide/src/completion')
10 files changed, 66 insertions, 60 deletions
diff --git a/crates/ra_ide/src/completion/complete_dot.rs b/crates/ra_ide/src/completion/complete_dot.rs index 2ca78c927..a6e0158b2 100644 --- a/crates/ra_ide/src/completion/complete_dot.rs +++ b/crates/ra_ide/src/completion/complete_dot.rs | |||
@@ -16,7 +16,7 @@ pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { | |||
16 | _ => return, | 16 | _ => return, |
17 | }; | 17 | }; |
18 | 18 | ||
19 | let receiver_ty = match ctx.analyzer.type_of(ctx.db, &dot_receiver) { | 19 | let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { |
20 | Some(ty) => ty, | 20 | Some(ty) => ty, |
21 | _ => return, | 21 | _ => return, |
22 | }; | 22 | }; |
@@ -55,7 +55,7 @@ fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Ty | |||
55 | fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { | 55 | fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { |
56 | if let Some(krate) = ctx.module.map(|it| it.krate()) { | 56 | if let Some(krate) = ctx.module.map(|it| it.krate()) { |
57 | let mut seen_methods = FxHashSet::default(); | 57 | let mut seen_methods = FxHashSet::default(); |
58 | let traits_in_scope = ctx.analyzer.traits_in_scope(ctx.db); | 58 | let traits_in_scope = ctx.scope().traits_in_scope(); |
59 | receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| { | 59 | receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| { |
60 | if func.has_self_param(ctx.db) && seen_methods.insert(func.name(ctx.db)) { | 60 | if func.has_self_param(ctx.db) && seen_methods.insert(func.name(ctx.db)) { |
61 | acc.add_function(ctx, func); | 61 | acc.add_function(ctx, func); |
diff --git a/crates/ra_ide/src/completion/complete_macro_in_item_position.rs b/crates/ra_ide/src/completion/complete_macro_in_item_position.rs index faadd1e3f..1866d9e6c 100644 --- a/crates/ra_ide/src/completion/complete_macro_in_item_position.rs +++ b/crates/ra_ide/src/completion/complete_macro_in_item_position.rs | |||
@@ -5,7 +5,7 @@ use crate::completion::{CompletionContext, Completions}; | |||
5 | pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) { | 5 | pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) { |
6 | // Show only macros in top level. | 6 | // Show only macros in top level. |
7 | if ctx.is_new_item { | 7 | if ctx.is_new_item { |
8 | ctx.analyzer.process_all_names(ctx.db, &mut |name, res| { | 8 | ctx.scope().process_all_names(&mut |name, res| { |
9 | if let hir::ScopeDef::MacroDef(mac) = res { | 9 | if let hir::ScopeDef::MacroDef(mac) = res { |
10 | acc.add_macro(ctx, Some(name.to_string()), mac); | 10 | acc.add_macro(ctx, Some(name.to_string()), mac); |
11 | } | 11 | } |
diff --git a/crates/ra_ide/src/completion/complete_path.rs b/crates/ra_ide/src/completion/complete_path.rs index 2d7f09a6c..c626e90cc 100644 --- a/crates/ra_ide/src/completion/complete_path.rs +++ b/crates/ra_ide/src/completion/complete_path.rs | |||
@@ -11,7 +11,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) { | |||
11 | Some(path) => path.clone(), | 11 | Some(path) => path.clone(), |
12 | _ => return, | 12 | _ => return, |
13 | }; | 13 | }; |
14 | let def = match ctx.analyzer.resolve_hir_path(ctx.db, &path) { | 14 | let def = match ctx.scope().resolve_hir_path(&path) { |
15 | Some(PathResolution::Def(def)) => def, | 15 | Some(PathResolution::Def(def)) => def, |
16 | _ => return, | 16 | _ => return, |
17 | }; | 17 | }; |
@@ -49,7 +49,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) { | |||
49 | // FIXME: complete T::AssocType | 49 | // FIXME: complete T::AssocType |
50 | let krate = ctx.module.map(|m| m.krate()); | 50 | let krate = ctx.module.map(|m| m.krate()); |
51 | if let Some(krate) = krate { | 51 | if let Some(krate) = krate { |
52 | let traits_in_scope = ctx.analyzer.traits_in_scope(ctx.db); | 52 | let traits_in_scope = ctx.scope().traits_in_scope(); |
53 | ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| { | 53 | ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| { |
54 | match item { | 54 | match item { |
55 | hir::AssocItem::Function(func) => { | 55 | hir::AssocItem::Function(func) => { |
diff --git a/crates/ra_ide/src/completion/complete_pattern.rs b/crates/ra_ide/src/completion/complete_pattern.rs index fd03b1c40..c2c6ca002 100644 --- a/crates/ra_ide/src/completion/complete_pattern.rs +++ b/crates/ra_ide/src/completion/complete_pattern.rs | |||
@@ -9,7 +9,7 @@ pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { | |||
9 | } | 9 | } |
10 | // FIXME: ideally, we should look at the type we are matching against and | 10 | // FIXME: ideally, we should look at the type we are matching against and |
11 | // suggest variants + auto-imports | 11 | // suggest variants + auto-imports |
12 | ctx.analyzer.process_all_names(ctx.db, &mut |name, res| { | 12 | ctx.scope().process_all_names(&mut |name, res| { |
13 | let def = match &res { | 13 | let def = match &res { |
14 | hir::ScopeDef::ModuleDef(def) => def, | 14 | hir::ScopeDef::ModuleDef(def) => def, |
15 | _ => return, | 15 | _ => return, |
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index 5470dc291..8a74f993a 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs | |||
@@ -29,7 +29,7 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
29 | dot_receiver.syntax().text().to_string() | 29 | dot_receiver.syntax().text().to_string() |
30 | }; | 30 | }; |
31 | 31 | ||
32 | let receiver_ty = match ctx.analyzer.type_of(ctx.db, &dot_receiver) { | 32 | let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { |
33 | Some(it) => it, | 33 | Some(it) => it, |
34 | None => return, | 34 | None => return, |
35 | }; | 35 | }; |
diff --git a/crates/ra_ide/src/completion/complete_record_literal.rs b/crates/ra_ide/src/completion/complete_record_literal.rs index 577c394d2..f98353d76 100644 --- a/crates/ra_ide/src/completion/complete_record_literal.rs +++ b/crates/ra_ide/src/completion/complete_record_literal.rs | |||
@@ -5,10 +5,7 @@ use crate::completion::{CompletionContext, Completions}; | |||
5 | /// Complete fields in fields literals. | 5 | /// Complete fields in fields literals. |
6 | pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionContext) { | 6 | pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionContext) { |
7 | let (ty, variant) = match ctx.record_lit_syntax.as_ref().and_then(|it| { | 7 | let (ty, variant) = match ctx.record_lit_syntax.as_ref().and_then(|it| { |
8 | Some(( | 8 | Some((ctx.sema.type_of_expr(&it.clone().into())?, ctx.sema.resolve_record_literal(it)?)) |
9 | ctx.analyzer.type_of(ctx.db, &it.clone().into())?, | ||
10 | ctx.analyzer.resolve_record_literal(it)?, | ||
11 | )) | ||
12 | }) { | 9 | }) { |
13 | Some(it) => it, | 10 | Some(it) => it, |
14 | _ => return, | 11 | _ => return, |
diff --git a/crates/ra_ide/src/completion/complete_record_pattern.rs b/crates/ra_ide/src/completion/complete_record_pattern.rs index a56c7e3a1..9bdeae49f 100644 --- a/crates/ra_ide/src/completion/complete_record_pattern.rs +++ b/crates/ra_ide/src/completion/complete_record_pattern.rs | |||
@@ -4,10 +4,7 @@ use crate::completion::{CompletionContext, Completions}; | |||
4 | 4 | ||
5 | pub(super) fn complete_record_pattern(acc: &mut Completions, ctx: &CompletionContext) { | 5 | pub(super) fn complete_record_pattern(acc: &mut Completions, ctx: &CompletionContext) { |
6 | let (ty, variant) = match ctx.record_lit_pat.as_ref().and_then(|it| { | 6 | let (ty, variant) = match ctx.record_lit_pat.as_ref().and_then(|it| { |
7 | Some(( | 7 | Some((ctx.sema.type_of_pat(&it.clone().into())?, ctx.sema.resolve_record_pattern(it)?)) |
8 | ctx.analyzer.type_of_pat(ctx.db, &it.clone().into())?, | ||
9 | ctx.analyzer.resolve_record_pattern(it)?, | ||
10 | )) | ||
11 | }) { | 8 | }) { |
12 | Some(it) => it, | 9 | Some(it) => it, |
13 | _ => return, | 10 | _ => return, |
diff --git a/crates/ra_ide/src/completion/complete_scope.rs b/crates/ra_ide/src/completion/complete_scope.rs index e2ee86dd1..aad016d4a 100644 --- a/crates/ra_ide/src/completion/complete_scope.rs +++ b/crates/ra_ide/src/completion/complete_scope.rs | |||
@@ -7,9 +7,7 @@ pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) { | |||
7 | return; | 7 | return; |
8 | } | 8 | } |
9 | 9 | ||
10 | ctx.analyzer.process_all_names(ctx.db, &mut |name, res| { | 10 | ctx.scope().process_all_names(&mut |name, res| acc.add_resolution(ctx, name.to_string(), &res)); |
11 | acc.add_resolution(ctx, name.to_string(), &res) | ||
12 | }); | ||
13 | } | 11 | } |
14 | 12 | ||
15 | #[cfg(test)] | 13 | #[cfg(test)] |
diff --git a/crates/ra_ide/src/completion/complete_trait_impl.rs b/crates/ra_ide/src/completion/complete_trait_impl.rs index 83628e35c..9a27c164b 100644 --- a/crates/ra_ide/src/completion/complete_trait_impl.rs +++ b/crates/ra_ide/src/completion/complete_trait_impl.rs | |||
@@ -64,11 +64,12 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext | |||
64 | if let (Some(trigger), Some(impl_block)) = (trigger, impl_block) { | 64 | if let (Some(trigger), Some(impl_block)) = (trigger, impl_block) { |
65 | match trigger.kind() { | 65 | match trigger.kind() { |
66 | SyntaxKind::FN_DEF => { | 66 | SyntaxKind::FN_DEF => { |
67 | for missing_fn in get_missing_impl_items(ctx.db, &ctx.analyzer, &impl_block) | 67 | for missing_fn in |
68 | .iter() | 68 | get_missing_impl_items(&ctx.sema, &impl_block).iter().filter_map(|item| { |
69 | .filter_map(|item| match item { | 69 | match item { |
70 | hir::AssocItem::Function(fn_item) => Some(fn_item), | 70 | hir::AssocItem::Function(fn_item) => Some(fn_item), |
71 | _ => None, | 71 | _ => None, |
72 | } | ||
72 | }) | 73 | }) |
73 | { | 74 | { |
74 | add_function_impl(&trigger, acc, ctx, &missing_fn); | 75 | add_function_impl(&trigger, acc, ctx, &missing_fn); |
@@ -76,11 +77,12 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext | |||
76 | } | 77 | } |
77 | 78 | ||
78 | SyntaxKind::TYPE_ALIAS_DEF => { | 79 | SyntaxKind::TYPE_ALIAS_DEF => { |
79 | for missing_fn in get_missing_impl_items(ctx.db, &ctx.analyzer, &impl_block) | 80 | for missing_fn in |
80 | .iter() | 81 | get_missing_impl_items(&ctx.sema, &impl_block).iter().filter_map(|item| { |
81 | .filter_map(|item| match item { | 82 | match item { |
82 | hir::AssocItem::TypeAlias(type_item) => Some(type_item), | 83 | hir::AssocItem::TypeAlias(type_item) => Some(type_item), |
83 | _ => None, | 84 | _ => None, |
85 | } | ||
84 | }) | 86 | }) |
85 | { | 87 | { |
86 | add_type_alias_impl(&trigger, acc, ctx, &missing_fn); | 88 | add_type_alias_impl(&trigger, acc, ctx, &missing_fn); |
@@ -88,11 +90,12 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext | |||
88 | } | 90 | } |
89 | 91 | ||
90 | SyntaxKind::CONST_DEF => { | 92 | SyntaxKind::CONST_DEF => { |
91 | for missing_fn in get_missing_impl_items(ctx.db, &ctx.analyzer, &impl_block) | 93 | for missing_fn in |
92 | .iter() | 94 | get_missing_impl_items(&ctx.sema, &impl_block).iter().filter_map(|item| { |
93 | .filter_map(|item| match item { | 95 | match item { |
94 | hir::AssocItem::Const(const_item) => Some(const_item), | 96 | hir::AssocItem::Const(const_item) => Some(const_item), |
95 | _ => None, | 97 | _ => None, |
98 | } | ||
96 | }) | 99 | }) |
97 | { | 100 | { |
98 | add_const_impl(&trigger, acc, ctx, &missing_fn); | 101 | add_const_impl(&trigger, acc, ctx, &missing_fn); |
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index 8678a3234..81321a897 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -1,9 +1,11 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use hir::{Semantics, SemanticsScope}; | ||
4 | use ra_db::SourceDatabase; | ||
3 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
4 | use ra_syntax::{ | 6 | use ra_syntax::{ |
5 | algo::{find_covering_element, find_node_at_offset}, | 7 | algo::{find_covering_element, find_node_at_offset}, |
6 | ast, AstNode, Parse, SourceFile, | 8 | ast, AstNode, SourceFile, |
7 | SyntaxKind::*, | 9 | SyntaxKind::*, |
8 | SyntaxNode, SyntaxToken, TextRange, TextUnit, | 10 | SyntaxNode, SyntaxToken, TextRange, TextUnit, |
9 | }; | 11 | }; |
@@ -15,8 +17,8 @@ use crate::FilePosition; | |||
15 | /// exactly is the cursor, syntax-wise. | 17 | /// exactly is the cursor, syntax-wise. |
16 | #[derive(Debug)] | 18 | #[derive(Debug)] |
17 | pub(crate) struct CompletionContext<'a> { | 19 | pub(crate) struct CompletionContext<'a> { |
20 | pub(super) sema: Semantics<'a, RootDatabase>, | ||
18 | pub(super) db: &'a RootDatabase, | 21 | pub(super) db: &'a RootDatabase, |
19 | pub(super) analyzer: hir::SourceAnalyzer, | ||
20 | pub(super) offset: TextUnit, | 22 | pub(super) offset: TextUnit, |
21 | pub(super) token: SyntaxToken, | 23 | pub(super) token: SyntaxToken, |
22 | pub(super) module: Option<hir::Module>, | 24 | pub(super) module: Option<hir::Module>, |
@@ -51,20 +53,26 @@ pub(crate) struct CompletionContext<'a> { | |||
51 | impl<'a> CompletionContext<'a> { | 53 | impl<'a> CompletionContext<'a> { |
52 | pub(super) fn new( | 54 | pub(super) fn new( |
53 | db: &'a RootDatabase, | 55 | db: &'a RootDatabase, |
54 | original_parse: &'a Parse<ast::SourceFile>, | ||
55 | position: FilePosition, | 56 | position: FilePosition, |
56 | ) -> Option<CompletionContext<'a>> { | 57 | ) -> Option<CompletionContext<'a>> { |
57 | let mut sb = hir::SourceBinder::new(db); | 58 | let sema = Semantics::new(db); |
58 | let module = sb.to_module_def(position.file_id); | 59 | |
59 | let token = | 60 | let original_file = sema.parse(position.file_id); |
60 | original_parse.tree().syntax().token_at_offset(position.offset).left_biased()?; | 61 | |
61 | let analyzer = sb.analyze( | 62 | // Insert a fake ident to get a valid parse tree. We will use this file |
62 | hir::InFile::new(position.file_id.into(), &token.parent()), | 63 | // to determine context, though the original_file will be used for |
63 | Some(position.offset), | 64 | // actual completion. |
64 | ); | 65 | let file_with_fake_ident = { |
66 | let parse = db.parse(position.file_id); | ||
67 | let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); | ||
68 | parse.reparse(&edit).tree() | ||
69 | }; | ||
70 | |||
71 | let module = sema.to_module_def(position.file_id); | ||
72 | let token = original_file.syntax().token_at_offset(position.offset).left_biased()?; | ||
65 | let mut ctx = CompletionContext { | 73 | let mut ctx = CompletionContext { |
74 | sema, | ||
66 | db, | 75 | db, |
67 | analyzer, | ||
68 | token, | 76 | token, |
69 | offset: position.offset, | 77 | offset: position.offset, |
70 | module, | 78 | module, |
@@ -87,7 +95,7 @@ impl<'a> CompletionContext<'a> { | |||
87 | has_type_args: false, | 95 | has_type_args: false, |
88 | dot_receiver_is_ambiguous_float_literal: false, | 96 | dot_receiver_is_ambiguous_float_literal: false, |
89 | }; | 97 | }; |
90 | ctx.fill(&original_parse, position.offset); | 98 | ctx.fill(&original_file, file_with_fake_ident, position.offset); |
91 | Some(ctx) | 99 | Some(ctx) |
92 | } | 100 | } |
93 | 101 | ||
@@ -100,29 +108,33 @@ impl<'a> CompletionContext<'a> { | |||
100 | } | 108 | } |
101 | } | 109 | } |
102 | 110 | ||
103 | fn fill(&mut self, original_parse: &'a Parse<ast::SourceFile>, offset: TextUnit) { | 111 | pub(crate) fn scope(&self) -> SemanticsScope<'_, RootDatabase> { |
104 | // Insert a fake ident to get a valid parse tree. We will use this file | 112 | self.sema.scope_at_offset(&self.token.parent(), self.offset) |
105 | // to determine context, though the original_file will be used for | 113 | } |
106 | // actual completion. | ||
107 | let file = { | ||
108 | let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string()); | ||
109 | original_parse.reparse(&edit).tree() | ||
110 | }; | ||
111 | 114 | ||
115 | fn fill( | ||
116 | &mut self, | ||
117 | original_file: &ast::SourceFile, | ||
118 | file_with_fake_ident: ast::SourceFile, | ||
119 | offset: TextUnit, | ||
120 | ) { | ||
112 | // First, let's try to complete a reference to some declaration. | 121 | // First, let's try to complete a reference to some declaration. |
113 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) { | 122 | if let Some(name_ref) = |
123 | find_node_at_offset::<ast::NameRef>(file_with_fake_ident.syntax(), offset) | ||
124 | { | ||
114 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. | 125 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. |
115 | // See RFC#1685. | 126 | // See RFC#1685. |
116 | if is_node::<ast::Param>(name_ref.syntax()) { | 127 | if is_node::<ast::Param>(name_ref.syntax()) { |
117 | self.is_param = true; | 128 | self.is_param = true; |
118 | return; | 129 | return; |
119 | } | 130 | } |
120 | self.classify_name_ref(original_parse.tree(), name_ref); | 131 | self.classify_name_ref(original_file, name_ref); |
121 | } | 132 | } |
122 | 133 | ||
123 | // Otherwise, see if this is a declaration. We can use heuristics to | 134 | // Otherwise, see if this is a declaration. We can use heuristics to |
124 | // suggest declaration names, see `CompletionKind::Magic`. | 135 | // suggest declaration names, see `CompletionKind::Magic`. |
125 | if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) { | 136 | if let Some(name) = find_node_at_offset::<ast::Name>(file_with_fake_ident.syntax(), offset) |
137 | { | ||
126 | if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) { | 138 | if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) { |
127 | let parent = bind_pat.syntax().parent(); | 139 | let parent = bind_pat.syntax().parent(); |
128 | if parent.clone().and_then(ast::MatchArm::cast).is_some() | 140 | if parent.clone().and_then(ast::MatchArm::cast).is_some() |
@@ -136,13 +148,12 @@ impl<'a> CompletionContext<'a> { | |||
136 | return; | 148 | return; |
137 | } | 149 | } |
138 | if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() { | 150 | if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() { |
139 | self.record_lit_pat = | 151 | self.record_lit_pat = find_node_at_offset(original_file.syntax(), self.offset); |
140 | find_node_at_offset(original_parse.tree().syntax(), self.offset); | ||
141 | } | 152 | } |
142 | } | 153 | } |
143 | } | 154 | } |
144 | 155 | ||
145 | fn classify_name_ref(&mut self, original_file: SourceFile, name_ref: ast::NameRef) { | 156 | fn classify_name_ref(&mut self, original_file: &SourceFile, name_ref: ast::NameRef) { |
146 | self.name_ref_syntax = | 157 | self.name_ref_syntax = |
147 | find_node_at_offset(original_file.syntax(), name_ref.syntax().text_range().start()); | 158 | find_node_at_offset(original_file.syntax(), name_ref.syntax().text_range().start()); |
148 | let name_range = name_ref.syntax().text_range(); | 159 | let name_range = name_ref.syntax().text_range(); |