Parse `content` with the mago-syntax parser and pass the resulting `Program` (plus the content string) to `f`. Handles the boilerplate that every parse entry-point needs: allocating a `Bump` arena, creating a `FileId`, calling `parse_file_content`, and wrapping the whole thing in `catch_unwind` so that a parser panic doesn't crash the LSP server. On panic the error is logged (using `method_name`
(
content: &str,
method_name: &str,
f: impl FnOnce(&Program<'_>, &str) -> T,
)
| 746 | /// the previously parsed `Program` is reused — no allocation or |
| 747 | /// parsing occurs. |
| 748 | pub(crate) fn with_parsed_program<T: Default>( |
| 749 | content: &str, |
| 750 | method_name: &str, |
| 751 | f: impl FnOnce(&Program<'_>, &str) -> T, |
| 752 | ) -> T { |
| 753 | // ── Fast path: check the thread-local cache ───────────────── |
| 754 | // 0 = miss, 1 = content matches but not yet parsed, 2 = ready |
| 755 | let cache_state: u8 = PARSE_CACHE.with(|cell| { |
| 756 | let borrow = cell.borrow(); |
| 757 | match borrow.as_ref() { |
| 758 | Some(e) if e.content == content => { |
| 759 | if e.program_ptr.is_some() { |
| 760 | 2 |
| 761 | } else { |
| 762 | 1 |
| 763 | } |
| 764 | } |
| 765 | _ => 0, |
| 766 | } |
| 767 | }); |
| 768 | |
| 769 | // Lazily parse on first access and populate the cache entry. |
| 770 | if cache_state == 1 { |
| 771 | PARSE_CACHE.with(|cell| { |
| 772 | let mut borrow = cell.borrow_mut(); |
| 773 | let entry = borrow.as_mut().unwrap(); |
| 774 | let arena = bumpalo::Bump::new(); |
| 775 | let file_id = mago_database::file::FileId::new("input.php"); |
| 776 | // SAFETY: `program` borrows from `arena` and `entry.content`. |
| 777 | // The arena is moved into `entry.arena` immediately after |
| 778 | // extracting the raw pointer — the heap-allocated chunks do |
| 779 | // not move, so the pointer stays valid. `entry.content` lives |
| 780 | // inside the `RefCell` until the guard is dropped. |
| 781 | let program = mago_syntax::parser::parse_file_content(&arena, file_id, &entry.content); |
| 782 | let program_ptr: *const () = (program as *const Program<'_>).cast(); |
| 783 | entry.program_ptr = Some(program_ptr); |
| 784 | entry.arena = Some(arena); |
| 785 | }); |
| 786 | } |
| 787 | |
| 788 | if cache_state >= 1 { |
| 789 | return PARSE_CACHE.with(|cell| { |
| 790 | let borrow = cell.borrow(); |
| 791 | let entry = borrow.as_ref().unwrap(); |
| 792 | // SAFETY: `program_ptr` was created from a valid `&Program` |
| 793 | // whose backing arena and content string are still alive |
| 794 | // inside `entry`. We hold a `Ref` borrow on the `RefCell`, |
| 795 | // so the entry cannot be mutated or dropped while we use |
| 796 | // the reference. |
| 797 | let program: &Program<'_> = |
| 798 | unsafe { &*(entry.program_ptr.unwrap().cast::<Program<'_>>()) }; |
| 799 | f(program, &entry.content) |
| 800 | }); |
| 801 | } |
| 802 | |
| 803 | // ── Slow path: parse from scratch ─────────────────────────── |
| 804 | let content_owned = content.to_string(); |
| 805 | let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { |