Resolve a subject to a [`SubjectOutcome`] in a single pass. This is the unified entry point for diagnostic subject resolution. It resolves the subject to `Vec ` (the same pipeline used by completion and hover) and classifies the result: - If any entry has `class_info`, return `Resolved`. - If all entries are primitive scalars, return `Scalar`. - If a type string refers to an unloada
(
subject: &str,
access_kind: AccessKind,
ctx: &ResolutionCtx<'_>,
)
| 1031 | /// `UnresolvableClass`. |
| 1032 | /// - If the result is empty, return `Untyped`. |
| 1033 | pub(crate) fn resolve_subject_outcome( |
| 1034 | subject: &str, |
| 1035 | access_kind: AccessKind, |
| 1036 | ctx: &ResolutionCtx<'_>, |
| 1037 | ) -> SubjectOutcome { |
| 1038 | let resolved = resolve_target_classes(subject, access_kind, ctx); |
| 1039 | if !resolved.is_empty() { |
| 1040 | // ── Check for class-bearing entries ────────────────────── |
| 1041 | let arced: Vec<Arc<ClassInfo>> = ResolvedType::into_arced_classes(resolved.clone()); |
| 1042 | if !arced.is_empty() { |
| 1043 | return SubjectOutcome::Resolved(arced); |
| 1044 | } |
| 1045 | |
| 1046 | // ── All entries are type-string-only (no class info) ──── |
| 1047 | let joined = ResolvedType::types_joined(&resolved); |
| 1048 | |
| 1049 | // Pure scalar — member access is a runtime crash. |
| 1050 | if joined.all_members_primitive_scalar() { |
| 1051 | let scalar = joined.non_null_type().unwrap_or(joined); |
| 1052 | return SubjectOutcome::Scalar(scalar); |
| 1053 | } |
| 1054 | |
| 1055 | // stdClass / object — synthetic resolution. |
| 1056 | if resolved |
| 1057 | .iter() |
| 1058 | .any(|rt| rt.type_string.is_named_ci("stdclass") || rt.type_string.is_object()) |
| 1059 | { |
| 1060 | let synthetic = Arc::new(ClassInfo { |
| 1061 | name: crate::atom::atom("stdClass"), |
| 1062 | ..ClassInfo::default() |
| 1063 | }); |
| 1064 | return SubjectOutcome::Resolved(vec![synthetic]); |
| 1065 | } |
| 1066 | |
| 1067 | // Non-scalar, non-class type — check for unresolvable class. |
| 1068 | if let Some(unresolved) = check_unresolvable_class_name(&joined, ctx.class_loader) { |
| 1069 | return SubjectOutcome::UnresolvableClass(unresolved); |
| 1070 | } |
| 1071 | return SubjectOutcome::Untyped; |
| 1072 | } |
| 1073 | |
| 1074 | // ── Result is empty — classify why ────────────────────────── |
| 1075 | let expr = SubjectExpr::parse(subject); |
| 1076 | |
| 1077 | // For call expressions, check the raw return type hint. |
| 1078 | if let SubjectExpr::CallExpr { |
| 1079 | callee, |
| 1080 | args_text: _, |
| 1081 | } = &expr |
| 1082 | { |
| 1083 | if let Some(scalar) = resolve_call_scalar_return(callee, access_kind, ctx) { |
| 1084 | return SubjectOutcome::Scalar(scalar); |
| 1085 | } |
| 1086 | // Try unresolvable class detection for function calls. |
| 1087 | if let SubjectExpr::FunctionCall(fn_name) = callee.as_ref() |
| 1088 | && let Some(fl) = ctx.function_loader |
| 1089 | && let Some(func_info) = fl(fn_name.as_str()) |
| 1090 | && let Some(ref raw_type) = func_info.return_type |
no test coverage detected