@@ -181,8 +181,79 @@ internal class KtStabilityInferencer(private val project: Project? = null) {
181181 )
182182 }
183183
184- // Analyze with empty tracking set
185- return analyzeClassSymbol(classSymbol, emptySet())
184+ // Analyze the class itself
185+ val classStability = analyzeClassSymbol(classSymbol, emptySet())
186+
187+ // Analyze type arguments if present (e.g., UiResult<Unit> -> check if Unit is stable)
188+ val typeArgumentStabilities = analyzeTypeArguments(nonNullableType)
189+
190+ // If class is already unstable, return it directly
191+ if (classStability.isUnstable()) {
192+ return classStability
193+ }
194+
195+ // If any type argument is unstable, the whole type is unstable
196+ val unstableTypeArg = typeArgumentStabilities.find { it.isUnstable() }
197+ if (unstableTypeArg != null ) {
198+ return KtStability .Certain (
199+ stable = false ,
200+ reason = " Has unstable type argument: ${unstableTypeArg.getReasonString()} " ,
201+ )
202+ }
203+
204+ // If any type argument is runtime, the whole type is runtime
205+ val runtimeTypeArg = typeArgumentStabilities.find { it is KtStability .Runtime }
206+ if (runtimeTypeArg != null ) {
207+ return KtStability .Runtime (
208+ className = originalTypeString,
209+ reason = " Has runtime type argument: ${runtimeTypeArg.getReasonString()} " ,
210+ )
211+ }
212+
213+ // If class stability is runtime but all type args are stable, still runtime
214+ if (classStability is KtStability .Runtime ) {
215+ return classStability
216+ }
217+
218+ // All stable - return class stability with type args info if applicable
219+ val hasStableTypeArgs = typeArgumentStabilities.isNotEmpty() &&
220+ typeArgumentStabilities.all { it.isStable() }
221+ return if (hasStableTypeArgs) {
222+ KtStability .Certain (
223+ stable = true ,
224+ reason = " ${classStability.getReasonString()} (all type arguments are stable)" ,
225+ )
226+ } else {
227+ classStability
228+ }
229+ }
230+
231+ /* *
232+ * Analyzes type arguments of a generic type.
233+ * Returns empty list if type has no type arguments.
234+ */
235+ context(KaSession )
236+ private fun analyzeTypeArguments (type : KaType ): List <KtStability > {
237+ return try {
238+ // Use reflection to access typeArguments as it may differ between K2 versions
239+ val typeArgsMethod = type::class .members.find { it.name == " typeArguments" }
240+ if (typeArgsMethod != null ) {
241+ @Suppress(" UNCHECKED_CAST" )
242+ val typeArgs = typeArgsMethod.call(type) as ? List <* > ? : return emptyList()
243+ typeArgs.mapNotNull { typeArg ->
244+ // Each type argument is a KaTypeProjection which has a type property
245+ val typeProperty = typeArg?.let { arg ->
246+ arg::class .members.find { it.name == " type" }?.call(arg) as ? KaType
247+ }
248+ typeProperty?.let { ktStabilityOf(it) }
249+ }
250+ } else {
251+ emptyList()
252+ }
253+ } catch (e: Exception ) {
254+ // If we can't analyze type arguments, return empty
255+ emptyList()
256+ }
186257 }
187258
188259 /* *
0 commit comments