ide_completion/completions/
fn_param.rs1use hir::HirDisplay;
4use ide_db::FxHashMap;
5use itertools::Either;
6use syntax::{
7 AstNode, Direction, SyntaxKind, TextRange, TextSize, algo,
8 ast::{self, HasModuleItem},
9 match_ast,
10};
11
12use crate::{
13 CompletionContext, CompletionItem, CompletionItemKind, Completions,
14 context::{ParamContext, ParamKind, PatternContext},
15};
16
17pub(crate) fn complete_fn_param(
24 acc: &mut Completions,
25 ctx: &CompletionContext<'_>,
26 pattern_ctx: &PatternContext,
27) -> Option<()> {
28 let (ParamContext { param_list, kind, .. }, impl_or_trait) = match pattern_ctx {
29 PatternContext { param_ctx: Some(kind), impl_or_trait, .. } => (kind, impl_or_trait),
30 _ => return None,
31 };
32
33 let comma_wrapper = comma_wrapper(ctx);
34 let mut add_new_item_to_acc = |label: &str| {
35 let mk_item = |label: &str, range: TextRange| {
36 CompletionItem::new(CompletionItemKind::Binding, range, label, ctx.edition)
37 };
38 let item = match &comma_wrapper {
39 Some((fmt, range)) => mk_item(&fmt(label), *range),
40 None => mk_item(label, ctx.source_range()),
41 };
42 item.add_to(acc, ctx.db)
45 };
46
47 match kind {
48 ParamKind::Function(function) => {
49 fill_fn_params(ctx, function, param_list, impl_or_trait, add_new_item_to_acc);
50 }
51 ParamKind::Closure(closure) => {
52 let stmt_list = closure.syntax().ancestors().find_map(ast::StmtList::cast)?;
53 params_from_stmt_list_scope(ctx, stmt_list, |name, ty| {
54 add_new_item_to_acc(&format!("{}: {ty}", name.display(ctx.db, ctx.edition)));
55 });
56 }
57 }
58
59 Some(())
60}
61
62fn fill_fn_params(
63 ctx: &CompletionContext<'_>,
64 function: &ast::Fn,
65 param_list: &ast::ParamList,
66 impl_or_trait: &Option<Either<ast::Impl, ast::Trait>>,
67 mut add_new_item_to_acc: impl FnMut(&str),
68) {
69 let mut file_params = FxHashMap::default();
70
71 let mut extract_params = |f: ast::Fn| {
72 f.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| {
73 if let Some(pat) = param.pat() {
74 let whole_param = param.syntax().text().to_string();
76 let binding = pat.syntax().text().to_string();
77 file_params.entry(whole_param).or_insert(binding);
78 }
79 });
80 };
81
82 for node in ctx.token.parent_ancestors() {
83 match_ast! {
84 match node {
85 ast::SourceFile(it) => it.items().filter_map(|item| match item {
86 ast::Item::Fn(it) => Some(it),
87 _ => None,
88 }).for_each(&mut extract_params),
89 ast::ItemList(it) => it.items().filter_map(|item| match item {
90 ast::Item::Fn(it) => Some(it),
91 _ => None,
92 }).for_each(&mut extract_params),
93 ast::AssocItemList(it) => it.assoc_items().filter_map(|item| match item {
94 ast::AssocItem::Fn(it) => Some(it),
95 _ => None,
96 }).for_each(&mut extract_params),
97 _ => continue,
98 }
99 };
100 }
101
102 if let Some(stmt_list) = function.syntax().parent().and_then(ast::StmtList::cast) {
103 params_from_stmt_list_scope(ctx, stmt_list, |name, ty| {
104 file_params
105 .entry(format!("{}: {ty}", name.display(ctx.db, ctx.edition)))
106 .or_insert(name.display(ctx.db, ctx.edition).to_string());
107 });
108 }
109 remove_duplicated(&mut file_params, param_list.params());
110 let self_completion_items = ["self", "&self", "mut self", "&mut self"];
111 if should_add_self_completions(ctx.token.text_range().start(), param_list, impl_or_trait) {
112 self_completion_items.into_iter().for_each(&mut add_new_item_to_acc);
113 }
114
115 file_params.keys().for_each(|whole_param| add_new_item_to_acc(whole_param));
116}
117
118fn params_from_stmt_list_scope(
119 ctx: &CompletionContext<'_>,
120 stmt_list: ast::StmtList,
121 mut cb: impl FnMut(hir::Name, String),
122) {
123 let syntax_node = match stmt_list.syntax().last_child() {
124 Some(it) => it,
125 None => return,
126 };
127 if let Some(scope) =
128 ctx.sema.scope_at_offset(stmt_list.syntax(), syntax_node.text_range().end())
129 {
130 let module = scope.module().into();
131 scope.process_all_names(&mut |name, def| {
132 if let hir::ScopeDef::Local(local) = def
133 && let Ok(ty) = local.ty(ctx.db).display_source_code(ctx.db, module, true)
134 {
135 cb(name, ty);
136 }
137 });
138 }
139}
140
141fn remove_duplicated(
142 file_params: &mut FxHashMap<String, String>,
143 fn_params: ast::AstChildren<ast::Param>,
144) {
145 fn_params.for_each(|param| {
146 let whole_param = param.syntax().text().to_string();
147 file_params.remove(&whole_param);
148
149 match param.pat() {
150 Some(pattern) if param.ty().is_some() => {
154 let binding = pattern.syntax().text().to_string();
155 file_params.retain(|_, v| v != &binding);
156 }
157 _ => (),
158 }
159 })
160}
161
162fn should_add_self_completions(
163 cursor: TextSize,
164 param_list: &ast::ParamList,
165 impl_or_trait: &Option<Either<ast::Impl, ast::Trait>>,
166) -> bool {
167 if impl_or_trait.is_none() || param_list.self_param().is_some() {
168 return false;
169 }
170 match param_list.params().next() {
171 Some(first) => first.pat().is_some_and(|pat| pat.syntax().text_range().contains(cursor)),
172 None => true,
173 }
174}
175
176fn comma_wrapper(ctx: &CompletionContext<'_>) -> Option<(impl Fn(&str) -> String, TextRange)> {
177 let param =
178 ctx.original_token.parent_ancestors().find(|node| node.kind() == SyntaxKind::PARAM)?;
179
180 let next_token_kind = {
181 let t = param.last_token()?.next_token()?;
182 let t = algo::skip_whitespace_token(t, Direction::Next)?;
183 t.kind()
184 };
185 let prev_token_kind = {
186 let t = param.first_token()?.prev_token()?;
187 let t = algo::skip_whitespace_token(t, Direction::Prev)?;
188 t.kind()
189 };
190
191 let has_trailing_comma =
192 matches!(next_token_kind, SyntaxKind::COMMA | SyntaxKind::R_PAREN | SyntaxKind::PIPE);
193 let trailing = if has_trailing_comma { "" } else { "," };
194
195 let has_leading_comma =
196 matches!(prev_token_kind, SyntaxKind::COMMA | SyntaxKind::L_PAREN | SyntaxKind::PIPE);
197 let leading = if has_leading_comma { "" } else { ", " };
198
199 Some((move |label: &_| format!("{leading}{label}{trailing}"), param.text_range()))
200}