@@ -135,15 +135,30 @@ protected override void ConstructPeerCore (
135135 }
136136
137137 try {
138- // Mirror legacy GetPeerType: callers commonly request universal
139- // interfaces / boxes (IJavaPeerable, object, Exception) — map these
140- // to a concrete peer type so the proxy lookup can succeed.
141- var resolvedTargetType = JavaMarshalValueManagerHelper . ResolvePeerType ( targetType ) ;
142- var peer = TrimmableTypeMap . Instance . CreateInstance ( reference . Handle , resolvedTargetType ) ;
143- if ( peer is not null ) {
144- return peer ;
138+ var resolvedTargetType = ResolvePeerType ( targetType ) ;
139+ return TrimmableTypeMap . Instance . CreateInstance ( reference . Handle , resolvedTargetType )
140+ ?? NotFoundFallback ( ref reference , targetType , resolvedTargetType ) ;
141+ } finally {
142+ JniObjectReference . Dispose ( ref reference , transfer ) ;
143+ }
144+
145+ [ return : DynamicallyAccessedMembers ( Constructors ) ]
146+ static Type ? ResolvePeerType ( [ DynamicallyAccessedMembers ( Constructors ) ] Type ? type )
147+ {
148+ if ( type is null ) {
149+ return null ;
145150 }
151+ if ( type == typeof ( object ) || type == typeof ( IJavaPeerable ) ) {
152+ return typeof ( global ::Java . Interop . JavaObject ) ;
153+ }
154+ if ( type == typeof ( Exception ) ) {
155+ return typeof ( JavaException ) ;
156+ }
157+ return type ;
158+ }
146159
160+ static IJavaPeerable ? NotFoundFallback ( ref JniObjectReference reference , Type ? targetType , Type ? resolvedTargetType )
161+ {
147162 // Disambiguate the failure — match the contract of the base
148163 // JniRuntime.JniValueManager.CreatePeer so JavaCast / JavaAs
149164 // surface the right exception (or null) to callers:
@@ -161,7 +176,7 @@ protected override void ConstructPeerCore (
161176 nameof ( targetType ) ) ;
162177 }
163178
164- if ( JavaMarshalValueManagerHelper . IsIncompatibleCast ( targetJniName , ref reference , resolvedTargetType ) ) {
179+ if ( IsIncompatibleCast ( targetJniName , ref reference , resolvedTargetType ) ) {
165180 return null ;
166181 }
167182 }
@@ -173,9 +188,43 @@ protected override void ConstructPeerCore (
173188 $ "No generated { nameof ( JavaPeerProxy ) } was found for Java type '{ javaType } ' " +
174189 $ "with targetType '{ targetName } ' while { nameof ( RuntimeFeature . TrimmableTypeMap ) } is enabled. " +
175190 $ "This indicates a missing trimmable typemap proxy or association and should be fixed in the generator.") ;
176- } finally {
177- JniObjectReference . Dispose ( ref reference , transfer ) ;
178191 }
192+
193+ static bool IsIncompatibleCast (
194+ string targetJniName ,
195+ ref JniObjectReference reference ,
196+ Type targetType )
197+ {
198+ var instanceClass = JniEnvironment . Types . GetObjectClass ( reference ) ;
199+ JniObjectReference targetClass = default ;
200+ try {
201+ targetClass = JniEnvironment . Types . FindClass ( targetJniName ) ;
202+
203+ if ( ! JniEnvironment . Types . IsAssignableFrom ( instanceClass , targetClass ) ) {
204+ // Match the legacy cast diagnostic when assembly logging is enabled.
205+ if ( Logger . LogAssembly ) {
206+ var targetSig = JniRuntime . CurrentRuntime . TypeManager . GetTypeSignature ( targetType ) ;
207+ var message = $ "Handle 0x{ reference . Handle : x} is of type '{ JNIEnv . GetClassNameFromInstance ( reference . Handle ) } ' which is not assignable to '{ targetSig . SimpleReference } '";
208+ Logger . Log ( LogLevel . Debug , "monodroid-assembly" , message ) ;
209+ }
210+
211+ if ( RuntimeFeature . IsAssignableFromCheck ) {
212+ return true ;
213+ }
214+ }
215+ } catch ( Java . Lang . ClassNotFoundException e ) {
216+ throw new ArgumentException (
217+ $ "Could not find Java class '{ targetJniName } '.",
218+ nameof ( targetType ) , e ) ;
219+ } finally {
220+ JniObjectReference . Dispose ( ref instanceClass ) ;
221+ JniObjectReference . Dispose ( ref targetClass ) ;
222+ }
223+
224+ // Compatible classes mean a proxy/activation gap.
225+ return false ;
226+ }
227+
179228 }
180229
181230 [ return : MaybeNull ]
0 commit comments