1 //===--- RustDemangle.cpp ---------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file defines a demangler for Rust v0 mangled symbols as specified in 10 // https://rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "llvm/Demangle/RustDemangle.h" 15 #include "llvm/Demangle/Demangle.h" 16 17 #include <algorithm> 18 #include <cassert> 19 #include <cstring> 20 #include <limits> 21 22 using namespace llvm; 23 using namespace rust_demangle; 24 25 char *llvm::rustDemangle(const char *MangledName, char *Buf, size_t *N, 26 int *Status) { 27 if (MangledName == nullptr || (Buf != nullptr && N == nullptr)) { 28 if (Status != nullptr) 29 *Status = demangle_invalid_args; 30 return nullptr; 31 } 32 33 // Return early if mangled name doesn't look like a Rust symbol. 34 StringView Mangled(MangledName); 35 if (!Mangled.startsWith("_R")) { 36 if (Status != nullptr) 37 *Status = demangle_invalid_mangled_name; 38 return nullptr; 39 } 40 41 Demangler D; 42 if (!initializeOutputStream(nullptr, nullptr, D.Output, 1024)) { 43 if (Status != nullptr) 44 *Status = demangle_memory_alloc_failure; 45 return nullptr; 46 } 47 48 if (!D.demangle(Mangled)) { 49 if (Status != nullptr) 50 *Status = demangle_invalid_mangled_name; 51 std::free(D.Output.getBuffer()); 52 return nullptr; 53 } 54 55 D.Output += '\0'; 56 char *Demangled = D.Output.getBuffer(); 57 size_t DemangledLen = D.Output.getCurrentPosition(); 58 59 if (Buf != nullptr) { 60 if (DemangledLen <= *N) { 61 std::memcpy(Buf, Demangled, DemangledLen); 62 std::free(Demangled); 63 Demangled = Buf; 64 } else { 65 std::free(Buf); 66 } 67 } 68 69 if (N != nullptr) 70 *N = DemangledLen; 71 72 if (Status != nullptr) 73 *Status = demangle_success; 74 75 return Demangled; 76 } 77 78 Demangler::Demangler(size_t MaxRecursionLevel) 79 : MaxRecursionLevel(MaxRecursionLevel) {} 80 81 static inline bool isDigit(const char C) { return '0' <= C && C <= '9'; } 82 83 static inline bool isHexDigit(const char C) { 84 return ('0' <= C && C <= '9') || ('a' <= C && C <= 'f'); 85 } 86 87 static inline bool isLower(const char C) { return 'a' <= C && C <= 'z'; } 88 89 static inline bool isUpper(const char C) { return 'A' <= C && C <= 'Z'; } 90 91 /// Returns true if C is a valid mangled character: <0-9a-zA-Z_>. 92 static inline bool isValid(const char C) { 93 return isDigit(C) || isLower(C) || isUpper(C) || C == '_'; 94 } 95 96 // Demangles Rust v0 mangled symbol. Returns true when successful, and false 97 // otherwise. The demangled symbol is stored in Output field. It is 98 // responsibility of the caller to free the memory behind the output stream. 99 // 100 // <symbol-name> = "_R" <path> [<instantiating-crate>] 101 bool Demangler::demangle(StringView Mangled) { 102 Position = 0; 103 Error = false; 104 Print = true; 105 RecursionLevel = 0; 106 BoundLifetimes = 0; 107 108 if (!Mangled.consumeFront("_R")) { 109 Error = true; 110 return false; 111 } 112 Input = Mangled; 113 114 demanglePath(rust_demangle::InType::No); 115 116 // FIXME parse optional <instantiating-crate>. 117 118 if (Position != Input.size()) 119 Error = true; 120 121 return !Error; 122 } 123 124 // Demangles a path. InType indicates whether a path is inside a type. When 125 // LeaveOpen is true, a closing `>` after generic arguments is omitted from the 126 // output. Return value indicates whether generics arguments have been left 127 // open. 128 // 129 // <path> = "C" <identifier> // crate root 130 // | "M" <impl-path> <type> // <T> (inherent impl) 131 // | "X" <impl-path> <type> <path> // <T as Trait> (trait impl) 132 // | "Y" <type> <path> // <T as Trait> (trait definition) 133 // | "N" <ns> <path> <identifier> // ...::ident (nested path) 134 // | "I" <path> {<generic-arg>} "E" // ...<T, U> (generic args) 135 // | <backref> 136 // <identifier> = [<disambiguator>] <undisambiguated-identifier> 137 // <ns> = "C" // closure 138 // | "S" // shim 139 // | <A-Z> // other special namespaces 140 // | <a-z> // internal namespaces 141 bool Demangler::demanglePath(InType InType, LeaveOpen LeaveOpen) { 142 if (Error || RecursionLevel >= MaxRecursionLevel) { 143 Error = true; 144 return false; 145 } 146 SwapAndRestore<size_t> SaveRecursionLevel(RecursionLevel, RecursionLevel + 1); 147 148 switch (consume()) { 149 case 'C': { 150 parseOptionalBase62Number('s'); 151 Identifier Ident = parseIdentifier(); 152 print(Ident.Name); 153 break; 154 } 155 case 'M': { 156 demangleImplPath(InType); 157 print("<"); 158 demangleType(); 159 print(">"); 160 break; 161 } 162 case 'X': { 163 demangleImplPath(InType); 164 print("<"); 165 demangleType(); 166 print(" as "); 167 demanglePath(rust_demangle::InType::Yes); 168 print(">"); 169 break; 170 } 171 case 'Y': { 172 print("<"); 173 demangleType(); 174 print(" as "); 175 demanglePath(rust_demangle::InType::Yes); 176 print(">"); 177 break; 178 } 179 case 'N': { 180 char NS = consume(); 181 if (!isLower(NS) && !isUpper(NS)) { 182 Error = true; 183 break; 184 } 185 demanglePath(InType); 186 187 uint64_t Disambiguator = parseOptionalBase62Number('s'); 188 Identifier Ident = parseIdentifier(); 189 190 if (isUpper(NS)) { 191 // Special namespaces 192 print("::{"); 193 if (NS == 'C') 194 print("closure"); 195 else if (NS == 'S') 196 print("shim"); 197 else 198 print(NS); 199 if (!Ident.empty()) { 200 print(":"); 201 print(Ident.Name); 202 } 203 print('#'); 204 printDecimalNumber(Disambiguator); 205 print('}'); 206 } else { 207 // Implementation internal namespaces. 208 if (!Ident.empty()) { 209 print("::"); 210 print(Ident.Name); 211 } 212 } 213 break; 214 } 215 case 'I': { 216 demanglePath(InType); 217 // Omit "::" when in a type, where it is optional. 218 if (InType == rust_demangle::InType::No) 219 print("::"); 220 print("<"); 221 for (size_t I = 0; !Error && !consumeIf('E'); ++I) { 222 if (I > 0) 223 print(", "); 224 demangleGenericArg(); 225 } 226 if (LeaveOpen == rust_demangle::LeaveOpen::Yes) 227 return true; 228 else 229 print(">"); 230 break; 231 } 232 default: 233 // FIXME parse remaining productions. 234 Error = true; 235 break; 236 } 237 238 return false; 239 } 240 241 // <impl-path> = [<disambiguator>] <path> 242 // <disambiguator> = "s" <base-62-number> 243 void Demangler::demangleImplPath(InType InType) { 244 SwapAndRestore<bool> SavePrint(Print, false); 245 parseOptionalBase62Number('s'); 246 demanglePath(InType); 247 } 248 249 // <generic-arg> = <lifetime> 250 // | <type> 251 // | "K" <const> 252 // <lifetime> = "L" <base-62-number> 253 void Demangler::demangleGenericArg() { 254 if (consumeIf('L')) 255 printLifetime(parseBase62Number()); 256 else if (consumeIf('K')) 257 demangleConst(); 258 else 259 demangleType(); 260 } 261 262 // <basic-type> = "a" // i8 263 // | "b" // bool 264 // | "c" // char 265 // | "d" // f64 266 // | "e" // str 267 // | "f" // f32 268 // | "h" // u8 269 // | "i" // isize 270 // | "j" // usize 271 // | "l" // i32 272 // | "m" // u32 273 // | "n" // i128 274 // | "o" // u128 275 // | "s" // i16 276 // | "t" // u16 277 // | "u" // () 278 // | "v" // ... 279 // | "x" // i64 280 // | "y" // u64 281 // | "z" // ! 282 // | "p" // placeholder (e.g. for generic params), shown as _ 283 static bool parseBasicType(char C, BasicType &Type) { 284 switch (C) { 285 case 'a': 286 Type = BasicType::I8; 287 return true; 288 case 'b': 289 Type = BasicType::Bool; 290 return true; 291 case 'c': 292 Type = BasicType::Char; 293 return true; 294 case 'd': 295 Type = BasicType::F64; 296 return true; 297 case 'e': 298 Type = BasicType::Str; 299 return true; 300 case 'f': 301 Type = BasicType::F32; 302 return true; 303 case 'h': 304 Type = BasicType::U8; 305 return true; 306 case 'i': 307 Type = BasicType::ISize; 308 return true; 309 case 'j': 310 Type = BasicType::USize; 311 return true; 312 case 'l': 313 Type = BasicType::I32; 314 return true; 315 case 'm': 316 Type = BasicType::U32; 317 return true; 318 case 'n': 319 Type = BasicType::I128; 320 return true; 321 case 'o': 322 Type = BasicType::U128; 323 return true; 324 case 'p': 325 Type = BasicType::Placeholder; 326 return true; 327 case 's': 328 Type = BasicType::I16; 329 return true; 330 case 't': 331 Type = BasicType::U16; 332 return true; 333 case 'u': 334 Type = BasicType::Unit; 335 return true; 336 case 'v': 337 Type = BasicType::Variadic; 338 return true; 339 case 'x': 340 Type = BasicType::I64; 341 return true; 342 case 'y': 343 Type = BasicType::U64; 344 return true; 345 case 'z': 346 Type = BasicType::Never; 347 return true; 348 default: 349 return false; 350 } 351 } 352 353 void Demangler::printBasicType(BasicType Type) { 354 switch (Type) { 355 case BasicType::Bool: 356 print("bool"); 357 break; 358 case BasicType::Char: 359 print("char"); 360 break; 361 case BasicType::I8: 362 print("i8"); 363 break; 364 case BasicType::I16: 365 print("i16"); 366 break; 367 case BasicType::I32: 368 print("i32"); 369 break; 370 case BasicType::I64: 371 print("i64"); 372 break; 373 case BasicType::I128: 374 print("i128"); 375 break; 376 case BasicType::ISize: 377 print("isize"); 378 break; 379 case BasicType::U8: 380 print("u8"); 381 break; 382 case BasicType::U16: 383 print("u16"); 384 break; 385 case BasicType::U32: 386 print("u32"); 387 break; 388 case BasicType::U64: 389 print("u64"); 390 break; 391 case BasicType::U128: 392 print("u128"); 393 break; 394 case BasicType::USize: 395 print("usize"); 396 break; 397 case BasicType::F32: 398 print("f32"); 399 break; 400 case BasicType::F64: 401 print("f64"); 402 break; 403 case BasicType::Str: 404 print("str"); 405 break; 406 case BasicType::Placeholder: 407 print("_"); 408 break; 409 case BasicType::Unit: 410 print("()"); 411 break; 412 case BasicType::Variadic: 413 print("..."); 414 break; 415 case BasicType::Never: 416 print("!"); 417 break; 418 } 419 } 420 421 // <type> = | <basic-type> 422 // | <path> // named type 423 // | "A" <type> <const> // [T; N] 424 // | "S" <type> // [T] 425 // | "T" {<type>} "E" // (T1, T2, T3, ...) 426 // | "R" [<lifetime>] <type> // &T 427 // | "Q" [<lifetime>] <type> // &mut T 428 // | "P" <type> // *const T 429 // | "O" <type> // *mut T 430 // | "F" <fn-sig> // fn(...) -> ... 431 // | "D" <dyn-bounds> <lifetime> // dyn Trait<Assoc = X> + Send + 'a 432 // | <backref> // backref 433 void Demangler::demangleType() { 434 size_t Start = Position; 435 436 char C = consume(); 437 BasicType Type; 438 if (parseBasicType(C, Type)) 439 return printBasicType(Type); 440 441 switch (C) { 442 case 'A': 443 print("["); 444 demangleType(); 445 print("; "); 446 demangleConst(); 447 print("]"); 448 break; 449 case 'S': 450 print("["); 451 demangleType(); 452 print("]"); 453 break; 454 case 'T': { 455 print("("); 456 size_t I = 0; 457 for (; !Error && !consumeIf('E'); ++I) { 458 if (I > 0) 459 print(", "); 460 demangleType(); 461 } 462 if (I == 1) 463 print(","); 464 print(")"); 465 break; 466 } 467 case 'R': 468 case 'Q': 469 print('&'); 470 if (consumeIf('L')) { 471 if (auto Lifetime = parseBase62Number()) { 472 printLifetime(Lifetime); 473 print(' '); 474 } 475 } 476 if (C == 'Q') 477 print("mut "); 478 demangleType(); 479 break; 480 case 'P': 481 print("*const "); 482 demangleType(); 483 break; 484 case 'O': 485 print("*mut "); 486 demangleType(); 487 break; 488 case 'F': 489 demangleFnSig(); 490 break; 491 case 'D': 492 demangleDynBounds(); 493 if (consumeIf('L')) { 494 if (auto Lifetime = parseBase62Number()) { 495 print(" + "); 496 printLifetime(Lifetime); 497 } 498 } else { 499 Error = true; 500 } 501 break; 502 default: 503 Position = Start; 504 demanglePath(rust_demangle::InType::Yes); 505 break; 506 } 507 } 508 509 // <fn-sig> := [<binder>] ["U"] ["K" <abi>] {<type>} "E" <type> 510 // <abi> = "C" 511 // | <undisambiguated-identifier> 512 void Demangler::demangleFnSig() { 513 SwapAndRestore<size_t> SaveBoundLifetimes(BoundLifetimes, BoundLifetimes); 514 demangleOptionalBinder(); 515 516 if (consumeIf('U')) 517 print("unsafe "); 518 519 if (consumeIf('K')) { 520 print("extern \""); 521 if (consumeIf('C')) { 522 print("C"); 523 } else { 524 Identifier Ident = parseIdentifier(); 525 for (char C : Ident.Name) { 526 // When mangling ABI string, the "-" is replaced with "_". 527 if (C == '_') 528 C = '-'; 529 print(C); 530 } 531 } 532 print("\" "); 533 } 534 535 print("fn("); 536 for (size_t I = 0; !Error && !consumeIf('E'); ++I) { 537 if (I > 0) 538 print(", "); 539 demangleType(); 540 } 541 print(")"); 542 543 if (consumeIf('u')) { 544 // Skip the unit type from the output. 545 } else { 546 print(" -> "); 547 demangleType(); 548 } 549 } 550 551 // <dyn-bounds> = [<binder>] {<dyn-trait>} "E" 552 void Demangler::demangleDynBounds() { 553 SwapAndRestore<size_t> SaveBoundLifetimes(BoundLifetimes, BoundLifetimes); 554 print("dyn "); 555 demangleOptionalBinder(); 556 for (size_t I = 0; !Error && !consumeIf('E'); ++I) { 557 if (I > 0) 558 print(" + "); 559 demangleDynTrait(); 560 } 561 } 562 563 // <dyn-trait> = <path> {<dyn-trait-assoc-binding>} 564 // <dyn-trait-assoc-binding> = "p" <undisambiguated-identifier> <type> 565 void Demangler::demangleDynTrait() { 566 bool IsOpen = demanglePath(InType::Yes, LeaveOpen::Yes); 567 while (!Error && consumeIf('p')) { 568 if (!IsOpen) { 569 IsOpen = true; 570 print('<'); 571 } else { 572 print(", "); 573 } 574 print(parseIdentifier().Name); 575 print(" = "); 576 demangleType(); 577 } 578 if (IsOpen) 579 print(">"); 580 } 581 582 // Demangles optional binder and updates the number of bound lifetimes. 583 // 584 // <binder> = "G" <base-62-number> 585 void Demangler::demangleOptionalBinder() { 586 uint64_t Binder = parseOptionalBase62Number('G'); 587 if (Error || Binder == 0) 588 return; 589 590 // In valid inputs each bound lifetime is referenced later. Referencing a 591 // lifetime requires at least one byte of input. Reject inputs that are too 592 // short to reference all bound lifetimes. Otherwise demangling of invalid 593 // binders could generate excessive amounts of output. 594 if (Binder >= Input.size() - BoundLifetimes) { 595 Error = true; 596 return; 597 } 598 599 print("for<"); 600 for (size_t I = 0; I != Binder; ++I) { 601 BoundLifetimes += 1; 602 if (I > 0) 603 print(", "); 604 printLifetime(1); 605 } 606 print("> "); 607 } 608 609 // <const> = <basic-type> <const-data> 610 // | "p" // placeholder 611 // | <backref> 612 void Demangler::demangleConst() { 613 BasicType Type; 614 if (parseBasicType(consume(), Type)) { 615 switch (Type) { 616 case BasicType::I8: 617 case BasicType::I16: 618 case BasicType::I32: 619 case BasicType::I64: 620 case BasicType::I128: 621 case BasicType::ISize: 622 case BasicType::U8: 623 case BasicType::U16: 624 case BasicType::U32: 625 case BasicType::U64: 626 case BasicType::U128: 627 case BasicType::USize: 628 demangleConstInt(); 629 break; 630 case BasicType::Bool: 631 demangleConstBool(); 632 break; 633 case BasicType::Char: 634 demangleConstChar(); 635 break; 636 case BasicType::Placeholder: 637 print('_'); 638 break; 639 default: 640 // FIXME demangle backreferences. 641 Error = true; 642 break; 643 } 644 } else { 645 Error = true; 646 } 647 } 648 649 // <const-data> = ["n"] <hex-number> 650 void Demangler::demangleConstInt() { 651 if (consumeIf('n')) 652 print('-'); 653 654 StringView HexDigits; 655 uint64_t Value = parseHexNumber(HexDigits); 656 if (HexDigits.size() <= 16) { 657 printDecimalNumber(Value); 658 } else { 659 print("0x"); 660 print(HexDigits); 661 } 662 } 663 664 // <const-data> = "0_" // false 665 // | "1_" // true 666 void Demangler::demangleConstBool() { 667 StringView HexDigits; 668 parseHexNumber(HexDigits); 669 if (HexDigits == "0") 670 print("false"); 671 else if (HexDigits == "1") 672 print("true"); 673 else 674 Error = true; 675 } 676 677 /// Returns true if CodePoint represents a printable ASCII character. 678 static bool isAsciiPrintable(uint64_t CodePoint) { 679 return 0x20 <= CodePoint && CodePoint <= 0x7e; 680 } 681 682 // <const-data> = <hex-number> 683 void Demangler::demangleConstChar() { 684 StringView HexDigits; 685 uint64_t CodePoint = parseHexNumber(HexDigits); 686 if (Error || HexDigits.size() > 6) { 687 Error = true; 688 return; 689 } 690 691 print("'"); 692 switch (CodePoint) { 693 case '\t': 694 print(R"(\t)"); 695 break; 696 case '\r': 697 print(R"(\r)"); 698 break; 699 case '\n': 700 print(R"(\n)"); 701 break; 702 case '\\': 703 print(R"(\\)"); 704 break; 705 case '"': 706 print(R"(")"); 707 break; 708 case '\'': 709 print(R"(\')"); 710 break; 711 default: 712 if (isAsciiPrintable(CodePoint)) { 713 char C = CodePoint; 714 print(C); 715 } else { 716 print(R"(\u{)"); 717 print(HexDigits); 718 print('}'); 719 } 720 break; 721 } 722 print('\''); 723 } 724 725 // <undisambiguated-identifier> = ["u"] <decimal-number> ["_"] <bytes> 726 Identifier Demangler::parseIdentifier() { 727 bool Punycode = consumeIf('u'); 728 uint64_t Bytes = parseDecimalNumber(); 729 730 // Underscore resolves the ambiguity when identifier starts with a decimal 731 // digit or another underscore. 732 consumeIf('_'); 733 734 if (Error || Bytes > Input.size() - Position) { 735 Error = true; 736 return {}; 737 } 738 StringView S = Input.substr(Position, Bytes); 739 Position += Bytes; 740 741 if (!std::all_of(S.begin(), S.end(), isValid)) { 742 Error = true; 743 return {}; 744 } 745 746 return {S, Punycode}; 747 } 748 749 // Parses optional base 62 number. The presence of a number is determined using 750 // Tag. Returns 0 when tag is absent and parsed value + 1 otherwise 751 // 752 // This function is indended for parsing disambiguators and binders which when 753 // not present have their value interpreted as 0, and otherwise as decoded 754 // value + 1. For example for binders, value for "G_" is 1, for "G0_" value is 755 // 2. When "G" is absent value is 0. 756 uint64_t Demangler::parseOptionalBase62Number(char Tag) { 757 if (!consumeIf(Tag)) 758 return 0; 759 760 uint64_t N = parseBase62Number(); 761 if (Error || !addAssign(N, 1)) 762 return 0; 763 764 return N; 765 } 766 767 // Parses base 62 number with <0-9a-zA-Z> as digits. Number is terminated by 768 // "_". All values are offset by 1, so that "_" encodes 0, "0_" encodes 1, 769 // "1_" encodes 2, etc. 770 // 771 // <base-62-number> = {<0-9a-zA-Z>} "_" 772 uint64_t Demangler::parseBase62Number() { 773 if (consumeIf('_')) 774 return 0; 775 776 uint64_t Value = 0; 777 778 while (true) { 779 uint64_t Digit; 780 char C = consume(); 781 782 if (C == '_') { 783 break; 784 } else if (isDigit(C)) { 785 Digit = C - '0'; 786 } else if (isLower(C)) { 787 Digit = 10 + (C - 'a'); 788 } else if (isUpper(C)) { 789 Digit = 10 + 26 + (C - 'A'); 790 } else { 791 Error = true; 792 return 0; 793 } 794 795 if (!mulAssign(Value, 62)) 796 return 0; 797 798 if (!addAssign(Value, Digit)) 799 return 0; 800 } 801 802 if (!addAssign(Value, 1)) 803 return 0; 804 805 return Value; 806 } 807 808 // Parses a decimal number that had been encoded without any leading zeros. 809 // 810 // <decimal-number> = "0" 811 // | <1-9> {<0-9>} 812 uint64_t Demangler::parseDecimalNumber() { 813 char C = look(); 814 if (!isDigit(C)) { 815 Error = true; 816 return 0; 817 } 818 819 if (C == '0') { 820 consume(); 821 return 0; 822 } 823 824 uint64_t Value = 0; 825 826 while (isDigit(look())) { 827 if (!mulAssign(Value, 10)) { 828 Error = true; 829 return 0; 830 } 831 832 uint64_t D = consume() - '0'; 833 if (!addAssign(Value, D)) 834 return 0; 835 } 836 837 return Value; 838 } 839 840 // Parses a hexadecimal number with <0-9a-f> as a digits. Returns the parsed 841 // value and stores hex digits in HexDigits. The return value is unspecified if 842 // HexDigits.size() > 16. 843 // 844 // <hex-number> = "0_" 845 // | <1-9a-f> {<0-9a-f>} "_" 846 uint64_t Demangler::parseHexNumber(StringView &HexDigits) { 847 size_t Start = Position; 848 uint64_t Value = 0; 849 850 if (!isHexDigit(look())) 851 Error = true; 852 853 if (consumeIf('0')) { 854 if (!consumeIf('_')) 855 Error = true; 856 } else { 857 while (!Error && !consumeIf('_')) { 858 char C = consume(); 859 Value *= 16; 860 if (isDigit(C)) 861 Value += C - '0'; 862 else if ('a' <= C && C <= 'f') 863 Value += 10 + (C - 'a'); 864 else 865 Error = true; 866 } 867 } 868 869 if (Error) { 870 HexDigits = StringView(); 871 return 0; 872 } 873 874 size_t End = Position - 1; 875 assert(Start < End); 876 HexDigits = Input.substr(Start, End - Start); 877 return Value; 878 } 879 880 // Prints a lifetime. An index 0 always represents an erased lifetime. Indices 881 // starting from 1, are De Bruijn indices, referring to higher-ranked lifetimes 882 // bound by one of the enclosing binders. 883 void Demangler::printLifetime(uint64_t Index) { 884 if (Index == 0) { 885 print("'_"); 886 return; 887 } 888 889 if (Index - 1 >= BoundLifetimes) { 890 Error = true; 891 return; 892 } 893 894 uint64_t Depth = BoundLifetimes - Index; 895 print('\''); 896 if (Depth < 26) { 897 char C = 'a' + Depth; 898 print(C); 899 } else { 900 print('z'); 901 printDecimalNumber(Depth - 26 + 1); 902 } 903 } 904