Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove dependencies #2127

Merged
merged 7 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ System.CommandLine
public System.Void ThrowIfInvalid()
public class CommandLineConfigurationException : System.Exception, System.Runtime.Serialization.ISerializable
.ctor(System.String message)
.ctor()
.ctor(System.String message, System.Exception innerException)
public static class CompletionSourceExtensions
public static System.Void Add(this System.Collections.Generic.List<System.Func<System.CommandLine.Completions.CompletionContext,System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem>>> completionSources, System.Func<System.CommandLine.Completions.CompletionContext,System.Collections.Generic.IEnumerable<System.String>> completionsDelegate)
public static System.Void Add(this System.Collections.Generic.List<System.Func<System.CommandLine.Completions.CompletionContext,System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem>>> completionSources, System.String[] completions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ internal static bool CalculateAllowsNull(ParameterInfo parameterInfo)
/// <inheritdoc />
public object? GetDefaultValue() =>
_parameterInfo.DefaultValue is DBNull
? ArgumentConverter.GetDefaultValue(ValueType)
? ValueType.GetDefaultValue()
: _parameterInfo.DefaultValue;

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal PropertyDescriptor(
public bool HasDefaultValue => false;

/// <inheritdoc />
public object? GetDefaultValue() => ArgumentConverter.GetDefaultValue(ValueType);
public object? GetDefaultValue() => ValueType.GetDefaultValue();

/// <summary>
/// Sets a value on the target property.
Expand Down
27 changes: 27 additions & 0 deletions src/System.CommandLine.NamingConventionBinder/TypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections;
using System.CommandLine.Binding;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;

namespace System.CommandLine.NamingConventionBinder;

Expand All @@ -28,4 +31,28 @@ public static bool IsConstructedGenericTypeOf(this Type type, Type genericTypeDe
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNullableValueType(this Type type)
=> type.IsValueType && type.IsConstructedGenericTypeOf(typeof(Nullable<>));

internal static object? GetDefaultValue(this Type type)
{
if (type.IsNullable())
{
return null;
}

if (type.GetElementTypeIfEnumerable() is { } itemType)
{
return ArgumentConverter.CreateEnumerable(type, itemType);
}

return type switch
{
{ } nonGeneric
when nonGeneric == typeof(IList) ||
nonGeneric == typeof(ICollection) ||
nonGeneric == typeof(IEnumerable)
=> Array.Empty<object>(),
_ when type.IsValueType => FormatterServices.GetUninitializedObject(type),
_ => null
};
}
}
36 changes: 27 additions & 9 deletions src/System.CommandLine.Tests/Binding/TypeConversionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -531,8 +531,15 @@ public void Values_can_be_correctly_converted_to_nullable_TimeSpan_without_the_p
}

[Fact]
public void Values_can_be_correctly_converted_to_Uri_without_the_parser_specifying_a_custom_converter()
=> GetValue(new Option<Uri>("-x"), "-x http://example.com").Should().BeEquivalentTo(new Uri("http://example.com"));
public void Values_can_be_correctly_converted_to_Uri_when_custom_parser_is_provided()
{
Option<Uri> option = new ("-x")
{
CustomParser = (argumentResult) => Uri.TryCreate(argumentResult.Tokens.Last().Value, UriKind.RelativeOrAbsolute, out var uri) ? uri : null
};

GetValue(option, "-x http://example.com").Should().BeEquivalentTo(new Uri("http://example.com"));
}

[Fact]
public void Options_with_arguments_specified_can_be_correctly_converted_to_bool_without_the_parser_specifying_a_custom_converter()
Expand Down Expand Up @@ -582,13 +589,27 @@ public void Values_can_be_correctly_converted_to_nullable_sbyte_without_the_pars
=> GetValue(new Option<sbyte?>("-us"), "-us 123").Should().Be(123);

[Fact]
public void Values_can_be_correctly_converted_to_ipaddress_without_the_parser_specifying_a_custom_converter()
=> GetValue(new Option<IPAddress>("-us"), "-us 1.2.3.4").Should().Be(IPAddress.Parse("1.2.3.4"));
public void Values_can_be_correctly_converted_to_ipaddress_when_custom_parser_is_provided()
{
Option<IPAddress> option = new ("-us")
{
CustomParser = (argumentResult) => IPAddress.Parse(argumentResult.Tokens.Last().Value)
};

GetValue(option, "-us 1.2.3.4").Should().Be(IPAddress.Parse("1.2.3.4"));
}

#if NETCOREAPP3_0_OR_GREATER
[Fact]
public void Values_can_be_correctly_converted_to_ipendpoint_without_the_parser_specifying_a_custom_converter()
=> GetValue(new Option<IPEndPoint>("-us"), "-us 1.2.3.4:56").Should().Be(IPEndPoint.Parse("1.2.3.4:56"));
public void Values_can_be_correctly_converted_to_ipendpoint_when_custom_parser_is_provided()
{
Option<IPEndPoint> option = new("-us")
{
CustomParser = (argumentResult) => IPEndPoint.Parse(argumentResult.Tokens.Last().Value)
};

GetValue(option, "-us 1.2.3.4:56").Should().Be(IPEndPoint.Parse("1.2.3.4:56"));
}
#endif

#if NET6_0_OR_GREATER
Expand Down Expand Up @@ -756,9 +777,6 @@ public void String_defaults_to_null_when_not_specified_only_for_not_required_arg
[InlineData(typeof(string[]))]
[InlineData(typeof(int[]))]
[InlineData(typeof(FileAccess[]))]
[InlineData(typeof(IEnumerable))]
[InlineData(typeof(ICollection))]
[InlineData(typeof(IList))]
public void Sequence_type_defaults_to_empty_when_not_specified(Type sequenceType)
{
var argument = Activator.CreateInstance(typeof(Argument<>).MakeGenericType(sequenceType), new object[] { "argName" });
Expand Down
37 changes: 33 additions & 4 deletions src/System.CommandLine/Argument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,40 @@ internal TryConvertArgument? ConvertArguments
/// <summary>
/// Gets the list of completion sources for the argument.
/// </summary>
public List<Func<CompletionContext, IEnumerable<CompletionItem>>> CompletionSources =>
_completionSources ??= new ()
public List<Func<CompletionContext, IEnumerable<CompletionItem>>> CompletionSources
{
get
{
CompletionSource.ForType(ValueType)
};
if (_completionSources is null)
{
Type? valueType = ValueType;
if (valueType == typeof(bool) || valueType == typeof(bool?))
{
_completionSources = new ()
{
static _ => new CompletionItem[]
{
new(bool.TrueString),
new(bool.FalseString)
}
};
}
else if (!valueType.IsPrimitive && (valueType.IsEnum || (valueType.TryGetNullableType(out valueType) && valueType.IsEnum)))
{
_completionSources = new()
{
_ => Enum.GetNames(valueType).Select(n => new CompletionItem(n))
};
}
else
{
_completionSources = new();
}
}

return _completionSources;
}
}

/// <summary>
/// Gets or sets the <see cref="Type" /> that the argument token(s) will be converted to.
Expand Down
33 changes: 33 additions & 0 deletions src/System.CommandLine/Argument{T}.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.CommandLine.Parsing;
using System.Diagnostics.CodeAnalysis;
using System.IO;

namespace System.CommandLine
Expand Down Expand Up @@ -160,5 +162,36 @@ public void AcceptLegalFileNamesOnly()
}
});
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050", Justification = "https://github.com/dotnet/command-line-api/issues/1638")]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2091", Justification = "https://github.com/dotnet/command-line-api/issues/1638")]
Comment on lines +166 to +167
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why the referenced issue is justification for suppressing these warnings. It might be better if we wrote it out here.

