// // Test the generated API for common protocol support // // Authors: // Sebastien Pouliot // // Copyright 2013, 2015 Xamarin Inc. // using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; using NUnit.Framework; using Foundation; using ObjCRuntime; namespace Introspection { public abstract class ApiProtocolTest : ApiBaseTest { static IntPtr conform_to = Selector.GetHandle ("conformsToProtocol:"); public ApiProtocolTest () { ContinueOnFailure = true; } static bool ConformTo (IntPtr klass, IntPtr protocol) { return bool_objc_msgSend_IntPtr (klass, conform_to, protocol); } protected virtual bool Skip (Type type) { switch (type.Name) { // *** NSForwarding: warning: object 0x5cbd078 of class 'JSExport' does not implement methodSignatureForSelector: -- trouble ahead // *** NSForwarding: warning: object 0x5cbd078 of class 'JSExport' does not implement doesNotRecognizeSelector: -- abort case "JSExport": return true; #if !NET case "MTLCounter": case "MTLCounterSampleBuffer": case "MTLCounterSet": return true; // Incorrectly bound, will be fixed for .NET. #endif case "MPSImageLaplacianPyramid": case "MPSImageLaplacianPyramidSubtract": case "MPSImageLaplacianPyramidAdd": case "MPSCnnYoloLossNode": // The presence of these seem to depend on hardware capabilities, and not only OS version. // Unfortunately I couldn't find any documentation related to determining exactly which // hardware capability these need (or how to detect them), so just ignore them. return true; case "CLKComplicationWidgetMigrator": // Not present in the simulator, is a migration class return true; // was removed by apple and is a compat class. case "HMMatterRequestHandler": return true; default: return SkipDueToAttribute (type); } } IntPtr GetClass (Type type) { return Class.GetHandle (type); } protected virtual bool Skip (Type type, string protocolName) { // The following protocols are skipped in classic since they were added in // later versions if (IntPtr.Size == 4) { switch (protocolName) { case "UIUserActivityRestoring": return true; } } switch (protocolName) { case "NSCopying": switch (type.Name) { // undocumented conformance (up to 7.0) and conformity varies between iOS versions case "CAEmitterCell": case "GKAchievement": case "GKScore": case "MPMediaItem": // new in iOS8 and 10.0 case "NSExtensionContext": case "NSLayoutAnchor`1": case "NSLayoutDimension": case "NSLayoutXAxisAnchor": case "NSLayoutYAxisAnchor": // iOS 10 beta 3 case "GKCloudPlayer": // iOS 10 : test throw because of generic usage case "NSMeasurement`1": // Xcode 9 - Conformance not in headers case "MLDictionaryConstraint": case "MLImageConstraint": case "MLMultiArrayConstraint": case "VSSubscription": return true; // skip // xcode 10 case "VSAccountMetadata": case "VSAccountMetadataRequest": case "VSAccountProviderResponse": case "PHEditingExtensionContext": case "HKCumulativeQuantitySeriesSample": // Xcode 11 - Conformance not in headers case "UISceneActivationConditions": case "UISceneSession": return true; // Xcode 11 case "NSFileProviderSearchQuery": return true; // Xcode 12 case "ACAccountType": case "ASAccountAuthenticationModificationExtensionContext": case "ASCredentialProviderExtensionContext": case "AVAssetDownloadUrlSession": case "NSFileProviderDomain": // Conformance not in headers case "NSUrlSession": case "SNClassification": case "SNClassificationResult": return true; // PassKit now available on macOS 11+ case "PKPaymentMethod": case "PKPaymentMerchantSession": case "PKPaymentSummaryItem": case "PKShareablePassMetadata": case "PKShippingMethod": case "PKSuicaPassProperties": // Conformance not in headers case "PKTransitPassProperties": // Conformance not in headers // Xcode 12.2 case "VSAccountApplicationProvider": // Conformance not in headers // Xcode 12.5 case "HMCharacteristicMetadata": case "HMAccessoryCategory": return true; // Xcode 13 case "HKVerifiableClinicalRecord": case "PKDeferredPaymentSummaryItem": case "PKRecurringPaymentSummaryItem": case "PKStoredValuePassProperties": case "SNTimeDurationConstraint": // Conformance not in headers // Xcode 13.3 case "HMAccessorySetupPayload": // Conformance not in headers return true; // Xcode 14 case "CLKComplicationIntentWidgetMigrationConfiguration": case "CLKComplicationStaticWidgetMigrationConfiguration": case "CLKComplicationWidgetMigrationConfiguration": case "HKElectrocardiogramVoltageMeasurement": case "AVPlayerInterstitialEvent": case "HKWorkoutActivity": case "HKContactsPrescription": case "HKGlassesPrescription": case "HKVisionPrescription": case "PKAutomaticReloadPaymentRequest": case "PKAutomaticReloadPaymentSummaryItem": case "PKPaymentOrderDetails": case "PKPaymentTokenContext": case "PKRecurringPaymentRequest": case "PKShareablePassMetadataPreview": return true; } break; case "NSMutableCopying": switch (type.Name) { // iOS 10 : test throw because of generic usage case "NSMeasurement`1": return true; // skip // Xcode 10 case "UNNotificationCategory": case "UNNotificationSound": // Xcode 11 - Conformance not in headers case "UISceneSession": return true; // xocde 12 beta 1 case "NSUrlSessionConfiguration": return true; } break; case "NSCoding": switch (type.Name) { // only documented to support NSCopying - not NSCoding (fails on iOS 7.1 but not 8.0) case "NSUrlSessionTask": case "NSUrlSessionDataTask": case "NSUrlSessionUploadTask": case "NSUrlSessionDownloadTask": // case "NSUrlSessionConfiguration": case "NSMergeConflict": // new in iOS8 and 10.0 case "NSExtensionContext": case "NSItemProvider": // iOS9 / 10.11 case "CNSaveRequest": case "NSLayoutAnchor`1": case "NSLayoutDimension": case "NSLayoutXAxisAnchor": case "NSLayoutYAxisAnchor": case "GKCloudPlayer": case "GKGameSession": // iOS 10 : test throw because of generic usage case "NSMeasurement`1": // iOS 11 / tvOS 11 case "VSSubscription": // iOS 11.3 / macOS 10.13.4 case "NSEntityMapping": case "NSMappingModel": case "NSPropertyMapping": return true; // Xcode 10 case "NSManagedObjectID": case "VSAccountMetadata": case "VSAccountMetadataRequest": case "VSAccountProviderResponse": case "PHEditingExtensionContext": return true; // Xcode 11 (on device only) case "ICHandle": case "ICNotification": case "ICNotificationManagerConfiguration": case "MPSNNNeuronDescriptor": // Xcode 11 case "NSFileProviderItemVersion": case "NSFileProviderRequest": case "NSFileProviderSearchQuery": return true; // Xcode 11.4, not documented case "NSHttpCookie": return true; // Xcode 12 beta 1 case "ASAccountAuthenticationModificationExtensionContext": case "ASCredentialProviderExtensionContext": case "GCController": case "GCExtendedGamepad": case "GCMicroGamepad": case "GCMotion": case "INFile": case "SNClassification": case "SNClassificationResult": return true; // PassKit now available on macOS 11+ case "PKPayment": case "PKPaymentSummaryItem": case "PKShippingMethod": case "PKPaymentRequest": case "PKPaymentToken": case "PKLabeledValue": case "PKPaymentAuthorizationResult": case "PKPaymentRequestShippingMethodUpdate": case "PKPaymentRequestUpdate": case "PKPaymentRequestPaymentMethodUpdate": case "PKPaymentRequestShippingContactUpdate": case "PKSuicaPassProperties": // Conformance not in headers case "PKTransitPassProperties": // Conformance not in headers case "PKDisbursementRequest": case "PKDisbursementVoucher": case "PKAddCarKeyPassConfiguration": case "PKAddSecureElementPassConfiguration": case "PKAddShareablePassConfiguration": case "PKBarcodeEventConfigurationRequest": case "PKBarcodeEventMetadataRequest": case "PKBarcodeEventMetadataResponse": case "PKBarcodeEventSignatureRequest": case "PKBarcodeEventSignatureResponse": case "PKIssuerProvisioningExtensionPaymentPassEntry": case "PKIssuerProvisioningExtensionStatus": case "PKPaymentMerchantSession": case "PKPaymentRequestMerchantSessionUpdate": case "PKShareablePassMetadata": // Xcode 12.2 case "VSAccountApplicationProvider": // Conformance not in headers return true; // Xcode 12.3 case "GCDirectionalGamepad": case "GCExtendedGamepadSnapshot": case "GCGamepadSnapshot": case "GCMicroGamepadSnapshot": case "GCGamepad": return true; // Xcode 12.5 case "GCDualSenseGamepad": // Xcode 13 case "AVAssetDownloadConfiguration": case "AVAssetDownloadContentConfiguration": case "AVAssetVariant": case "AVAssetVariantQualifier": case "PKDeferredPaymentSummaryItem": case "PKPaymentRequestCouponCodeUpdate": case "PKRecurringPaymentSummaryItem": case "PKStoredValuePassBalance": case "PKStoredValuePassProperties": case "QLPreviewReply": // conformance not in headers case "QLPreviewReplyAttachment": // conformance not in headers case "SNTimeDurationConstraint": // Conformance not in headers // Xcode 13.3 case "SRWristDetection": // Conformance not in headers case "HMAccessorySetupPayload": // Conformance not in headers case "HMAccessorySetupRequest": // Conformance not in headers case "HMAccessorySetupResult": // Conformance not in headers return true; // Xcode 14 beta 2 case "CLKComplicationIntentWidgetMigrationConfiguration": case "CLKComplicationStaticWidgetMigrationConfiguration": case "CLKComplicationWidgetMigrationConfiguration": case "PHPickerConfiguration": case "PHAssetChangeRequest": case "PHAssetCreationRequest": case "NSUserActivity": case "UIDictationPhrase": case "HKWorkoutActivity": case "PKAutomaticReloadPaymentRequest": case "PKAutomaticReloadPaymentSummaryItem": case "PKPaymentOrderDetails": case "PKPaymentTokenContext": case "PKRecurringPaymentRequest": case "PKShareablePassMetadataPreview": return true; } break; case "NSSecureCoding": switch (type.Name) { case "NSMergeConflict": // undocumented // only documented to support NSCopying (and OSX side only does that) case "NSUrlSessionTask": case "NSUrlSessionDataTask": case "NSUrlSessionUploadTask": case "NSUrlSessionDownloadTask": case "NSUrlSessionConfiguration": // new in iOS8 and 10.0 case "NSExtensionContext": case "NSItemProvider": case "NSParagraphStyle": //17770106 case "NSMutableParagraphStyle": //17770106 return true; // skip // iOS9 / 10.11 case "CNSaveRequest": case "NSPersonNameComponentsFormatter": case "GKCloudPlayer": case "GKGameSession": // iOS 10 : test throw because of generic usage case "NSMeasurement`1": return true; // skip // xcode 9 case "NSConstraintConflict": // Conformance not in headers case "VSSubscription": // iOS 11.3 / macOS 10.13.4 case "NSEntityMapping": case "NSMappingModel": case "NSPropertyMapping": return true; case "MPSImageAllocator": // Header shows NSSecureCoding, but intro gives: MPSImageAllocator conforms to NSSecureCoding but SupportsSecureCoding returned false return true; // Xcode 10 case "ARDirectionalLightEstimate": case "ARFrame": case "ARLightEstimate": case "NSManagedObjectID": // beta 2 case "NSTextAttachment": case "VSAccountMetadata": case "VSAccountMetadataRequest": case "VSAccountProviderResponse": case "PHEditingExtensionContext": return true; // Xcode 11 (on device only) case "ICHandle": case "ICNotification": case "ICNotificationManagerConfiguration": case "MPSNNNeuronDescriptor": case "MPSNDArrayAllocator": // Conformance in header, but fails at runtime. case "MPSNNLossCallback": // Conformance in header, but fails at runtime. return true; // Xcode 11 case "NSFileProviderItemVersion": case "NSFileProviderRequest": case "NSFileProviderSearchQuery": case "INUserContext": // Header shows NSSecureCoding but intro on both device and simulator says nope FB7604793 return true; // Xcode 11.4, not documented case "NSHttpCookie": return true; // Xcode 12 case "ASAccountAuthenticationModificationExtensionContext": case "ASCredentialProviderExtensionContext": case "GCController": case "GCExtendedGamepad": case "GCMicroGamepad": case "GCMotion": case "SNClassification": case "SNClassificationResult": case "CPMessageListItem": // Conformance not in headers return true; // PassKit now available on macOS 11+ case "PKPayment": case "PKPaymentSummaryItem": case "PKShippingMethod": case "PKPaymentRequest": case "PKPaymentToken": case "PKLabeledValue": case "PKPaymentAuthorizationResult": case "PKPaymentRequestShippingMethodUpdate": case "PKPaymentRequestUpdate": case "PKPaymentRequestPaymentMethodUpdate": case "PKPaymentRequestShippingContactUpdate": case "PKSuicaPassProperties": // Conformance not in headers case "PKTransitPassProperties": // Conformance not in headers case "PKDisbursementRequest": case "PKDisbursementVoucher": case "PKAddCarKeyPassConfiguration": case "PKAddSecureElementPassConfiguration": case "PKAddShareablePassConfiguration": case "PKBarcodeEventConfigurationRequest": case "PKBarcodeEventMetadataRequest": case "PKBarcodeEventMetadataResponse": case "PKBarcodeEventSignatureRequest": case "PKBarcodeEventSignatureResponse": case "PKIssuerProvisioningExtensionPaymentPassEntry": case "PKIssuerProvisioningExtensionStatus": case "PKPaymentMerchantSession": case "PKPaymentRequestMerchantSessionUpdate": case "PKShareablePassMetadata": // Xcode 12.2 case "VSAccountApplicationProvider": // Conformance not in headers // Xcode 12.3 case "ARAppClipCodeAnchor": // Conformance comes from the base type, ARAppClipCodeAnchor conforms to NSSecureCoding but SupportsSecureCoding returned false. case "GCDirectionalGamepad": case "GCExtendedGamepadSnapshot": case "GCGamepadSnapshot": case "GCMicroGamepadSnapshot": case "GCGamepad": return true; // Xcode 12.5 case "GCDualSenseGamepad": // xcode 13 case "AVAssetDownloadConfiguration": case "AVAssetDownloadContentConfiguration": case "AVAssetVariant": case "AVAssetVariantQualifier": case "PKDeferredPaymentSummaryItem": case "PKPaymentRequestCouponCodeUpdate": case "PKRecurringPaymentSummaryItem": case "PKStoredValuePassBalance": case "PKStoredValuePassProperties": case "QLPreviewReply": // conformance not in headers case "QLPreviewReplyAttachment": // conformance not in headers case "SNTimeDurationConstraint": // Conformance not in headers case "NSInflectionRule": // Xcode 13.3 case "SRWristDetection": // Conformance not in headers case "HMAccessorySetupPayload": // Conformance not in headers case "HMAccessorySetupRequest": // Conformance not in headers case "HMAccessorySetupResult": // Conformance not in headers return true; // Xcode 14 case "CLKComplicationIntentWidgetMigrationConfiguration": case "CLKComplicationStaticWidgetMigrationConfiguration": case "CLKComplicationWidgetMigrationConfiguration": case "PHPickerConfiguration": case "PHAssetChangeRequest": case "PHAssetCreationRequest": case "NSUserActivity": case "UIDictationPhrase": case "HKWorkoutActivity": case "PKAutomaticReloadPaymentRequest": case "PKAutomaticReloadPaymentSummaryItem": case "PKPaymentOrderDetails": case "PKPaymentTokenContext": case "PKRecurringPaymentRequest": case "PKShareablePassMetadataPreview": return true; } break; // conformance added in Xcode 8 (iOS 10 / macOS 10.12) case "MDLNamed": switch (type.Name) { case "MTKMeshBuffer": case "MDLVoxelArray": // base class changed to MDLObject (was NSObject before) return true; } break; case "CALayerDelegate": switch (type.Name) { case "MTKView": return true; } break; case "NSProgressReporting": switch (type.Name) { case "NSOperationQueue": if (!TestRuntime.CheckXcodeVersion (11, 0)) return true; break; default: if (!TestRuntime.CheckXcodeVersion (9, 0)) return true; break; } break; case "GKSceneRootNodeType": // it's an empty protocol, defined by a category and does not reply as expected switch (type.Name) { // GameplayKit.framework/Headers/SceneKit+Additions.h case "SCNScene": // GameplayKit.framework/Headers/SpriteKit+Additions.h case "SKScene": return true; } break; case "UIUserActivityRestoring": if (!TestRuntime.CheckXcodeVersion (10, 0)) return true; switch (type.Name) { // UIKit.framework/Headers/UIDocument.h case "UIDocument": // inherits it from UIDocument case "UIManagedDocument": return true; } break; case "SCNTechniqueSupport": if (!TestRuntime.CheckXcodeVersion (11, 0)) return false; switch (type.Name) { case "SCNLight": return true; } break; case "VNRequestRevisionProviding": switch (type.Name) { case "VNFaceLandmarks": case "VNFaceLandmarks2D": case "VNFaceLandmarkRegion": case "VNFaceLandmarkRegion2D": if (!TestRuntime.CheckXcodeVersion (11, 0)) return true; break; case "VNRecognizedText": // Conformance added in Xcode 13 if (!TestRuntime.CheckXcodeVersion (13, 0)) return true; break; } break; case "NSUserActivityRestoring": return true; #if __MACCATALYST__ case "UIScrollViewDelegate": // The headers say PKCanvasViewDelegate implements UIScrollViewDelegate if (type.Name == "PKCanvasViewDelegate") return true; break; #endif case "NSExtensionRequestHandling": if (type.Name == "HMChipServiceRequestHandler") // Apple removed this class return true; break; case "QLPreviewItem": if (type.Name == "NSUrl") return true; break; } return false; } void CheckProtocol (string protocolName, Action action) { IntPtr protocol = Runtime.GetProtocol (protocolName); Assert.AreNotEqual (protocol, IntPtr.Zero, protocolName); int n = 0; foreach (Type t in Assembly.GetTypes ()) { if (!NSObjectType.IsAssignableFrom (t)) continue; if (Skip (t) || Skip (t, protocolName)) continue; if (LogProgress) Console.WriteLine ("{0}. {1} conforms to {2}", ++n, t.FullName, protocolName); IntPtr klass = GetClass (t); action (t, klass, ConformTo (klass, protocol)); } } [Test] public void Coding () { Errors = 0; var list = new List (); CheckProtocol ("NSCoding", delegate (Type type, IntPtr klass, bool result) { // `type` conforms to (native) NSCoding so... if (result) { // the type should implements INSCoding if (!typeof (INSCoding).IsAssignableFrom (type)) { list.Add (type.Name); ReportError ("{0} conforms to NSCoding but does not implement INSCoding", type.Name); } // FIXME: and implement the .ctor(NSCoder) } }); Assert.AreEqual (Errors, 0, "{0} types conforms to NSCoding but does not implement INSCoding: {1}", Errors, String.Join ('\n', list)); } // [Test] -> iOS 6.0+ and Mountain Lion (10.8) + public virtual void SecureCoding () { Errors = 0; var list = new List (); CheckProtocol ("NSSecureCoding", delegate (Type type, IntPtr klass, bool result) { if (result) { // the type should implements INSSecureCoding if (!typeof (INSSecureCoding).IsAssignableFrom (type)) { ReportError ("{0} conforms to NSSecureCoding but does not implement INSSecureCoding", type.Name); } } }); Assert.AreEqual (Errors, 0, "{0} types conforms to NSSecureCoding but does not implement INSSecureCoding: {1}", Errors, String.Join ('\n', list)); } bool SupportsSecureCoding (Type type) { Class cls = new Class (type); if (!bool_objc_msgSend_IntPtr (cls.Handle, Selector.GetHandle ("respondsToSelector:"), Selector.GetHandle ("supportsSecureCoding"))) return false; return NSSecureCoding.SupportsSecureCoding (type); } // [Test] -> iOS 6.0+ and Mountain Lion (10.8) + public virtual void SupportsSecureCoding () { Errors = 0; CheckProtocol ("NSSecureCoding", delegate (Type type, IntPtr klass, bool result) { bool supports = SupportsSecureCoding (type); if (result) { // check that +supportsSecureCoding returns YES if (!supports) { #if __IOS__ // broken in xcode 12 beta 1 simulator (only) if (TestRuntime.IsSimulatorOrDesktop && TestRuntime.CheckXcodeVersion (12, 0)) { switch (type.Name) { case "ARFaceGeometry": case "ARPlaneGeometry": case "ARPointCloud": case "ARAnchor": case "ARBodyAnchor": case "AREnvironmentProbeAnchor": case "ARFaceAnchor": case "ARGeoAnchor": case "ARGeometryElement": case "ARGeometrySource": case "ARImageAnchor": case "ARMeshAnchor": case "ARMeshGeometry": case "ARObjectAnchor": case "ARParticipantAnchor": case "ARPlaneAnchor": case "ARReferenceObject": case "ARSkeletonDefinition": // iOS15 / device only case "ARWorldMap": return; } } #endif ReportError ("{0} conforms to NSSecureCoding but SupportsSecureCoding returned false", type.Name); } } else if (type.IsPublic && supports) { // there are internal types, e.g. DataWrapper : NSData, that subclass NSSecureCoding-types without // [re-]declaring their allegiance - but we can live with those small betrayals Assert.IsFalse (NSSecureCoding.SupportsSecureCoding (type), "{0} !SupportsSecureCoding", type.Name); ReportError ("SupportsSecureCoding returns true but {0} does not conforms to NSSecureCoding", type.Name); } }); Assert.AreEqual (Errors, 0, "{0} types conforms to NSCoding but does not implement INSSecureCoding", Errors); } [Test] public void Copying () { Errors = 0; var list = new List (); CheckProtocol ("NSCopying", delegate (Type type, IntPtr klass, bool result) { // `type` conforms to (native) NSCopying so... if (result) { // the type should implements INSCopying if (!typeof (INSCopying).IsAssignableFrom (type)) { list.Add (type.Name); ReportError ("{0} conforms to NSCopying but does not implement INSCopying", type.Name); } } }); Assert.AreEqual (Errors, 0, "{0} types conforms to NSCopying but does not implement INSCopying: {1}", Errors, String.Join ('\n', list)); } [Test] public void MutableCopying () { Errors = 0; var list = new List (); CheckProtocol ("NSMutableCopying", delegate (Type type, IntPtr klass, bool result) { // `type` conforms to (native) NSMutableCopying so... if (result) { // the type should implements INSMutableCopying if (!typeof (INSMutableCopying).IsAssignableFrom (type)) { list.Add (type.Name); ReportError ("{0} conforms to NSMutableCopying but does not implement INSMutableCopying", type.Name); } } }); Assert.AreEqual (Errors, 0, "{0} types conforms to NSMutableCopying but does not implement INSMutableCopying: {1}", Errors, String.Join ('\n', list)); } [Test] public void GeneralCase () { Errors = 0; var list = new List (); foreach (Type t in Assembly.GetTypes ()) { if (!NSObjectType.IsAssignableFrom (t)) continue; if (Skip (t)) continue; var klass = new Class (t); if (klass.Handle == IntPtr.Zero) { // This can often by caused by [Protocol] classes with no [Model] but having a [BaseType]. // Either have both a Model and BaseType or neither switch (t.Name) { case "AVPlayerInterstitialEventMonitor": // deprecated continue; #if !MONOMAC case "MTLCaptureManager": case "NEHotspotConfiguration": case "NEHotspotConfigurationManager": case "NEHotspotEapSettings": case "NEHotspotHS20Settings": case "SCNGeometryTessellator": case "SKRenderer": // was not possible in iOS 11.4 (current minimum) simulator if (!TestRuntime.CheckXcodeVersion (12, 0)) { if (TestRuntime.IsSimulatorOrDesktop) continue; } break; #endif default: var e = $"[FAIL] Could not load {t.FullName}"; list.Add (e); AddErrorLine (e); continue; } } foreach (var intf in t.GetInterfaces ()) { if (SkipDueToAttribute (intf)) continue; string protocolName = intf.Name.Substring (1); switch (protocolName) { case "NSCoding": case "NSSecureCoding": case "NSCopying": case "NSMutableCopying": // we have special test cases for them continue; default: if (Skip (t, protocolName)) continue; break; } var a = intf.GetCustomAttribute (true); if (a == null || a.IsInformal) continue; IntPtr protocol = Runtime.GetProtocol (protocolName); if (protocol == IntPtr.Zero) continue; // not a protocol if (LogProgress) Console.WriteLine ("{0} conforms to {1}", t.FullName, protocolName); if (t.IsPublic && !ConformTo (klass.Handle, protocol)) { // note: some internal types, e.g. like UIAppearance subclasses, return false (and there's not much value in changing this) var msg = $"Type {t.FullName} (native: {klass.Name}) does not conform {protocolName}"; list.Add (msg); ReportError (msg); } } } AssertIfErrors ("{0} types do not really conform to the protocol interfaces: {1}", Errors, String.Join ('\n', list)); } } }