1 use serde::{Deserialize, Serialize}; 2 3 use crate::{ 4 Command, Documentation, MarkupKind, PartialResultParams, TagSupport, 5 TextDocumentPositionParams, TextDocumentRegistrationOptions, TextEdit, WorkDoneProgressOptions, 6 WorkDoneProgressParams, 7 }; 8 9 use crate::Range; 10 use serde_json::Value; 11 use std::fmt::Debug; 12 13 /// Defines how to interpret the insert text in a completion item 14 #[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)] 15 #[serde(transparent)] 16 pub struct InsertTextFormat(i32); 17 lsp_enum! { 18 impl InsertTextFormat { 19 pub const PLAIN_TEXT: InsertTextFormat = InsertTextFormat(1); 20 pub const SNIPPET: InsertTextFormat = InsertTextFormat(2); 21 } 22 } 23 24 /// The kind of a completion entry. 25 #[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)] 26 #[serde(transparent)] 27 pub struct CompletionItemKind(i32); 28 lsp_enum! { 29 impl CompletionItemKind { 30 pub const TEXT: CompletionItemKind = CompletionItemKind(1); 31 pub const METHOD: CompletionItemKind = CompletionItemKind(2); 32 pub const FUNCTION: CompletionItemKind = CompletionItemKind(3); 33 pub const CONSTRUCTOR: CompletionItemKind = CompletionItemKind(4); 34 pub const FIELD: CompletionItemKind = CompletionItemKind(5); 35 pub const VARIABLE: CompletionItemKind = CompletionItemKind(6); 36 pub const CLASS: CompletionItemKind = CompletionItemKind(7); 37 pub const INTERFACE: CompletionItemKind = CompletionItemKind(8); 38 pub const MODULE: CompletionItemKind = CompletionItemKind(9); 39 pub const PROPERTY: CompletionItemKind = CompletionItemKind(10); 40 pub const UNIT: CompletionItemKind = CompletionItemKind(11); 41 pub const VALUE: CompletionItemKind = CompletionItemKind(12); 42 pub const ENUM: CompletionItemKind = CompletionItemKind(13); 43 pub const KEYWORD: CompletionItemKind = CompletionItemKind(14); 44 pub const SNIPPET: CompletionItemKind = CompletionItemKind(15); 45 pub const COLOR: CompletionItemKind = CompletionItemKind(16); 46 pub const FILE: CompletionItemKind = CompletionItemKind(17); 47 pub const REFERENCE: CompletionItemKind = CompletionItemKind(18); 48 pub const FOLDER: CompletionItemKind = CompletionItemKind(19); 49 pub const ENUM_MEMBER: CompletionItemKind = CompletionItemKind(20); 50 pub const CONSTANT: CompletionItemKind = CompletionItemKind(21); 51 pub const STRUCT: CompletionItemKind = CompletionItemKind(22); 52 pub const EVENT: CompletionItemKind = CompletionItemKind(23); 53 pub const OPERATOR: CompletionItemKind = CompletionItemKind(24); 54 pub const TYPE_PARAMETER: CompletionItemKind = CompletionItemKind(25); 55 } 56 } 57 58 #[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] 59 #[serde(rename_all = "camelCase")] 60 pub struct CompletionItemCapability { 61 /// Client supports snippets as insert text. 62 /// 63 /// A snippet can define tab stops and placeholders with `$1`, `$2` 64 /// and `${3:foo}`. `$0` defines the final tab stop, it defaults to 65 /// the end of the snippet. Placeholders with equal identifiers are linked, 66 /// that is typing in one will update others too. 67 #[serde(skip_serializing_if = "Option::is_none")] 68 pub snippet_support: Option<bool>, 69 70 /// Client supports commit characters on a completion item. 71 #[serde(skip_serializing_if = "Option::is_none")] 72 pub commit_characters_support: Option<bool>, 73 74 /// Client supports the follow content formats for the documentation 75 /// property. The order describes the preferred format of the client. 76 #[serde(skip_serializing_if = "Option::is_none")] 77 pub documentation_format: Option<Vec<MarkupKind>>, 78 79 /// Client supports the deprecated property on a completion item. 80 #[serde(skip_serializing_if = "Option::is_none")] 81 pub deprecated_support: Option<bool>, 82 83 /// Client supports the preselect property on a completion item. 84 #[serde(skip_serializing_if = "Option::is_none")] 85 pub preselect_support: Option<bool>, 86 87 /// Client supports the tag property on a completion item. Clients supporting 88 /// tags have to handle unknown tags gracefully. Clients especially need to 89 /// preserve unknown tags when sending a completion item back to the server in 90 /// a resolve call. 91 #[serde( 92 default, 93 skip_serializing_if = "Option::is_none", 94 deserialize_with = "TagSupport::deserialize_compat" 95 )] 96 pub tag_support: Option<TagSupport<CompletionItemTag>>, 97 98 /// Client support insert replace edit to control different behavior if a 99 /// completion item is inserted in the text or should replace text. 100 /// 101 /// @since 3.16.0 102 #[serde(skip_serializing_if = "Option::is_none")] 103 pub insert_replace_support: Option<bool>, 104 105 /// Indicates which properties a client can resolve lazily on a completion 106 /// item. Before version 3.16.0 only the predefined properties `documentation` 107 /// and `details` could be resolved lazily. 108 /// 109 /// @since 3.16.0 110 #[serde(skip_serializing_if = "Option::is_none")] 111 pub resolve_support: Option<CompletionItemCapabilityResolveSupport>, 112 113 /// The client supports the `insertTextMode` property on 114 /// a completion item to override the whitespace handling mode 115 /// as defined by the client. 116 /// 117 /// @since 3.16.0 118 #[serde(skip_serializing_if = "Option::is_none")] 119 pub insert_text_mode_support: Option<InsertTextModeSupport>, 120 121 /// The client has support for completion item label 122 /// details (see also `CompletionItemLabelDetails`). 123 /// 124 /// @since 3.17.0 - proposed state 125 /// 126 #[cfg(feature = "proposed")] 127 #[serde(skip_serializing_if = "Option::is_none")] 128 pub label_details_support: Option<bool>, 129 } 130 131 #[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] 132 #[serde(rename_all = "camelCase")] 133 pub struct CompletionItemCapabilityResolveSupport { 134 /// The properties that a client can resolve lazily. 135 pub properties: Vec<String>, 136 } 137 138 #[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] 139 #[serde(rename_all = "camelCase")] 140 pub struct InsertTextModeSupport { 141 pub value_set: Vec<InsertTextMode>, 142 } 143 144 /// How whitespace and indentation is handled during completion 145 /// item insertion. 146 /// 147 /// @since 3.16.0 148 #[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)] 149 #[serde(transparent)] 150 pub struct InsertTextMode(i32); 151 lsp_enum! { 152 impl InsertTextMode { 153 /// The insertion or replace strings is taken as it is. If the 154 /// value is multi line the lines below the cursor will be 155 /// inserted using the indentation defined in the string value. 156 /// The client will not apply any kind of adjustments to the 157 /// string. 158 pub const AS_IS: InsertTextMode = InsertTextMode(1); 159 160 /// The editor adjusts leading whitespace of new lines so that 161 /// they match the indentation up to the cursor of the line for 162 /// which the item is accepted. 163 /// 164 /// Consider a line like this: <2tabs><cursor><3tabs>foo. Accepting a 165 /// multi line completion item is indented using 2 tabs all 166 /// following lines inserted will be indented using 2 tabs as well. 167 pub const ADJUST_INDENTATION: InsertTextMode = InsertTextMode(2); 168 } 169 } 170 171 #[derive(Eq, PartialEq, Clone, Deserialize, Serialize)] 172 #[serde(transparent)] 173 pub struct CompletionItemTag(i32); 174 lsp_enum! { 175 impl CompletionItemTag { 176 pub const DEPRECATED: CompletionItemTag = CompletionItemTag(1); 177 } 178 } 179 180 #[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] 181 #[serde(rename_all = "camelCase")] 182 pub struct CompletionItemKindCapability { 183 /// The completion item kind values the client supports. When this 184 /// property exists the client also guarantees that it will 185 /// handle values outside its set gracefully and falls back 186 /// to a default value when unknown. 187 /// 188 /// If this property is not present the client only supports 189 /// the completion items kinds from `Text` to `Reference` as defined in 190 /// the initial version of the protocol. 191 #[serde(skip_serializing_if = "Option::is_none")] 192 pub value_set: Option<Vec<CompletionItemKind>>, 193 } 194 195 #[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] 196 #[serde(rename_all = "camelCase")] 197 pub struct CompletionClientCapabilities { 198 /// Whether completion supports dynamic registration. 199 #[serde(skip_serializing_if = "Option::is_none")] 200 pub dynamic_registration: Option<bool>, 201 202 /// The client supports the following `CompletionItem` specific 203 /// capabilities. 204 #[serde(skip_serializing_if = "Option::is_none")] 205 pub completion_item: Option<CompletionItemCapability>, 206 207 #[serde(skip_serializing_if = "Option::is_none")] 208 pub completion_item_kind: Option<CompletionItemKindCapability>, 209 210 /// The client supports to send additional context information for a 211 /// `textDocument/completion` requestion. 212 #[serde(skip_serializing_if = "Option::is_none")] 213 pub context_support: Option<bool>, 214 215 /// The client's default when the completion item doesn't provide a 216 /// `insertTextMode` property. 217 /// 218 /// @since 3.17.0 219 #[cfg(feature = "proposed")] 220 #[serde(skip_serializing_if = "Option::is_none")] 221 pub insert_text_mode: Option<InsertTextMode>, 222 } 223 224 /// A special text edit to provide an insert and a replace operation. 225 /// 226 /// @since 3.16.0 227 #[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] 228 #[serde(rename_all = "camelCase")] 229 pub struct InsertReplaceEdit { 230 /// The string to be inserted. 231 pub new_text: String, 232 233 /// The range if the insert is requested 234 pub insert: Range, 235 236 /// The range if the replace is requested. 237 pub replace: Range, 238 } 239 240 #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] 241 #[serde(untagged)] 242 pub enum CompletionTextEdit { 243 Edit(TextEdit), 244 InsertAndReplace(InsertReplaceEdit), 245 } 246 247 impl From<TextEdit> for CompletionTextEdit { from(edit: TextEdit) -> Self248 fn from(edit: TextEdit) -> Self { 249 CompletionTextEdit::Edit(edit) 250 } 251 } 252 253 impl From<InsertReplaceEdit> for CompletionTextEdit { from(edit: InsertReplaceEdit) -> Self254 fn from(edit: InsertReplaceEdit) -> Self { 255 CompletionTextEdit::InsertAndReplace(edit) 256 } 257 } 258 259 /// Completion options. 260 #[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] 261 #[serde(rename_all = "camelCase")] 262 pub struct CompletionOptions { 263 /// The server provides support to resolve additional information for a completion item. 264 #[serde(skip_serializing_if = "Option::is_none")] 265 pub resolve_provider: Option<bool>, 266 267 /// Most tools trigger completion request automatically without explicitly 268 /// requesting it using a keyboard shortcut (e.g. Ctrl+Space). Typically they 269 /// do so when the user starts to type an identifier. For example if the user 270 /// types `c` in a JavaScript file code complete will automatically pop up 271 /// present `console` besides others as a completion item. Characters that 272 /// make up identifiers don't need to be listed here. 273 /// 274 /// If code complete should automatically be trigger on characters not being 275 /// valid inside an identifier (for example `.` in JavaScript) list them in 276 /// `triggerCharacters`. 277 #[serde(skip_serializing_if = "Option::is_none")] 278 pub trigger_characters: Option<Vec<String>>, 279 280 /// The list of all possible characters that commit a completion. This field 281 /// can be used if clients don't support individual commit characters per 282 /// completion item. See client capability 283 /// `completion.completionItem.commitCharactersSupport`. 284 /// 285 /// If a server provides both `allCommitCharacters` and commit characters on 286 /// an individual completion item the ones on the completion item win. 287 /// 288 /// @since 3.2.0 289 #[serde(skip_serializing_if = "Option::is_none")] 290 pub all_commit_characters: Option<Vec<String>>, 291 292 #[serde(flatten)] 293 pub work_done_progress_options: WorkDoneProgressOptions, 294 295 /// The server supports the following `CompletionItem` specific 296 /// capabilities. 297 /// 298 /// @since 3.17.0 - proposed state 299 #[cfg(feature = "proposed")] 300 #[serde(skip_serializing_if = "Option::is_none")] 301 pub completion_item: Option<CompletionOptionsCompletionItem>, 302 } 303 304 #[cfg(feature = "proposed")] 305 #[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] 306 #[serde(rename_all = "camelCase")] 307 pub struct CompletionOptionsCompletionItem { 308 /// The server has support for completion item label 309 /// details (see also `CompletionItemLabelDetails`) when receiving 310 /// a completion item in a resolve call. 311 /// 312 /// @since 3.17.0 - proposed state 313 #[serde(skip_serializing_if = "Option::is_none")] 314 pub label_details_support: Option<bool>, 315 } 316 317 #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 318 pub struct CompletionRegistrationOptions { 319 #[serde(flatten)] 320 pub text_document_registration_options: TextDocumentRegistrationOptions, 321 322 #[serde(flatten)] 323 pub completion_options: CompletionOptions, 324 } 325 326 #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 327 #[serde(untagged)] 328 pub enum CompletionResponse { 329 Array(Vec<CompletionItem>), 330 List(CompletionList), 331 } 332 333 impl From<Vec<CompletionItem>> for CompletionResponse { from(items: Vec<CompletionItem>) -> Self334 fn from(items: Vec<CompletionItem>) -> Self { 335 CompletionResponse::Array(items) 336 } 337 } 338 339 impl From<CompletionList> for CompletionResponse { from(list: CompletionList) -> Self340 fn from(list: CompletionList) -> Self { 341 CompletionResponse::List(list) 342 } 343 } 344 345 #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] 346 #[serde(rename_all = "camelCase")] 347 pub struct CompletionParams { 348 // This field was "mixed-in" from TextDocumentPositionParams 349 #[serde(flatten)] 350 pub text_document_position: TextDocumentPositionParams, 351 352 #[serde(flatten)] 353 pub work_done_progress_params: WorkDoneProgressParams, 354 355 #[serde(flatten)] 356 pub partial_result_params: PartialResultParams, 357 358 // CompletionParams properties: 359 #[serde(skip_serializing_if = "Option::is_none")] 360 pub context: Option<CompletionContext>, 361 } 362 363 #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] 364 #[serde(rename_all = "camelCase")] 365 pub struct CompletionContext { 366 /// How the completion was triggered. 367 pub trigger_kind: CompletionTriggerKind, 368 369 /// The trigger character (a single character) that has trigger code complete. 370 /// Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter` 371 #[serde(skip_serializing_if = "Option::is_none")] 372 pub trigger_character: Option<String>, 373 } 374 375 /// How a completion was triggered. 376 #[derive(Eq, PartialEq, Clone, Copy, Deserialize, Serialize)] 377 #[serde(transparent)] 378 pub struct CompletionTriggerKind(i32); 379 lsp_enum! { 380 impl CompletionTriggerKind { 381 pub const INVOKED: CompletionTriggerKind = CompletionTriggerKind(1); 382 pub const TRIGGER_CHARACTER: CompletionTriggerKind = CompletionTriggerKind(2); 383 pub const TRIGGER_FOR_INCOMPLETE_COMPLETIONS: CompletionTriggerKind = CompletionTriggerKind(3); 384 } 385 } 386 387 /// Represents a collection of [completion items](#CompletionItem) to be presented 388 /// in the editor. 389 #[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)] 390 #[serde(rename_all = "camelCase")] 391 pub struct CompletionList { 392 /// This list it not complete. Further typing should result in recomputing 393 /// this list. 394 pub is_incomplete: bool, 395 396 /// The completion items. 397 pub items: Vec<CompletionItem>, 398 } 399 400 #[derive(Debug, PartialEq, Default, Deserialize, Serialize, Clone)] 401 #[serde(rename_all = "camelCase")] 402 pub struct CompletionItem { 403 /// The label of this completion item. By default 404 /// also the text that is inserted when selecting 405 /// this completion. 406 pub label: String, 407 408 /// Additional details for the label 409 /// 410 /// @since 3.17.0 - proposed state 411 /// 412 #[cfg(feature = "proposed")] 413 #[serde(skip_serializing_if = "Option::is_none")] 414 pub label_details: Option<CompletionItemLabelDetails>, 415 416 /// The kind of this completion item. Based of the kind 417 /// an icon is chosen by the editor. 418 #[serde(skip_serializing_if = "Option::is_none")] 419 pub kind: Option<CompletionItemKind>, 420 421 /// A human-readable string with additional information 422 /// about this item, like type or symbol information. 423 #[serde(skip_serializing_if = "Option::is_none")] 424 pub detail: Option<String>, 425 426 /// A human-readable string that represents a doc-comment. 427 #[serde(skip_serializing_if = "Option::is_none")] 428 pub documentation: Option<Documentation>, 429 430 /// Indicates if this item is deprecated. 431 #[serde(skip_serializing_if = "Option::is_none")] 432 pub deprecated: Option<bool>, 433 434 /// Select this item when showing. 435 #[serde(skip_serializing_if = "Option::is_none")] 436 pub preselect: Option<bool>, 437 438 /// A string that should be used when comparing this item 439 /// with other items. When `falsy` the label is used 440 /// as the sort text for this item. 441 #[serde(skip_serializing_if = "Option::is_none")] 442 pub sort_text: Option<String>, 443 444 /// A string that should be used when filtering a set of 445 /// completion items. When `falsy` the label is used as the 446 /// filter text for this item. 447 #[serde(skip_serializing_if = "Option::is_none")] 448 pub filter_text: Option<String>, 449 450 /// A string that should be inserted into a document when selecting 451 /// this completion. When `falsy` the label is used as the insert text 452 /// for this item. 453 /// 454 /// The `insertText` is subject to interpretation by the client side. 455 /// Some tools might not take the string literally. For example 456 /// VS Code when code complete is requested in this example 457 /// `con<cursor position>` and a completion item with an `insertText` of 458 /// `console` is provided it will only insert `sole`. Therefore it is 459 /// recommended to use `textEdit` instead since it avoids additional client 460 /// side interpretation. 461 #[serde(skip_serializing_if = "Option::is_none")] 462 pub insert_text: Option<String>, 463 464 /// The format of the insert text. The format applies to both the `insertText` property 465 /// and the `newText` property of a provided `textEdit`. If omitted defaults to `InsertTextFormat.PlainText`. 466 /// 467 /// @since 3.16.0 468 #[serde(skip_serializing_if = "Option::is_none")] 469 pub insert_text_format: Option<InsertTextFormat>, 470 471 /// How whitespace and indentation is handled during completion 472 /// item insertion. If not provided the client's default value is used. 473 /// 474 /// @since 3.16.0 475 #[serde(skip_serializing_if = "Option::is_none")] 476 pub insert_text_mode: Option<InsertTextMode>, 477 478 /// An edit which is applied to a document when selecting 479 /// this completion. When an edit is provided the value of 480 /// insertText is ignored. 481 /// 482 /// Most editors support two different operation when accepting a completion item. One is to insert a 483 484 /// completion text and the other is to replace an existing text with a completion text. Since this can 485 /// usually not predetermined by a server it can report both ranges. Clients need to signal support for 486 /// `InsertReplaceEdits` via the `textDocument.completion.insertReplaceSupport` client capability 487 /// property. 488 /// 489 /// *Note 1:* The text edit's range as well as both ranges from a insert replace edit must be a 490 /// [single line] and they must contain the position at which completion has been requested. 491 /// *Note 2:* If an `InsertReplaceEdit` is returned the edit's insert range must be a prefix of 492 /// the edit's replace range, that means it must be contained and starting at the same position. 493 /// 494 /// @since 3.16.0 additional type `InsertReplaceEdit` 495 #[serde(skip_serializing_if = "Option::is_none")] 496 pub text_edit: Option<CompletionTextEdit>, 497 498 /// An optional array of additional text edits that are applied when 499 /// selecting this completion. Edits must not overlap with the main edit 500 /// nor with themselves. 501 #[serde(skip_serializing_if = "Option::is_none")] 502 pub additional_text_edits: Option<Vec<TextEdit>>, 503 504 /// An optional command that is executed *after* inserting this completion. *Note* that 505 /// additional modifications to the current document should be described with the 506 /// additionalTextEdits-property. 507 #[serde(skip_serializing_if = "Option::is_none")] 508 pub command: Option<Command>, 509 510 /// An optional set of characters that when pressed while this completion is 511 /// active will accept it first and then type that character. *Note* that all 512 /// commit characters should have `length=1` and that superfluous characters 513 /// will be ignored. 514 #[serde(skip_serializing_if = "Option::is_none")] 515 pub commit_characters: Option<Vec<String>>, 516 517 /// An data entry field that is preserved on a completion item between 518 /// a completion and a completion resolve request. 519 #[serde(skip_serializing_if = "Option::is_none")] 520 pub data: Option<Value>, 521 522 /// Tags for this completion item. 523 #[serde(skip_serializing_if = "Option::is_none")] 524 pub tags: Option<Vec<CompletionItemTag>>, 525 } 526 527 impl CompletionItem { 528 /// Create a CompletionItem with the minimum possible info (label and detail). new_simple(label: String, detail: String) -> CompletionItem529 pub fn new_simple(label: String, detail: String) -> CompletionItem { 530 CompletionItem { 531 label, 532 detail: Some(detail), 533 ..Self::default() 534 } 535 } 536 } 537 538 /// Additional details for a completion item label. 539 /// 540 /// @since 3.17.0 - proposed state 541 #[cfg(feature = "proposed")] 542 #[derive(Debug, PartialEq, Default, Deserialize, Serialize, Clone)] 543 #[serde(rename_all = "camelCase")] 544 pub struct CompletionItemLabelDetails { 545 /// An optional string which is rendered less prominently directly after 546 /// {@link CompletionItemLabel.label label}, without any spacing. Should be 547 /// used for function signatures or type annotations. 548 #[serde(skip_serializing_if = "Option::is_none")] 549 pub detail: Option<String>, 550 551 /// An optional string which is rendered less prominently after 552 /// {@link CompletionItemLabel.detail}. Should be used for fully qualified 553 /// names or file path. 554 #[serde(skip_serializing_if = "Option::is_none")] 555 pub description: Option<String>, 556 } 557 558 #[cfg(test)] 559 mod tests { 560 use super::*; 561 use crate::tests::test_deserialization; 562 563 #[test] test_tag_support_deserialization()564 fn test_tag_support_deserialization() { 565 let mut empty = CompletionItemCapability::default(); 566 empty.tag_support = None; 567 568 test_deserialization(r#"{}"#, &empty); 569 test_deserialization(r#"{"tagSupport": false}"#, &empty); 570 571 let mut t = CompletionItemCapability::default(); 572 t.tag_support = Some(TagSupport { value_set: vec![] }); 573 test_deserialization(r#"{"tagSupport": true}"#, &t); 574 575 let mut t = CompletionItemCapability::default(); 576 t.tag_support = Some(TagSupport { 577 value_set: vec![CompletionItemTag::DEPRECATED], 578 }); 579 test_deserialization(r#"{"tagSupport": {"valueSet": [1]}}"#, &t); 580 } 581 582 #[test] test_debug_enum()583 fn test_debug_enum() { 584 assert_eq!(format!("{:?}", CompletionItemKind::TEXT), "Text"); 585 assert_eq!( 586 format!("{:?}", CompletionItemKind::TYPE_PARAMETER), 587 "TypeParameter" 588 ); 589 } 590 } 591