I believe you could fix IL2091 by annotating the T with [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]. See https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/fixing-warnings#dynamicallyaccessedmembers

internal static T? CreateDefaultValue()
{
if (default(T) is null && typeof(T) != typeof(string))
{
if (typeof(T).IsArray)
{
return (T?)(object)Array.CreateInstance(typeof(T).GetElementType()!, 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to use dotnet/runtime#76478 once it is implemented in .NET 8. That will allow us to do this without a warning.

}
adamsitnik marked this conversation as resolved.
Show resolved Hide resolved
else if (typeof(T).IsGenericType)
adamsitnik marked this conversation as resolved.
Show resolved Hide resolved
{
var genericTypeDefinition = typeof(T).GetGenericTypeDefinition();

if (genericTypeDefinition == typeof(IEnumerable<>) ||
genericTypeDefinition == typeof(IList<>) ||
genericTypeDefinition == typeof(ICollection<>))
{
return (T?)(object)Array.CreateInstance(typeof(T).GenericTypeArguments[0], 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't guaranteed to work in a NativeAOT app. Just because the app uses a IList<double>, it doesn't mean that double[]'s code is going to get preserved/generated.

}

if (genericTypeDefinition == typeof(List<>))
{
return Activator.CreateInstance<T>();
}
}
}

return default;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.Serialization;

namespace System.CommandLine.Binding;

Expand Down Expand Up @@ -37,7 +36,7 @@ private static IList CreateEmptyList(Type listType)
return (IList)ctor.Invoke(null);
}

private static IList CreateEnumerable(Type type, Type itemType, int capacity = 0)
internal static IList CreateEnumerable(Type type, Type itemType, int capacity = 0)
{
if (type.IsArray)
{
Expand All @@ -63,9 +62,4 @@ private static IList CreateEnumerable(Type type, Type itemType, int capacity = 0

throw new ArgumentException($"Type {type} cannot be created without a custom binder.");
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern",
Justification = $"{nameof(CreateDefaultValueType)} is only called on a ValueType. You can always create an instance of a ValueType.")]
private static object CreateDefaultValueType(Type type) =>
FormatterServices.GetUninitializedObject(type);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@

using System.Collections.Generic;
using System.IO;
using System.Net;

namespace System.CommandLine.Binding;

internal static partial class ArgumentConverter
{
private delegate bool TryConvertString(string token, out object? value);

private static readonly Dictionary<Type, TryConvertString> _stringConverters = new()
private static Dictionary<Type, TryConvertString>? _stringConverters;

private static Dictionary<Type, TryConvertString> StringConverters
=> _stringConverters ??= new()
{
[typeof(bool)] = (string token, out object? value) =>
{
Expand Down Expand Up @@ -169,32 +171,6 @@ internal static partial class ArgumentConverter
return false;
},

[typeof(IPAddress)] = (string token, out object? value) =>
{
if (IPAddress.TryParse(token, out var ip))
{
value = ip;
return true;
}

value = default;
return false;
},

#if NETCOREAPP3_0_OR_GREATER
[typeof(IPEndPoint)] = (string token, out object? value) =>
{
if (IPEndPoint.TryParse(token, out var ipendpoint))
{
value = ipendpoint;
return true;
}

value = default;
return false;
},
#endif

[typeof(long)] = (string token, out object? value) =>
{
if (long.TryParse(token, out var longValue))
Expand Down Expand Up @@ -299,18 +275,6 @@ internal static partial class ArgumentConverter
return false;
},

[typeof(Uri)] = (string input, out object? value) =>
{
if (Uri.TryCreate(input, UriKind.RelativeOrAbsolute, out var uri))
{
value = uri;
return true;
}

value = default;
return false;
},

[typeof(TimeSpan)] = (string input, out object? value) =>
{
if (TimeSpan.TryParse(input, out var timeSpan))
Expand Down
31 changes: 3 additions & 28 deletions src/System.CommandLine/Binding/ArgumentConverter.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections;
using System.Collections.Generic;
using System.CommandLine.Parsing;
using static System.CommandLine.Binding.ArgumentConversionResult;
Expand Down Expand Up @@ -40,7 +39,7 @@ private static ArgumentConversionResult ConvertToken(
return ConvertToken(argumentResult, nullableType, token);
}

if (_stringConverters.TryGetValue(type, out var tryConvert))
if (StringConverters.TryGetValue(type, out var tryConvert))
{
if (tryConvert(value, out var converted))
{
Expand Down Expand Up @@ -123,12 +122,12 @@ private static ArgumentConversionResult ConvertTokens(
if (argument.Arity is { MaximumNumberOfValues: 1, MinimumNumberOfValues: 1 })
{
if (argument.ValueType.TryGetNullableType(out var nullableType) &&
_stringConverters.TryGetValue(nullableType, out var convertNullable))
StringConverters.TryGetValue(nullableType, out var convertNullable))
{
return (ArgumentResult result, out object? value) => ConvertSingleString(result, convertNullable, out value);
}

if (_stringConverters.TryGetValue(argument.ValueType, out var convert1))
if (StringConverters.TryGetValue(argument.ValueType, out var convert1))
{
return (ArgumentResult result, out object? value) => ConvertSingleString(result, convert1, out value);
}
Expand Down Expand Up @@ -224,29 +223,5 @@ public static bool TryConvertArgument(ArgumentResult argumentResult, out object?
value = result;
return result.Result == ArgumentConversionResultType.Successful;
}

internal static object? GetDefaultValue(Type type)
{
if (type.IsNullable())
{
return null;
}

if (type.GetElementTypeIfEnumerable() is { } itemType)
{
return CreateEnumerable(type, itemType);
}

return type switch
{
{ } nonGeneric
when nonGeneric == typeof(IList) ||
nonGeneric == typeof(ICollection) ||
nonGeneric == typeof(IEnumerable)
=> Array.Empty<object>(),
_ when type.IsValueType => CreateDefaultValueType(type),
_ => null
};
}
}
}
Loading