Shared implementation behind [`resolve_class_fully`] and [`resolve_class_fully_cached`].
(
class: &ClassInfo,
class_loader: &dyn Fn(&str) -> Option<Arc<ClassInfo>>,
cache: Option<&ResolvedClassCache>,
generic_args: &[String],
)
| 754 | /// Shared implementation behind [`resolve_class_fully`] and |
| 755 | /// [`resolve_class_fully_cached`]. |
| 756 | fn resolve_class_fully_inner( |
| 757 | class: &ClassInfo, |
| 758 | class_loader: &dyn Fn(&str) -> Option<Arc<ClassInfo>>, |
| 759 | cache: Option<&ResolvedClassCache>, |
| 760 | generic_args: &[String], |
| 761 | ) -> Arc<ClassInfo> { |
| 762 | let fqn = class.fqn(); |
| 763 | let cache_key: ResolvedClassCacheKey = (fqn, generic_args.to_vec()); |
| 764 | |
| 765 | // ── Reload raw class from class_loader ────────────────────────── |
| 766 | // Callers sometimes pass a ClassInfo that has already been through |
| 767 | // `apply_generic_args` (e.g. `MockBuilder<Rule>` where template |
| 768 | // parameters are substituted with concrete types). If we resolve |
| 769 | // from that substituted class and cache the result under the bare |
| 770 | // FQN key `(MockBuilder, [])`, every subsequent lookup gets the |
| 771 | // contaminated version — causing non-deterministic diagnostics |
| 772 | // depending on which thread/file was processed first (B28). |
| 773 | // |
| 774 | // To prevent this, always try to reload the raw (un-substituted) |
| 775 | // class from the class_loader. The class_loader returns the |
| 776 | // parsed ClassInfo from uri_classes_index/stubs, which has unsubstituted |
| 777 | // template parameters. Fall back to the passed-in `class` only |
| 778 | // when the loader cannot find it (e.g. anonymous classes). |
| 779 | let raw_class_arc = class_loader(fqn.as_str()); |
| 780 | let effective_class: &ClassInfo = match &raw_class_arc { |
| 781 | Some(raw) => raw, |
| 782 | None => class, |
| 783 | }; |
| 784 | |
| 785 | // ── Cache lookup ──────────────────────────────────────────────── |
| 786 | if let Some(cache) = cache { |
| 787 | let map = cache.lock(); |
| 788 | if let Some(cached) = map.get(&cache_key) { |
| 789 | return Arc::clone(cached); |
| 790 | } |
| 791 | } |
| 792 | |
| 793 | // ── Recursion guard ───────────────────────────────────────────── |
| 794 | // If this class is already being resolved on this thread (re-entrant |
| 795 | // call from a virtual member provider or interface merge), or we have |
| 796 | // exceeded the maximum nesting depth, return a partial result with |
| 797 | // base inheritance only. This breaks the mutual recursion that |
| 798 | // causes stack overflow. |
| 799 | let depth = RESOLVE_DEPTH.with(|d| d.get()); |
| 800 | if depth >= MAX_RESOLVE_DEPTH || !mark_resolving(&fqn) { |
| 801 | // Already resolving or too deep — return base-only resolution. |
| 802 | return Arc::new(resolve_class_with_inheritance( |
| 803 | effective_class, |
| 804 | class_loader, |
| 805 | )); |
| 806 | } |
| 807 | RESOLVE_DEPTH.with(|d| d.set(depth + 1)); |
| 808 | |
| 809 | // ── Uncached resolution ───────────────────────────────────────── |
| 810 | let mut merged = resolve_class_with_inheritance(effective_class, class_loader); |
| 811 | |
| 812 | // ── Pre-provider patches ──────────────────────────────────────── |
| 813 | // Inject missing `@mixin` annotations before virtual member |
no test coverage detected