Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/CommandLine/CommandLine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<AssemblyName>CommandLine</AssemblyName>
<OutputType>Library</OutputType>
<TargetFrameworks>netstandard2.0;net40;net45;net461</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net40;net45;net461;net5.0</TargetFrameworks>
<DefineConstants>$(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC;CSX_REM_CRYPTORAND;ERRH_ADD_MAYBE_METHODS</DefineConstants>
<DefineConstants Condition="'$(BuildTarget)' != 'fsharp'">$(DefineConstants);SKIP_FSHARP</DefineConstants>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Expand Down
39 changes: 30 additions & 9 deletions src/CommandLine/Core/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ public static Maybe<Tuple<PropertyInfo, UsageAttribute>> GetUsageData(this Type
{
return
(from pi in type.FlattenHierarchy().SelectMany(x => x.GetTypeInfo().GetProperties())
let attrs = pi.GetCustomAttributes(typeof(UsageAttribute), true)
where attrs.Any()
select Tuple.Create(pi, (UsageAttribute)attrs.First()))
let attrs = pi.GetCustomAttributes(typeof(UsageAttribute), true)
where attrs.Any()
select Tuple.Create(pi, (UsageAttribute)attrs.First()))
.SingleOrDefault()
.ToMaybe();
}
Expand Down Expand Up @@ -125,16 +125,16 @@ public static object GetDefaultValue(this Type type)

public static bool IsMutable(this Type type)
{
if(type == typeof(object))
if (type == typeof(object))
return true;

// Find all inherited defined properties and fields on the type
var inheritedTypes = type.GetTypeInfo().FlattenHierarchy().Select(i => i.GetTypeInfo());

foreach (var inheritedType in inheritedTypes)
foreach (var inheritedType in inheritedTypes)
{
if (
inheritedType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite) ||
inheritedType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.IsMutable()) ||
inheritedType.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any()
)
{
Expand Down Expand Up @@ -162,7 +162,7 @@ public static object AutoDefault(this Type type)
}

var ctorTypes = type.GetSpecifications(pi => pi.PropertyType).ToArray();

return ReflectionHelper.CreateDefaultImmutableInstance(type, ctorTypes);
}

Expand Down Expand Up @@ -206,7 +206,7 @@ public static bool IsPrimitiveEx(this Type type)
return
(type.GetTypeInfo().IsValueType && type != typeof(Guid))
|| type.GetTypeInfo().IsPrimitive
|| new [] {
|| new[] {
typeof(string)
,typeof(decimal)
,typeof(DateTime)
Expand All @@ -218,10 +218,31 @@ public static bool IsPrimitiveEx(this Type type)

public static bool IsCustomStruct(this Type type)
{
var isStruct = type.GetTypeInfo().IsValueType && !type.GetTypeInfo().IsPrimitive && !type.GetTypeInfo().IsEnum && type != typeof(Guid);
var isStruct = type.GetTypeInfo().IsValueType && !type.GetTypeInfo().IsPrimitive && !type.GetTypeInfo().IsEnum && type != typeof(Guid);
if (!isStruct) return false;
var ctor = type.GetTypeInfo().GetConstructor(new[] { typeof(string) });
return ctor != null;
}

#if NET5_0_OR_GREATER
public static bool IsInitOnly(this PropertyInfo propertyInfo)
{
var setMethod = propertyInfo.SetMethod;

if (setMethod == null)
return false;

var isExternalInitType = typeof(System.Runtime.CompilerServices.IsExternalInit);

return setMethod.ReturnParameter.GetRequiredCustomModifiers().Contains(isExternalInitType);
}
#endif

public static bool IsMutable(this PropertyInfo propertyInfo) =>
#if NET5_0_OR_GREATER
propertyInfo.CanWrite && propertyInfo.GetSetMethod(false) != null && !propertyInfo.IsInitOnly();
#else
propertyInfo.CanWrite && propertyInfo.GetSetMethod(false) != null;
#endif
}
}
1 change: 1 addition & 0 deletions src/CommandLine/Core/TokenPartitioner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using CommandLine.Infrastructure;
using CSharpx;
using ReferenceEqualityComparer = CommandLine.Infrastructure.ReferenceEqualityComparer;

