diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 94dd82a..8c13831 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -9,6 +9,7 @@ + diff --git a/src/Spectre.Console/Internal/TypeConverterHelper.cs b/src/Spectre.Console/Internal/TypeConverterHelper.cs index 03c393e..6828c7c 100644 --- a/src/Spectre.Console/Internal/TypeConverterHelper.cs +++ b/src/Spectre.Console/Internal/TypeConverterHelper.cs @@ -2,6 +2,11 @@ namespace Spectre.Console; internal static class TypeConverterHelper { + internal const DynamicallyAccessedMemberTypes ConverterAnnotation = DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields; + + internal static bool IsGetConverterSupported => + !AppContext.TryGetSwitch("Spectre.Console.TypeConverterHelper.IsGetConverterSupported ", out var enabled) || enabled; + public static string ConvertToString(T input) { var result = GetTypeConverter().ConvertToInvariantString(input); @@ -51,7 +56,7 @@ internal static class TypeConverterHelper public static TypeConverter GetTypeConverter() { - var converter = TypeDescriptor.GetConverter(typeof(T)); + var converter = GetConverter(); if (converter != null) { return converter; @@ -72,5 +77,91 @@ internal static class TypeConverterHelper } throw new InvalidOperationException("Could not find type converter"); + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2087", Justification = "Feature switches are not currently supported in the analyzer")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = "Feature switches are not currently supported in the analyzer")] + static TypeConverter? GetConverter() + { + if (!IsGetConverterSupported) + { + return GetIntrinsicConverter(typeof(T)); + } + + return TypeDescriptor.GetConverter(typeof(T)); + } + } + + private delegate TypeConverter FuncWithDam([DynamicallyAccessedMembers(ConverterAnnotation)] Type type); + + private static readonly Dictionary _intrinsicConverters; + + static TypeConverterHelper() + { + _intrinsicConverters = new() + { + [typeof(bool)] = _ => new BooleanConverter(), + [typeof(byte)] = _ => new ByteConverter(), + [typeof(sbyte)] = _ => new SByteConverter(), + [typeof(char)] = _ => new CharConverter(), + [typeof(double)] = _ => new DoubleConverter(), + [typeof(string)] = _ => new StringConverter(), + [typeof(int)] = _ => new Int32Converter(), + [typeof(short)] = _ => new Int16Converter(), + [typeof(long)] = _ => new Int64Converter(), + [typeof(float)] = _ => new SingleConverter(), + [typeof(ushort)] = _ => new UInt16Converter(), + [typeof(uint)] = _ => new UInt32Converter(), + [typeof(ulong)] = _ => new UInt64Converter(), + [typeof(object)] = _ => new TypeConverter(), + [typeof(CultureInfo)] = _ => new CultureInfoConverter(), + [typeof(DateTime)] = _ => new DateTimeConverter(), + [typeof(DateTimeOffset)] = _ => new DateTimeOffsetConverter(), + [typeof(decimal)] = _ => new DecimalConverter(), + [typeof(TimeSpan)] = _ => new TimeSpanConverter(), + [typeof(Guid)] = _ => new GuidConverter(), + [typeof(Uri)] = _ => new UriTypeConverter(), + [typeof(Array)] = _ => new ArrayConverter(), + [typeof(ICollection)] = _ => new CollectionConverter(), + [typeof(Enum)] = CreateEnumConverter(), + #if !NETSTANDARD2_0 + [typeof(Int128)] = _ => new Int128Converter(), + [typeof(Half)] = _ => new HalfConverter(), + [typeof(UInt128)] = _ => new UInt128Converter(), + [typeof(DateOnly)] = _ => new DateOnlyConverter(), + [typeof(TimeOnly)] = _ => new TimeOnlyConverter(), + [typeof(Version)] = _ => new VersionConverter(), + #endif + }; + } + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111", Justification = "Delegate reflection is safe for all usages in this type.")] + private static FuncWithDam CreateEnumConverter() => ([DynamicallyAccessedMembers(ConverterAnnotation)] Type type) => new EnumConverter(type); + + /// + /// A highly-constrained version of that only returns intrinsic converters. + /// + private static TypeConverter? GetIntrinsicConverter([DynamicallyAccessedMembers(ConverterAnnotation)] Type type) + { + if (type.IsArray) + { + type = typeof(Array); + } + + if (typeof(ICollection).IsAssignableFrom(type)) + { + type = typeof(ICollection); + } + + if (type.IsEnum) + { + type = typeof(Enum); + } + + if (_intrinsicConverters.TryGetValue(type, out var factory)) + { + return factory(type); + } + + return null; } } \ No newline at end of file diff --git a/src/Spectre.Console/Spectre.Console.csproj b/src/Spectre.Console/Spectre.Console.csproj index 27bcc02..2c00fd5 100644 --- a/src/Spectre.Console/Spectre.Console.csproj +++ b/src/Spectre.Console/Spectre.Console.csproj @@ -23,7 +23,7 @@ all - + all runtime; build; native; contentfiles; analyzers diff --git a/src/Spectre.Console/Widgets/Exceptions/ExceptionFormatter.cs b/src/Spectre.Console/Widgets/Exceptions/ExceptionFormatter.cs index a7e530c..383e619 100644 --- a/src/Spectre.Console/Widgets/Exceptions/ExceptionFormatter.cs +++ b/src/Spectre.Console/Widgets/Exceptions/ExceptionFormatter.cs @@ -1,12 +1,15 @@ namespace Spectre.Console; -#pragma warning disable IL2026,IL2070,IL2075,IL3050 - +// ExceptionFormatter relies heavily on reflection of types unknown until runtime. +// We'll suppress these warnings, but alert the user this method is not supported. +[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode")] +[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2070:RequiresUnreferencedCode")] +[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2075:RequiresUnreferencedCode")] +[RequiresDynamicCode(AotWarning)] internal static class ExceptionFormatter { public const string AotWarning = "ExceptionFormatter is currently not supported for AOT."; - [RequiresDynamicCode(ExceptionFormatter.AotWarning)] public static IRenderable Format(Exception exception, ExceptionSettings settings) { if (exception is null)