Appearance
| 1 | namespace Syntax.Process is | |
| 2 | ||
| 3 | use Logging; | |
| 4 | use Source; | |
| 5 | ||
| 6 | use Semantic.Types.Type; | |
| 7 | ||
| 8 | use Ghul.Pipes; | |
| 9 | ||
| 10 | // One overload presented to the IDE's signature-help popup: a label | |
| 11 | // for the whole call and a description per parameter. Built either | |
| 12 | // from an overload-resolved FUNCTION_GROUP member or, when the callee | |
| 13 | // is a value of function type (a let-bound lambda, a function-typed | |
| 14 | // parameter, a directly-invoked anonymous function), from the | |
| 15 | // function type itself. | |
| 16 | class SIGNATURE is | |
| 17 | description: string public; | |
| 18 | parameter_descriptions: Collections.List[string] public; | |
| 19 | ||
| 20 | init(description: string, parameter_descriptions: Collections.List[string]) is | |
| 21 | self.description = description; | |
| 22 | self.parameter_descriptions = parameter_descriptions; | |
| 23 | si | |
| 24 | si | |
| 25 | ||
| 26 | class SIGNATURE_HELP_RESULT is | |
| 27 | signatures: Collections.List[SIGNATURE] public; | |
| 28 | best_signature_index: int public; | |
| 29 | current_parameter_index: int public; | |
| 30 | ||
| 31 | init( | |
| 32 | signatures: Collections.List[SIGNATURE], | |
| 33 | best_signature_index: int | |
| 34 | ) is | |
| 35 | self.signatures = signatures; | |
| 36 | self.best_signature_index = best_signature_index; | |
| 37 | self.current_parameter_index = 0; | |
| 38 | si | |
| 39 | si | |
| 40 | ||
| 41 | class SIGNATURE_HELP: ScopedVisitor is | |
| 42 | _overload_resolver: Semantic.OVERLOAD_RESOLVER; | |
| 43 | ||
| 44 | _target_line: int; | |
| 45 | _target_column: int; | |
| 46 | ||
| 47 | _results: SIGNATURE_HELP_RESULT?; | |
| 48 | ||
| 49 | init( | |
| 50 | logger: Logger, | |
| 51 | symbol_table: Semantic.SYMBOL_TABLE, | |
| 52 | namespaces: Semantic.NAMESPACES, | |
| 53 | overload_resolver: Semantic.OVERLOAD_RESOLVER | |
| 54 | ) | |
| 55 | is | |
| 56 | super.init(logger, symbol_table, namespaces); | |
| 57 | ||
| 58 | _overload_resolver = overload_resolver; | |
| 59 | si | |
| 60 | ||
| 61 | find_signatures(root: Trees.Node, target_line: int, target_column: int) -> SIGNATURE_HELP_RESULT? is | |
| 62 | _results = null; | |
| 63 | ||
| 64 | _target_line = target_line; | |
| 65 | _target_column = target_column; | |
| 66 | ||
| 67 | root.walk(self); | |
| 68 | ||
| 69 | return _results; | |
| 70 | si | |
| 71 | ||
| 72 | visit(call: Trees.Expressions.CALL) is | |
| 73 | if call.arguments == null then | |
| 74 | return; | |
| 75 | fi | |
| 76 | ||
| 77 | if !_results? then | |
| 78 | if !call.arguments.location.contains(_target_line, _target_column) /\ | |
| 79 | (!call.location.contains(_target_line, _target_column) \/ | |
| 80 | call.function.location.contains(_target_line, _target_column)) | |
| 81 | then | |
| 82 | return; | |
| 83 | fi | |
| 84 | ||
| 85 | _results = help_for(call); | |
| 86 | ||
| 87 | if _results? then | |
| 88 | // A `|>` call carries its subject as arguments[0], but the | |
| 89 | // subject sits to the left of the call's own location and | |
| 90 | // is never the argument being edited. Locate the cursor | |
| 91 | // among the written arguments alone, then shift past the | |
| 92 | // parameter the subject occupies. | |
| 93 | if call.is_thread_first /\ call.arguments.count > 0 then | |
| 94 | _results.current_parameter_index = | |
| 95 | get_current_parameter_index( | |
| 96 | call.location, | |
| 97 | Collections.LIST[Trees.Expressions.Expression](call.arguments |> skip(1)), | |
| 98 | call.arguments.has_trailing_comma | |
| 99 | ) + 1; | |
| 100 | else | |
| 101 | _results.current_parameter_index = | |
| 102 | get_current_parameter_index( | |
| 103 | call.location, | |
| 104 | Collections.LIST[Trees.Expressions.Expression](call.arguments), | |
| 105 | call.arguments.has_trailing_comma | |
| 106 | ); | |
| 107 | fi | |
| 108 | fi | |
| 109 | fi | |
| 110 | si | |
| 111 | ||
| 112 | visit(index: Trees.Expressions.INDEX) is | |
| 113 | if index.index == null then | |
| 114 | return; | |
| 115 | fi | |
| 116 | ||
| 117 | if !_results? then | |
| 118 | let brackets_location = | |
| 119 | LOCATION( | |
| 120 | index.location.file_name, | |
| 121 | index.left.location.end_line, | |
| 122 | index.left.location.end_column, | |
| 123 | index.location.end_line, | |
| 124 | index.location.end_column | |
| 125 | ); | |
| 126 | ||
| 127 | if !brackets_location.contains(_target_line, _target_column) \/ | |
| 128 | index.left.location.contains(_target_line, _target_column) | |
| 129 | then | |
| 130 | return; | |
| 131 | fi | |
| 132 | ||
| 133 | _results = help_for(index); | |
| 134 | fi | |
| 135 | si | |
| 136 | ||
| 137 | visit(`new: Trees.Expressions.NEW) is | |
| 138 | if `new.arguments == null then | |
| 139 | return; | |
| 140 | fi | |
| 141 | ||
| 142 | if !(_results?) then | |
| 143 | if | |
| 144 | !`new.location.contains(_target_line, _target_column) \/ | |
| 145 | LOCATION( | |
| 146 | `new.location.file_name, | |
| 147 | `new.location.start_line, | |
| 148 | `new.location.start_column, | |
| 149 | `new.type_expression!.location.end_line, | |
| 150 | `new.type_expression!.location.end_column | |
| 151 | ).contains(_target_line, _target_column) | |
| 152 | then | |
| 153 | return; | |
| 154 | fi | |
| 155 | ||
| 156 | _results = help_for(`new); | |
| 157 | ||
| 158 | if _results? then | |
| 159 | _results.current_parameter_index = | |
| 160 | get_current_parameter_index( | |
| 161 | `new.location, | |
| 162 | Collections.LIST[Trees.Expressions.Expression](`new.arguments), | |
| 163 | `new.arguments.has_trailing_comma | |
| 164 | ); | |
| 165 | fi | |
| 166 | fi | |
| 167 | si | |
| 168 | ||
| 169 | // Slot the cursor falls in, counting from the start of the call. | |
| 170 | // Each slot runs from the end of the argument before it to the end | |
| 171 | // of its own argument, so the comma introducing an argument belongs | |
| 172 | // to that argument. A trailing comma adds one more slot, empty of | |
| 173 | // any argument, running to the end of the call: that is where the | |
| 174 | // cursor sits with the closing bracket already in place. | |
| 175 | get_current_parameter_index( | |
| 176 | full_location: LOCATION, | |
| 177 | arguments: Collections.LIST[Trees.Expressions.Expression], | |
| 178 | has_trailing_comma: bool | |
| 179 | ) -> int is | |
| 180 | let slots = | |
| 181 | if has_trailing_comma then | |
| 182 | arguments.count + 1; | |
| 183 | else | |
| 184 | arguments.count; | |
| 185 | fi; | |
| 186 | ||
| 187 | if slots <= 1 then | |
| 188 | return 0; | |
| 189 | fi | |
| 190 | ||
| 191 | let last = slots - 1; | |
| 192 | ||
| 193 | for i in 0..slots do | |
| 194 | let location = | |
| 195 | LOCATION( | |
| 196 | full_location.file_name, | |
| 197 | if i == 0 then full_location.start_line else arguments[i-1].location.end_line fi, | |
| 198 | if i == 0 then full_location.start_column else arguments[i-1].location.end_column fi, | |
| 199 | if i == last then full_location.end_line else arguments[i].location.end_line fi, | |
| 200 | if i == last then full_location.end_column else arguments[i].location.end_column fi | |
| 201 | ); | |
| 202 | ||
| 203 | if location.contains(_target_line, _target_column) then | |
| 204 | return i; | |
| 205 | fi | |
| 206 | od | |
| 207 | ||
| 208 | return 0; | |
| 209 | si | |
| 210 | ||
| 211 | help_for(call: Trees.Expressions.CALL) -> SIGNATURE_HELP_RESULT? is | |
| 212 | if _results? then | |
| 213 | return null; | |
| 214 | fi | |
| 215 | ||
| 216 | let callee = call.function.value; | |
| 217 | ||
| 218 | if !callee? then | |
| 219 | return null; | |
| 220 | fi | |
| 221 | ||
| 222 | let function_group = function_group_of(callee); | |
| 223 | ||
| 224 | if function_group? then | |
| 225 | return help_for_function_group(function_group, argument_types_of(call.arguments)); | |
| 226 | fi | |
| 227 | ||
| 228 | if callee.type? /\ callee.type!.is_function then | |
| 229 | return help_for_function_type(callee.type!); | |
| 230 | fi | |
| 231 | ||
| 232 | return null; | |
| 233 | si | |
| 234 | ||
| 235 | // Signature help for `x[i]` / `x[i] = v`: resolve the indexer | |
| 236 | // as the read accessor (`get_Item`) on the left's type. The | |
| 237 | // assign-side `set_Item` shares its leading parameters, so the | |
| 238 | // read form's signature gives a useful surface for the index | |
| 239 | // arguments the user is typing. | |
| 240 | help_for(index: Trees.Expressions.INDEX) -> SIGNATURE_HELP_RESULT? is | |
| 241 | if _results? then | |
| 242 | return null; | |
| 243 | fi | |
| 244 | ||
| 245 | let left_value = index.left.value; | |
| 246 | ||
| 247 | if !left_value? \/ !left_value.type? then | |
| 248 | return null; | |
| 249 | fi | |
| 250 | ||
| 251 | let type = left_value.type; | |
| 252 | ||
| 253 | if type!.is_error then | |
| 254 | return null; | |
| 255 | fi | |
| 256 | ||
| 257 | let named_type = cast Semantic.Types.NAMED?(type); | |
| 258 | ||
| 259 | if !named_type? then | |
| 260 | return null; | |
| 261 | fi | |
| 262 | ||
| 263 | let symbol = named_type.find_member("get_Item"); | |
| 264 | ||
| 265 | if !symbol? \/ !symbol.is_function_group then | |
| 266 | return null; | |
| 267 | fi | |
| 268 | ||
| 269 | let argument_types = Collections.LIST[Type](); | |
| 270 | ||
| 271 | if let value = index.index?.value /\ value.type? then | |
| 272 | argument_types.add(value.type!); | |
| 273 | else | |
| 274 | argument_types.add(Semantic.Types.ERROR()); | |
| 275 | fi | |
| 276 | ||
| 277 | return help_for_function_group( | |
| 278 | cast Semantic.Symbols.FUNCTION_GROUP?(symbol)!, | |
| 279 | argument_types | |
| 280 | ); | |
| 281 | si | |
| 282 | ||
| 283 | help_for(`new: Trees.Expressions.NEW) -> SIGNATURE_HELP_RESULT? is | |
| 284 | if _results? then | |
| 285 | return null; | |
| 286 | fi | |
| 287 | ||
| 288 | if !`new.type_expression? then | |
| 289 | return null; | |
| 290 | fi | |
| 291 | ||
| 292 | let type = `new.type_expression.type; | |
| 293 | ||
| 294 | if type == null then | |
| 295 | return null; | |
| 296 | fi | |
| 297 | ||
| 298 | let symbol = type.find_member("init"); | |
| 299 | ||
| 300 | if !symbol? \/ !symbol.is_function_group then | |
| 301 | return null; | |
| 302 | fi | |
| 303 | ||
| 304 | return help_for_function_group( | |
| 305 | cast Semantic.Symbols.FUNCTION_GROUP?(symbol)!, | |
| 306 | argument_types_of(`new.arguments) | |
| 307 | ); | |
| 308 | si | |
| 309 | ||
| 310 | // The callee's IR value resolves to a named function only when | |
| 311 | // it is a direct load of a FUNCTION_GROUP symbol. A load of a | |
| 312 | // variable / parameter / field of function type does not — its | |
| 313 | // signature comes from the function type instead. | |
| 314 | function_group_of(callee: IR.Values.Value) -> Semantic.Symbols.FUNCTION_GROUP? is | |
| 315 | let load = cast IR.Values.Load.SYMBOL?(callee); | |
| 316 | ||
| 317 | if !load? then | |
| 318 | return null; | |
| 319 | fi | |
| 320 | ||
| 321 | return cast Semantic.Symbols.FUNCTION_GROUP?(load.symbol); | |
| 322 | si | |
| 323 | ||
| 324 | argument_types_of(arguments: Trees.Expressions.LIST) -> Collections.LIST[Type] is | |
| 325 | let argument_types = Collections.LIST[Type](); | |
| 326 | ||
| 327 | for a in arguments do | |
| 328 | if let value = a?.value /\ value.type? then | |
| 329 | argument_types.add(value.type!); | |
| 330 | else | |
| 331 | argument_types.add(Semantic.Types.ERROR()); | |
| 332 | fi | |
| 333 | od | |
| 334 | ||
| 335 | return argument_types; | |
| 336 | si | |
| 337 | ||
| 338 | help_for_function_group( | |
| 339 | function_group: Semantic.Symbols.FUNCTION_GROUP, | |
| 340 | argument_types: Collections.LIST[Type] | |
| 341 | ) -> SIGNATURE_HELP_RESULT? is | |
| 342 | let overload_results = _overload_resolver.find_matches(function_group, argument_types); | |
| 343 | ||
| 344 | if !overload_results? \/ overload_results.results.count == 0 then | |
| 345 | return null; | |
| 346 | fi | |
| 347 | ||
| 348 | let signatures = Collections.LIST[SIGNATURE](); | |
| 349 | ||
| 350 | for f in overload_results.results do | |
| 351 | signatures.add(signature_for_function(f)); | |
| 352 | od | |
| 353 | ||
| 354 | return SIGNATURE_HELP_RESULT(signatures, overload_results.best_result_index); | |
| 355 | si | |
| 356 | ||
| 357 | signature_for_function(function: Semantic.Symbols.Function) -> SIGNATURE is | |
| 358 | let parameter_descriptions = Collections.LIST[string](); | |
| 359 | ||
| 360 | for i in 0..function.arguments.count do | |
| 361 | parameter_descriptions.add(function.get_short_argument_description(i)); | |
| 362 | od | |
| 363 | ||
| 364 | return SIGNATURE(function.short_description, parameter_descriptions); | |
| 365 | si | |
| 366 | ||
| 367 | // Signature for a callee whose type is a function type. Function | |
| 368 | // types carry no parameter names, so the label and parameter | |
| 369 | // descriptions are the formal types alone: `(int, string) -> bool`. | |
| 370 | help_for_function_type(function_type: Type) -> SIGNATURE_HELP_RESULT? is | |
| 371 | let arguments = function_type.arguments; | |
| 372 | ||
| 373 | let formal_count = | |
| 374 | if function_type.is_action then | |
| 375 | arguments.count; | |
| 376 | else | |
| 377 | arguments.count - 1; | |
| 378 | fi; | |
| 379 | ||
| 380 | if formal_count < 0 then | |
| 381 | return null; | |
| 382 | fi | |
| 383 | ||
| 384 | let parameter_descriptions = Collections.LIST[string](); | |
| 385 | ||
| 386 | for i in 0..formal_count do | |
| 387 | parameter_descriptions.add(arguments[i].short_description); | |
| 388 | od | |
| 389 | ||
| 390 | let label = System.Text.StringBuilder(); | |
| 391 | ||
| 392 | label.append('('); | |
| 393 | parameter_descriptions |> append_to(label, ", "); | |
| 394 | label.append(") -> "); | |
| 395 | ||
| 396 | if function_type.is_action then | |
| 397 | label.append("void"); | |
| 398 | else | |
| 399 | label.append(arguments[arguments.count - 1].short_description); | |
| 400 | fi | |
| 401 | ||
| 402 | return SIGNATURE_HELP_RESULT( | |
| 403 | Collections.LIST[SIGNATURE]([SIGNATURE(label.to_string(), parameter_descriptions)]), | |
| 404 | 0 | |
| 405 | ); | |
| 406 | si | |
| 407 | si | |
| 408 | si |