public void GetProxyType_IfAlreadyInCache_AlsoAddedToVisited_FromError() { // Arrange var targetType = typeof(IPerson); var sourceType = typeof(Person); var key = new Tuple <Type, Type>(sourceType, targetType); var cache = new ProxyTypeCache(); cache[key] = ProxyTypeCacheResult.FromError(key, "Test Error"); var context = new ProxyTypeEmitter.ProxyBuilderContext(cache, targetType, sourceType); // Act var result = ProxyTypeEmitter.VerifyProxySupport(context, key); // Assert Assert.False(result); Assert.Equal(key, context.Visited.Single().Key); }
private static bool VerifyProxySupport(ProxyBuilderContext context, Tuple <Type, Type> key) { var sourceType = key.Item1; var targetType = key.Item2; if (context.Visited.ContainsKey(key)) { // We've already seen this combination and so far so good. return(true); } ProxyTypeCacheResult cacheResult; if (context.Cache.TryGetValue(key, out cacheResult)) { // If we get here we've got a published conversion or error, so we can stop searching. return(!cacheResult.IsError); } if (targetType == sourceType || targetType.GetTypeInfo().IsAssignableFrom(sourceType.GetTypeInfo())) { // If we find a trivial conversion, then that will work. return(true); } if (!targetType.GetTypeInfo().IsInterface) { var message = Resources.FormatConverter_TypeMustBeInterface(targetType.FullName, sourceType.FullName); context.Cache[key] = ProxyTypeCacheResult.FromError(key, message); return(false); } // This is a combination we haven't seen before, and it *might* support proxy generation, so let's // start trying. var verificationResult = new VerificationResult(); context.Visited.Add(key, verificationResult); // We support conversions from IList<T> -> IReadOnlyList<U> and IReadOnlyList<T> -> IReadOnlyList<U> if (targetType.GetTypeInfo().IsGenericType&& targetType.GetTypeInfo().GetGenericTypeDefinition() == typeof(IReadOnlyList <>)) { var sourceInterfaceType = GetGenericImplementation(sourceType, typeof(IList <>)); if (sourceInterfaceType != null) { var targetElementType = targetType.GetTypeInfo().GenericTypeArguments[0]; var sourceElementType = sourceInterfaceType.GetTypeInfo().GenericTypeArguments[0]; var elementKey = new Tuple <Type, Type>(sourceElementType, targetElementType); if (!VerifyProxySupport(context, elementKey)) { var error = context.Cache[elementKey]; Debug.Assert(error != null && error.IsError); context.Cache[key] = error; return(false); } VerificationResult elementResult; context.Visited.TryGetValue(elementKey, out elementResult); var proxyType = elementResult?.Type?.GetTypeInfo() ?? (TypeInfo)elementResult?.TypeBuilder; if (proxyType == null) { // No proxy needed for elements. verificationResult.Type = typeof(ProxyList <,>).MakeGenericType(elementKey.Item1, elementKey.Item2); verificationResult.Constructor = verificationResult.Type.GetTypeInfo().DeclaredConstructors.First(); } else { // We need to proxy each of the elements. Let's generate a type. GenerateProxyTypeForList(elementKey.Item1, elementKey.Item2, proxyType.AsType(), verificationResult); } return(true); } } // This doesn't match any of our interface conversions, so we'll codegen a proxy. var propertyMappings = new List <KeyValuePair <PropertyInfo, PropertyInfo> >(); var sourceProperties = sourceType.GetRuntimeProperties(); var targetProperties = targetType .GetTypeInfo() .ImplementedInterfaces .SelectMany(i => i.GetRuntimeProperties()) .Concat(targetType.GetRuntimeProperties()); foreach (var targetProperty in targetProperties) { if (!targetProperty.CanRead) { var message = Resources.FormatConverter_PropertyMustHaveGetter( targetProperty.Name, targetType.FullName); context.Cache[key] = ProxyTypeCacheResult.FromError(key, message); return(false); } if (targetProperty.CanWrite) { var message = Resources.FormatConverter_PropertyMustNotHaveSetter( targetProperty.Name, targetType.FullName); context.Cache[key] = ProxyTypeCacheResult.FromError(key, message); return(false); } if (targetProperty.GetIndexParameters()?.Length > 0) { var message = Resources.FormatConverter_PropertyMustNotHaveIndexParameters( targetProperty.Name, targetType.FullName); context.Cache[key] = ProxyTypeCacheResult.FromError(key, message); return(false); } // To allow for flexible versioning, we want to allow missing properties in the source. // // For now we'll just store null, and later generate a stub getter that returns default(T). var sourceProperty = sourceProperties.Where(p => p.Name == targetProperty.Name).FirstOrDefault(); if (sourceProperty != null && sourceProperty.CanRead && sourceProperty.GetMethod?.IsPublic == true) { var propertyKey = new Tuple <Type, Type>(sourceProperty.PropertyType, targetProperty.PropertyType); if (!VerifyProxySupport(context, propertyKey)) { // There's an error here, so bubble it up and cache it. var error = context.Cache[propertyKey]; Debug.Assert(error != null && error.IsError); context.Cache[key] = ProxyTypeCacheResult.FromError(key, error.Error); return(false); } propertyMappings.Add(new KeyValuePair <PropertyInfo, PropertyInfo>(targetProperty, sourceProperty)); } else { propertyMappings.Add(new KeyValuePair <PropertyInfo, PropertyInfo>(targetProperty, null)); } } verificationResult.Mappings = propertyMappings; GenerateProxyTypeFromProperties(sourceType, targetType, verificationResult); return(true); }