Fork of daniellemaywood.uk/gleam — Wasm codegen work
11 kB
286 lines
1use std::{
2 collections::{HashMap, HashSet},
3 sync::Arc,
4};
5
6use ecow::EcoString;
7use lsp_types::{
8 ActiveParameter, Documentation, MarkupContent, MarkupKind, ParameterInformation,
9 ParameterInformationLabel, SignatureHelp, SignatureInformation,
10};
11
12use gleam_core::{
13 ast::{CallArg, ImplicitCallArgOrigin, TypedExpr},
14 build::Module,
15 type_::{FieldMap, ModuleValueConstructor, Type, printer::Printer},
16};
17
18pub fn for_expression(expr: &TypedExpr, module: &Module) -> Option<SignatureHelp> {
19 // If we're inside a function call we can provide signature help,
20 // otherwise we don't want anything to pop up.
21 let TypedExpr::Call { fun, arguments, .. } = expr else {
22 return None;
23 };
24
25 match fun.as_ref() {
26 // If the thing being called is a local variable then we want to
27 // use it's name as the function name to be used in the signature
28 // help.
29 TypedExpr::Var {
30 constructor, name, ..
31 } => signature_help(
32 name.clone(),
33 fun,
34 arguments,
35 constructor.field_map(),
36 module,
37 ),
38
39 // If we're making a qualified call to another module's function
40 // then we want to show its type, documentation and the exact name
41 // being used (that is "<module_name>.<function_name>").
42 //
43 // eg. list.map(|)
44 // ^ When the cursor is here we are going to show
45 // "list.map(List(a), with: fn(a) -> b) -> List(b)"
46 // as the help signature.
47 //
48 TypedExpr::ModuleSelect {
49 module_alias,
50 label,
51 constructor,
52 ..
53 } => {
54 let field_map = match constructor {
55 ModuleValueConstructor::Constant { .. } => None,
56 ModuleValueConstructor::Record { field_map, .. }
57 | ModuleValueConstructor::Fn { field_map, .. } => field_map.into(),
58 };
59 let name = format!("{module_alias}.{label}").into();
60 signature_help(name, fun, arguments, field_map, module)
61 }
62
63 // If the function being called is an invalid node we don't want to
64 // provide any hint, otherwise one might be under the impression that
65 // that function actually exists somewhere.
66 //
67 TypedExpr::Invalid { .. } => None,
68
69 // In all other cases we can't figure out a good name to show in the
70 // signature help so we use an anonymous `fn` as the name to be
71 // shown.
72 //
73 // eg. fn(a){a}(|)
74 // ^ When the cursor is here we are going to show
75 // "fn(a: a) -> a" as the help signature.
76 //
77 TypedExpr::Int { .. }
78 | TypedExpr::Float { .. }
79 | TypedExpr::String { .. }
80 | TypedExpr::Block { .. }
81 | TypedExpr::Pipeline { .. }
82 | TypedExpr::Fn { .. }
83 | TypedExpr::List { .. }
84 | TypedExpr::Call { .. }
85 | TypedExpr::BinOp { .. }
86 | TypedExpr::Case { .. }
87 | TypedExpr::RecordAccess { .. }
88 | TypedExpr::PositionalAccess { .. }
89 | TypedExpr::Tuple { .. }
90 | TypedExpr::TupleIndex { .. }
91 | TypedExpr::Todo { .. }
92 | TypedExpr::Panic { .. }
93 | TypedExpr::Echo { .. }
94 | TypedExpr::BitArray { .. }
95 | TypedExpr::RecordUpdate { .. }
96 | TypedExpr::NegateBool { .. }
97 | TypedExpr::NegateInt { .. } => signature_help("fn".into(), fun, arguments, None, module),
98 }
99}
100
101/// Show the signature help of a function with the given name.
102/// Besides the function's typed expression `fun`, this function needs a bit of
103/// additional data to properly display a useful help signature:
104///
105/// - `fun_name` is used as the display name of the function in the help
106/// signature.
107/// - `supplied_arguments` are arguments being passed to the function call, those
108/// might not be of the correct arity or have wrong types but are used to
109/// deduce which argument should be highlighted next in the help signature.
110/// - `field_map` is the function's field map (if any) that will be used to
111/// display labels and understand which labelled argument should be
112/// highlighted next in the help signature.
113///
114fn signature_help(
115 fun_name: EcoString,
116 fun: &TypedExpr,
117 supplied_arguments: &[CallArg<TypedExpr>],
118 field_map: Option<&FieldMap>,
119 module: &Module,
120) -> Option<SignatureHelp> {
121 let (arguments, return_) = fun.type_().fn_types()?;
122
123 // If the function has no arguments, we don't want to show any help.
124 let arity = arguments.len() as u32;
125 if arity == 0 {
126 return None;
127 }
128
129 let index_to_label = match field_map {
130 Some(field_map) => field_map
131 .fields
132 .iter()
133 .map(|(name, index)| (*index, name))
134 .collect(),
135 None => HashMap::new(),
136 };
137
138 let printer = Printer::new(&module.ast.names);
139 let (label, parameters) =
140 print_signature_help(printer, fun_name, arguments, return_, &index_to_label);
141
142 let active_parameter = active_parameter_index(arity, supplied_arguments, index_to_label)
143 // If we don't want to highlight any arg in the suggestion we have to
144 // explicitly provide an out of bound index.
145 .or(Some(arity))
146 .map(ActiveParameter::Int);
147
148 Some(SignatureHelp {
149 signatures: vec![SignatureInformation {
150 label,
151 documentation: fun.get_documentation().map(|d| {
152 Documentation::MarkupContent(MarkupContent {
153 kind: MarkupKind::Markdown,
154 value: d.into(),
155 })
156 }),
157 parameters: Some(parameters),
158 active_parameter: None,
159 }],
160 active_signature: Some(0),
161 active_parameter,
162 })
163}
164
165fn active_parameter_index(
166 arity: u32,
167 supplied_arguments: &[CallArg<TypedExpr>],
168 mut index_to_label: HashMap<u32, &EcoString>,
169) -> Option<u32> {
170 let mut is_use_call = false;
171 let mut found_labelled_argument = false;
172 let mut used_labels = HashSet::new();
173
174 let mut supplied_unlabelled_arguments = 0;
175 let unlabelled_arguments = arity - index_to_label.len() as u32;
176
177 for (i, arg) in supplied_arguments.iter().enumerate() {
178 // If there's an unlabelled argument after a labelled one, we can't
179 // figure out what to suggest since arguments were passed in a wrong
180 // order.
181 if found_labelled_argument && arg.label.is_none() && !arg.is_implicit() {
182 return None;
183 }
184
185 // Once we reach to an implicit use argument (be it the callback or the
186 // missing implicitly inserted ones) we can break since those must be
187 // the last arguments of the function and are not explicitly supplied by
188 // the programmer.
189 if let Some(ImplicitCallArgOrigin::Use | ImplicitCallArgOrigin::IncorrectArityUse) =
190 arg.implicit
191 {
192 is_use_call = true;
193 break;
194 }
195
196 match &arg.label {
197 Some(label) => {
198 found_labelled_argument = true;
199 let _ = used_labels.insert(label);
200 }
201
202 // If the argument is unlabelled we just remove the label
203 // corresponding to it from the field map since it has already been
204 // passed as an unlabelled argument.
205 None => {
206 supplied_unlabelled_arguments += 1;
207 let _ = index_to_label.remove(&(i as u32));
208 }
209 }
210 }
211
212 let active_index = if supplied_unlabelled_arguments < unlabelled_arguments {
213 if found_labelled_argument {
214 // If I have supplied some labelled args but I haven't supplied all
215 // unlabelled args before a labelled one then we can't safely
216 // suggest anything as the next argument.
217 None
218 } else {
219 // If I haven't supplied enough unlabelled arguments then I have to
220 // set the next one as active (be it labelled or not).
221 Some(supplied_unlabelled_arguments)
222 }
223 } else {
224 // If I have supplied all the unlabelled arguments (and we could have
225 // also supplied some labelled ones as unlabelled!) then we pick the
226 // leftmost labelled argument that hasn't been supplied yet.
227 index_to_label
228 .into_iter()
229 .filter(|(_index, label)| !used_labels.contains(label))
230 .map(|(index, _label)| index)
231 .min()
232 .or(Some(supplied_arguments.len() as u32))
233 };
234
235 // If we're showing hints for a use call and we end up deciding that the
236 // only index we can suggest is the one of the use callback then we do not
237 // highlight it or it would lead people into believing they can manually
238 // pass that argument in.
239 if is_use_call && active_index == Some(arity - 1) {
240 None
241 } else {
242 active_index
243 }
244}
245
246/// To produce a signature that can be used by the LS, we need to also keep
247/// track of the arguments' positions in the printed signature. So this function
248/// prints the signature help producing at the same time a list of correct
249/// `ParameterInformation` for all its arguments.
250///
251fn print_signature_help(
252 mut printer: Printer<'_>,
253 function_name: EcoString,
254 arguments: Vec<Arc<Type>>,
255 return_: Arc<Type>,
256 index_to_label: &HashMap<u32, &EcoString>,
257) -> (String, Vec<ParameterInformation>) {
258 let arguments_count = arguments.len();
259 let mut signature = format!("{function_name}(");
260 let mut parameter_informations = Vec::with_capacity(arguments_count);
261
262 for (i, argument) in arguments.iter().enumerate() {
263 let arg_start = signature.len();
264 if let Some(label) = index_to_label.get(&(i as u32)) {
265 signature.push_str(label);
266 signature.push_str(": ");
267 }
268 signature.push_str(&printer.print_type(argument));
269 let arg_end = signature.len();
270 let label = ParameterInformationLabel::Tuple((arg_start as u32, arg_end as u32));
271
272 parameter_informations.push(ParameterInformation {
273 label,
274 documentation: None,
275 });
276
277 let is_last = i == arguments_count - 1;
278 if !is_last {
279 signature.push_str(", ");
280 }
281 }
282
283 signature.push_str(") -> ");
284 signature.push_str(&printer.print_type(&return_));
285 (signature, parameter_informations)
286}