namespace CommandLine.Core
{
Expand Down
4 changes: 2 additions & 2 deletions tests/CommandLine.Tests/CommandLine.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFrameworks>net461;netcoreapp3.1</TargetFrameworks>
<TargetFrameworks>net461;netcoreapp3.1;net5.0</TargetFrameworks>
<DefineConstants Condition="'$(BuildTarget)' != 'fsharp'">$(DefineConstants);SKIP_FSHARP</DefineConstants>
<AssemblyOriginatorKeyFile>..\..\CommandLine.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
Expand Down Expand Up @@ -35,4 +35,4 @@
</ItemGroup>


</Project>
</Project>
87 changes: 86 additions & 1 deletion tests/CommandLine.Tests/Fakes/Immutable_Simple_Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public Immutable_Simple_Options(string stringValue, IEnumerable<int> intSequence
[Value(0)]
public long LongValue { get { return longValue; } }
}

public class Immutable_Simple_Options_Invalid_Ctor_Args
{
private readonly string stringValue;
Expand Down Expand Up @@ -59,4 +59,89 @@ public Immutable_Simple_Options_Invalid_Ctor_Args(string stringValue1, IEnumerab
[Value(0)]
public long LongValue { get { return longValue; } }
}

public class Immutable_Simple_Options_Read_Only
{
public Immutable_Simple_Options_Read_Only(string stringValue, IEnumerable<int> intSequence, bool boolValue, long longValue)
{
StringValue = stringValue;
IntSequence = intSequence;
BoolValue = boolValue;
LongValue = longValue;
}

[Option(HelpText = "Define a string value here.")]
public string StringValue { get; }

[Option('i', Min = 3, Max = 4, HelpText = "Define a int sequence here.")]
public IEnumerable<int> IntSequence { get; }

[Option('x', HelpText = "Define a boolean or switch value here.")]
public bool BoolValue { get; }

[Value(0)]
public long LongValue { get; }
}

public class Immutable_Simple_Options_Private_Set
{
public Immutable_Simple_Options_Private_Set(string stringValue, IEnumerable<int> intSequence, bool boolValue, long longValue)
{
StringValue = stringValue;
IntSequence = intSequence;
BoolValue = boolValue;
LongValue = longValue;
}

[Option(HelpText = "Define a string value here.")]
public string StringValue { get; private set; }

[Option('i', Min = 3, Max = 4, HelpText = "Define a int sequence here.")]
public IEnumerable<int> IntSequence { get; private set; }

[Option('x', HelpText = "Define a boolean or switch value here.")]
public bool BoolValue { get; private set; }

[Value(0)]
public long LongValue { get; private set; }
}

#if NET5_0_OR_GREATER
public class Immutable_Simple_Options_Init
{
public Immutable_Simple_Options_Init(string stringValue, IEnumerable<int> intSequence, bool boolValue, long longValue)
{
StringValue = stringValue;
IntSequence = intSequence;
BoolValue = boolValue;
LongValue = longValue;
}

[Option(HelpText = "Define a string value here.")]
public string StringValue { get; init; }

[Option('i', Min = 3, Max = 4, HelpText = "Define a int sequence here.")]
public IEnumerable<int> IntSequence { get; init; }

[Option('x', HelpText = "Define a boolean or switch value here.")]
public bool BoolValue { get; init; }

[Value(0)]
public long LongValue { get; init; }
}

public record Immutable_Simple_Options_Record(
[property: Option(HelpText = "Define a string value here.")]
string StringValue,

[property: Option('i', Min = 3, Max = 4, HelpText = "Define a int sequence here.")]
IEnumerable<int> IntSequence,

[property: Option('x', HelpText = "Define a boolean or switch value here.")]
bool BoolValue,

[property: Value(0)]
long LongValue
);
#endif
}
28 changes: 27 additions & 1 deletion tests/CommandLine.Tests/Unit/Core/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using CommandLine.Core;
using CommandLine.Tests.Fakes;

namespace CommandLine.Tests.Unit.Infrastructure
namespace CommandLine.Tests.Unit.Core
{
public class ReflectionHelperTests
{
Expand All @@ -20,5 +20,31 @@ public static void Class_without_public_set_properties_or_fields_is_ranked_immut
{
typeof(Immutable_Simple_Options).IsMutable().Should().BeFalse();
}

[Fact]
public static void Class_with_read_only_properties_is_ranked_immutable()
{
typeof(Immutable_Simple_Options_Read_Only).IsMutable().Should().BeFalse();
}

[Fact]
public static void Class_with_private_set_properties_is_ranked_immutable()
{
typeof(Immutable_Simple_Options_Private_Set).IsMutable().Should().BeFalse();
}

#if NET5_0_OR_GREATER
[Fact]
public static void Class_with_init_properties_is_ranked_immutable()
{
typeof(Immutable_Simple_Options_Init).IsMutable().Should().BeFalse();
}

[Fact]
public static void Record_without_public_set_properties_or_fields_is_ranked_immutable()
{
typeof(Immutable_Simple_Options_Record).IsMutable().Should().BeFalse();
}
#endif
}
}
42 changes: 42 additions & 0 deletions tests/CommandLine.Tests/Unit/Issue777Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using FluentAssertions;
using Xunit;

// Issue #777
// Record types should be usable as immutable options types.

namespace CommandLine.Tests.Unit
{
#if NET5_0_OR_GREATER
public class Issue777Tests
{
[Fact]
public void Immutable_record_types_should_work()
{
var arguments = new[] { "--option1=test", "--option2=5", "--option3" };
var result = Parser.Default
.ParseArguments<Options>(arguments);

Assert.Empty(result.Errors);
Assert.Equal(ParserResultType.Parsed, result.Tag);

result.WithParsed(options =>
{
options.Option1.Should().Be("test");
options.Option2.Should().Be(5);
options.Option3.Should().Be(true);
});
}

private record Options(
[property: Option]
string Option1,

[property: Option]
int Option2,

[property: Option]
bool Option3
);
}
#endif
}