Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / language-server / src / signature_help.rs
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}