Skip to content

Commit 9f91088

Browse files
authored
Merge pull request #394 from commandlineparser/fix/issue-380
Enhance error reporting when Options class has Value or Option attribute on unsettable property and no constructors available
2 parents 3d4fec5 + d9efdac commit 9f91088

File tree

3 files changed

+95
-42
lines changed

3 files changed

+95
-42
lines changed

src/CommandLine/Core/InstanceBuilder.cs

Lines changed: 73 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -82,44 +82,19 @@ public static ParserResult<T> Build<T>(
8282
var specPropsWithValue =
8383
optionSpecPropsResult.SucceededWith().Concat(valueSpecPropsResult.SucceededWith()).Memorize();
8484

85-
var setPropertyErrors = new List<Error>();
85+
var setPropertyErrors = new List<Error>();
8686

87-
Func <T> buildMutable = () =>
87+
//build the instance, determining if the type is mutable or not.
88+
T instance;
89+
if(typeInfo.IsMutable() == true)
8890
{
89-
var mutable = factory.MapValueOrDefault(f => f(), Activator.CreateInstance<T>());
90-
setPropertyErrors.AddRange(mutable.SetProperties(specPropsWithValue, sp => sp.Value.IsJust(), sp => sp.Value.FromJustOrFail()));
91-
setPropertyErrors.AddRange(mutable.SetProperties(
92-
specPropsWithValue,
93-
sp => sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust(),
94-
sp => sp.Specification.DefaultValue.FromJustOrFail()));
95-
setPropertyErrors.AddRange(mutable.SetProperties(
96-
specPropsWithValue,
97-
sp =>
98-
sp.Value.IsNothing() && sp.Specification.TargetType == TargetType.Sequence
99-
&& sp.Specification.DefaultValue.MatchNothing(),
100-
sp => sp.Property.PropertyType.GetTypeInfo().GetGenericArguments().Single().CreateEmptyArray()));
101-
return mutable;
102-
};
103-
104-
Func<T> buildImmutable = () =>
91+
instance = BuildMutable(factory, specPropsWithValue, setPropertyErrors);
92+
}
93+
else
10594
{
106-
var ctor = typeInfo.GetTypeInfo().GetConstructor((from sp in specProps select sp.Property.PropertyType).ToArray());
107-
var values = (from prms in ctor.GetParameters()
108-
join sp in specPropsWithValue on prms.Name.ToLower() equals sp.Property.Name.ToLower() into spv
109-
from sp in spv.DefaultIfEmpty()
110-
select
111-
sp == null
112-
? specProps.First(s => String.Equals(s.Property.Name, prms.Name, StringComparison.CurrentCultureIgnoreCase))
113-
.Property.PropertyType.GetDefaultValue()
114-
: sp.Value.GetValueOrDefault(
115-
sp.Specification.DefaultValue.GetValueOrDefault(
116-
sp.Specification.ConversionType.CreateDefaultForImmutable()))).ToArray();
117-
var immutable = (T)ctor.Invoke(values);
118-
return immutable;
119-
};
120-
121-
var instance = typeInfo.IsMutable() ? buildMutable() : buildImmutable();
122-
95+
instance = BuildImmutable(typeInfo, factory, specProps, specPropsWithValue, setPropertyErrors);
96+
}
97+
12398
var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens));
12499

125100
var allErrors =
@@ -150,5 +125,67 @@ from sp in spv.DefaultIfEmpty()
150125

151126
return result;
152127
}
128+
129+
private static T BuildMutable<T>(Maybe<Func<T>> factory, IEnumerable<SpecificationProperty> specPropsWithValue, List<Error> setPropertyErrors )
130+
{
131+
var mutable = factory.MapValueOrDefault(f => f(), Activator.CreateInstance<T>());
132+
133+
setPropertyErrors.AddRange(
134+
mutable.SetProperties(
135+
specPropsWithValue,
136+
sp => sp.Value.IsJust(),
137+
sp => sp.Value.FromJustOrFail()
138+
)
139+
);
140+
141+
setPropertyErrors.AddRange(
142+
mutable.SetProperties(
143+
specPropsWithValue,
144+
sp => sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust(),
145+
sp => sp.Specification.DefaultValue.FromJustOrFail()
146+
)
147+
);
148+
149+
setPropertyErrors.AddRange(
150+
mutable.SetProperties(
151+
specPropsWithValue,
152+
sp => sp.Value.IsNothing()
153+
&& sp.Specification.TargetType == TargetType.Sequence
154+
&& sp.Specification.DefaultValue.MatchNothing(),
155+
sp => sp.Property.PropertyType.GetTypeInfo().GetGenericArguments().Single().CreateEmptyArray()
156+
)
157+
);
158+
159+
return mutable;
160+
}
161+
162+
private static T BuildImmutable<T>(Type typeInfo, Maybe<Func<T>> factory, IEnumerable<SpecificationProperty> specProps, IEnumerable<SpecificationProperty> specPropsWithValue, List<Error> setPropertyErrors)
163+
{
164+
var ctor = typeInfo.GetTypeInfo().GetConstructor(
165+
specProps.Select(sp => sp.Property.PropertyType).ToArray()
166+
);
167+
168+
if(ctor == null)
169+
{
170+
throw new InvalidOperationException($"Type appears to be immutable, but no constructor found for type {typeInfo.FullName} to accept values.");
171+
}
172+
173+
var values =
174+
(from prms in ctor.GetParameters()
175+
join sp in specPropsWithValue on prms.Name.ToLower() equals sp.Property.Name.ToLower() into spv
176+
from sp in spv.DefaultIfEmpty()
177+
select
178+
sp == null
179+
? specProps.First(s => String.Equals(s.Property.Name, prms.Name, StringComparison.CurrentCultureIgnoreCase))
180+
.Property.PropertyType.GetDefaultValue()
181+
: sp.Value.GetValueOrDefault(
182+
sp.Specification.DefaultValue.GetValueOrDefault(
183+
sp.Specification.ConversionType.CreateDefaultForImmutable()))).ToArray();
184+
185+
var immutable = (T)ctor.Invoke(values);
186+
187+
return immutable;
188+
}
189+
153190
}
154-
}
191+
}

src/CommandLine/Core/ReflectionExtensions.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,13 @@ public static object GetDefaultValue(this Type type)
122122

123123
public static bool IsMutable(this Type type)
124124
{
125-
Func<bool> isMutable = () => {
126-
var props = type.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite);
127-
var fields = type.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any();
128-
return props || fields;
129-
};
130-
return type != typeof(object) ? isMutable() : true;
125+
if(type == typeof(object))
126+
return true;
127+
128+
var props = type.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite);
129+
var fields = type.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any();
130+
131+
return props || fields;
131132
}
132133

133134
public static object CreateDefaultForImmutable(this Type type)

tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,6 +1133,21 @@ public void Parse_TimeSpan()
11331133
// Teardown
11341134
}
11351135

1136+
[Fact]
1137+
public void OptionClass_IsImmutable_HasNoCtor()
1138+
{
1139+
Action act = () => InvokeBuild<ValueWithNoSetterOptions>(new string[] { "Test" }, false, false);
1140+
1141+
act.Should().Throw<InvalidOperationException>();
1142+
}
1143+
1144+
private class ValueWithNoSetterOptions
1145+
{
1146+
[Value(0, MetaName = "Test", Default = 0)]
1147+
public int TestValue { get; }
1148+
}
1149+
1150+
11361151
public static IEnumerable<object[]> RequiredValueStringData
11371152
{
11381153
get

0 commit comments

Comments
 (0)