Skip to content

Commit e793666

Browse files
authored
Merge branch 'release/8.0-staging' into merge/release/8.0-to-release/8.0-staging
2 parents 34544b9 + 30d6848 commit e793666

18 files changed

+715
-84
lines changed

eng/Version.Details.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,9 +354,9 @@
354354
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-optimization</Uri>
355355
<Sha>5914dbda1860830a56179692058d94c9f615deda</Sha>
356356
</Dependency>
357-
<Dependency Name="Microsoft.DotNet.HotReload.Utils.Generator.BuildTool" Version="8.0.0-alpha.0.23563.1">
357+
<Dependency Name="Microsoft.DotNet.HotReload.Utils.Generator.BuildTool" Version="8.0.0-alpha.0.23523.2">
358358
<Uri>https://github.com/dotnet/hotreload-utils</Uri>
359-
<Sha>12d02e5c2310be6460f896bc3aeb0ddf1e8926bc</Sha>
359+
<Sha>8e108a21d0c7d8fa2050a1bdd4d4ba50d2b8df13</Sha>
360360
</Dependency>
361361
<Dependency Name="System.Runtime.Numerics.TestData" Version="8.0.0-beta.23566.1">
362362
<Uri>https://github.com/dotnet/runtime-assets</Uri>

src/libraries/Microsoft.Extensions.Options/gen/Emitter.cs

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -374,12 +374,83 @@ public void EmitCompareAttribute(string modifier, string prefix, string classNam
374374
""");
375375
}
376376

377-
public void EmitRangeAttribute(string modifier, string prefix, string className, string suffix)
377+
public void EmitRangeAttribute(string modifier, string prefix, string className, string suffix, bool emitTimeSpanSupport)
378378
{
379379
OutGeneratedCodeAttribute();
380380

381381
string qualifiedClassName = $"{prefix}{suffix}_{className}";
382382

383+
string initializationString = emitTimeSpanSupport ?
384+
"""
385+
if (OperandType == typeof(global::System.TimeSpan))
386+
{
387+
if (!global::System.TimeSpan.TryParse((string)Minimum, culture, out global::System.TimeSpan timeSpanMinimum) ||
388+
!global::System.TimeSpan.TryParse((string)Maximum, culture, out global::System.TimeSpan timeSpanMaximum))
389+
{
390+
throw new global::System.InvalidOperationException(c_minMaxError);
391+
}
392+
Minimum = timeSpanMinimum;
393+
Maximum = timeSpanMaximum;
394+
}
395+
else
396+
{
397+
Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError);
398+
Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError);
399+
}
400+
"""
401+
:
402+
"""
403+
Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError);
404+
Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError);
405+
""";
406+
407+
string convertValue = emitTimeSpanSupport ?
408+
"""
409+
if (OperandType == typeof(global::System.TimeSpan))
410+
{
411+
if (value is global::System.TimeSpan)
412+
{
413+
convertedValue = value;
414+
}
415+
else if (value is string)
416+
{
417+
if (!global::System.TimeSpan.TryParse((string)value, formatProvider, out global::System.TimeSpan timeSpanValue))
418+
{
419+
return false;
420+
}
421+
convertedValue = timeSpanValue;
422+
}
423+
else
424+
{
425+
throw new global::System.InvalidOperationException($"A value type {value.GetType()} that is not a TimeSpan or a string has been given. This might indicate a problem with the source generator.");
426+
}
427+
}
428+
else
429+
{
430+
try
431+
{
432+
convertedValue = ConvertValue(value, formatProvider);
433+
}
434+
catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException)
435+
{
436+
return false;
437+
}
438+
}
439+
"""
440+
:
441+
"""
442+
try
443+
{
444+
convertedValue = ConvertValue(value, formatProvider);
445+
}
446+
catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException)
447+
{
448+
return false;
449+
}
450+
""";
451+
452+
453+
383454
OutLn($$"""
384455
[global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
385456
{{modifier}} class {{qualifiedClassName}} : {{StaticValidationAttributeType}}
@@ -414,19 +485,20 @@ public override string FormatErrorMessage(string name) =>
414485
string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum);
415486
private bool NeedToConvertMinMax { get; }
416487
private bool Initialized { get; set; }
488+
private const string c_minMaxError = "The minimum and maximum values must be set to valid values.";
489+
417490
public override bool IsValid(object? value)
418491
{
419492
if (!Initialized)
420493
{
421494
if (Minimum is null || Maximum is null)
422495
{
423-
throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
496+
throw new global::System.InvalidOperationException(c_minMaxError);
424497
}
425498
if (NeedToConvertMinMax)
426499
{
427500
System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
428-
Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
429-
Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
501+
{{initializationString}}
430502
}
431503
int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum);
432504
if (cmp > 0)
@@ -448,14 +520,7 @@ public override bool IsValid(object? value)
448520
System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
449521
object? convertedValue;
450522
451-
try
452-
{
453-
convertedValue = ConvertValue(value, formatProvider);
454-
}
455-
catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException)
456-
{
457-
return false;
458-
}
523+
{{convertValue}}
459524
460525
var min = (global::System.IComparable)Minimum;
461526
var max = (global::System.IComparable)Maximum;
@@ -574,7 +639,7 @@ private void GenValidationAttributesClasses()
574639
}
575640
else if (attributeData.Key == _symbolHolder.RangeAttributeSymbol.Name)
576641
{
577-
EmitRangeAttribute(_optionsSourceGenContext.ClassModifier, Emitter.StaticAttributeClassNamePrefix, attributeData.Key, _optionsSourceGenContext.Suffix);
642+
EmitRangeAttribute(_optionsSourceGenContext.ClassModifier, Emitter.StaticAttributeClassNamePrefix, attributeData.Key, _optionsSourceGenContext.Suffix, attributeData.Value is not null);
578643
}
579644
}
580645

src/libraries/Microsoft.Extensions.Options/gen/Parser.cs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -624,10 +624,21 @@ private void TrackCompareAttributeForSubstitution(AttributeData attribute, IType
624624
private void TrackRangeAttributeForSubstitution(AttributeData attribute, ITypeSymbol memberType, ref string attributeFullQualifiedName)
625625
{
626626
ImmutableArray<IParameterSymbol> constructorParameters = attribute.AttributeConstructor?.Parameters ?? ImmutableArray<IParameterSymbol>.Empty;
627-
SpecialType argumentSpecialType = SpecialType.None;
627+
ITypeSymbol? argumentType = null;
628+
bool hasTimeSpanType = false;
629+
630+
ITypeSymbol typeSymbol = memberType;
631+
if (typeSymbol.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
632+
{
633+
typeSymbol = ((INamedTypeSymbol)typeSymbol).TypeArguments[0];
634+
}
635+
628636
if (constructorParameters.Length == 2)
629637
{
630-
argumentSpecialType = constructorParameters[0].Type.SpecialType;
638+
if (OptionsSourceGenContext.IsConvertibleBasicType(typeSymbol))
639+
{
640+
argumentType = constructorParameters[0].Type;
641+
}
631642
}
632643
else if (constructorParameters.Length == 3)
633644
{
@@ -641,23 +652,25 @@ private void TrackRangeAttributeForSubstitution(AttributeData attribute, ITypeSy
641652
}
642653
}
643654

644-
if (argumentValue is INamedTypeSymbol namedTypeSymbol && OptionsSourceGenContext.IsConvertibleBasicType(namedTypeSymbol))
655+
if (argumentValue is INamedTypeSymbol namedTypeSymbol)
645656
{
646-
argumentSpecialType = namedTypeSymbol.SpecialType;
657+
// When type is provided as a parameter, it has to match the property type.
658+
if (OptionsSourceGenContext.IsConvertibleBasicType(namedTypeSymbol) && typeSymbol.SpecialType == namedTypeSymbol.SpecialType)
659+
{
660+
argumentType = namedTypeSymbol;
661+
}
662+
else if (SymbolEqualityComparer.Default.Equals(namedTypeSymbol, _symbolHolder.TimeSpanSymbol) &&
663+
(SymbolEqualityComparer.Default.Equals(typeSymbol, _symbolHolder.TimeSpanSymbol) || typeSymbol.SpecialType == SpecialType.System_String))
664+
{
665+
hasTimeSpanType = true;
666+
argumentType = _symbolHolder.TimeSpanSymbol;
667+
}
647668
}
648669
}
649670

650-
ITypeSymbol typeSymbol = memberType;
651-
if (typeSymbol.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
652-
{
653-
typeSymbol = ((INamedTypeSymbol)typeSymbol).TypeArguments[0];
654-
}
655-
656-
if (argumentSpecialType != SpecialType.None &&
657-
OptionsSourceGenContext.IsConvertibleBasicType(typeSymbol) &&
658-
(constructorParameters.Length != 3 || typeSymbol.SpecialType == argumentSpecialType)) // When type is provided as a parameter, it has to match the property type.
671+
if (argumentType is not null)
659672
{
660-
_optionsSourceGenContext.EnsureTrackingAttribute(attribute.AttributeClass!.Name, createValue: false, out _);
673+
_optionsSourceGenContext.EnsureTrackingAttribute(attribute.AttributeClass!.Name, createValue: hasTimeSpanType, out _);
661674
attributeFullQualifiedName = $"{Emitter.StaticGeneratedValidationAttributesClassesNamespace}.{Emitter.StaticAttributeClassNamePrefix}{_optionsSourceGenContext.Suffix}_{attribute.AttributeClass!.Name}";
662675
}
663676
}

src/libraries/Microsoft.Extensions.Options/gen/SymbolHolder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ internal sealed record class SymbolHolder(
2323
INamedTypeSymbol IValidatableObjectSymbol,
2424
INamedTypeSymbol GenericIEnumerableSymbol,
2525
INamedTypeSymbol TypeSymbol,
26+
INamedTypeSymbol TimeSpanSymbol,
2627
INamedTypeSymbol ValidateObjectMembersAttributeSymbol,
2728
INamedTypeSymbol ValidateEnumeratedItemsAttributeSymbol);
2829
}

src/libraries/Microsoft.Extensions.Options/gen/SymbolLoader.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ internal static class SymbolLoader
1919
internal const string IValidatableObjectType = "System.ComponentModel.DataAnnotations.IValidatableObject";
2020
internal const string IValidateOptionsType = "Microsoft.Extensions.Options.IValidateOptions`1";
2121
internal const string TypeOfType = "System.Type";
22+
internal const string TimeSpanType = "System.TimeSpan";
2223
internal const string ValidateObjectMembersAttribute = "Microsoft.Extensions.Options.ValidateObjectMembersAttribute";
2324
internal const string ValidateEnumeratedItemsAttribute = "Microsoft.Extensions.Options.ValidateEnumeratedItemsAttribute";
2425
internal const string GenericIEnumerableType = "System.Collections.Generic.IEnumerable`1";
@@ -42,6 +43,7 @@ public static bool TryLoad(Compilation compilation, out SymbolHolder? symbolHold
4243
var validateOptionsSymbol = GetSymbol(IValidateOptionsType);
4344
var genericIEnumerableSymbol = GetSymbol(GenericIEnumerableType);
4445
var typeSymbol = GetSymbol(TypeOfType);
46+
var timeSpanSymbol = GetSymbol(TimeSpanType);
4547
var validateObjectMembersAttribute = GetSymbol(ValidateObjectMembersAttribute);
4648
var validateEnumeratedItemsAttribute = GetSymbol(ValidateEnumeratedItemsAttribute);
4749
var unconditionalSuppressMessageAttributeSymbol = GetSymbol(UnconditionalSuppressMessageAttributeType);
@@ -70,6 +72,7 @@ public static bool TryLoad(Compilation compilation, out SymbolHolder? symbolHold
7072
validateOptionsSymbol == null ||
7173
genericIEnumerableSymbol == null ||
7274
typeSymbol == null ||
75+
timeSpanSymbol == null ||
7376
validateObjectMembersAttribute == null ||
7477
validateEnumeratedItemsAttribute == null)
7578
{
@@ -93,6 +96,7 @@ public static bool TryLoad(Compilation compilation, out SymbolHolder? symbolHold
9396
ivalidatableObjectSymbol,
9497
genericIEnumerableSymbol,
9598
typeSymbol,
99+
timeSpanSymbol,
96100
validateObjectMembersAttribute,
97101
validateEnumeratedItemsAttribute);
98102

src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<EnableDefaultItems>true</EnableDefaultItems>
66
<IsPackable>true</IsPackable>
77
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
8-
<ServicingVersion>1</ServicingVersion>
8+
<ServicingVersion>2</ServicingVersion>
99
<PackageDescription>Provides a strongly typed way of specifying and accessing settings using dependency injection.</PackageDescription>
1010
</PropertyGroup>
1111

src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netcore.g.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,19 +99,21 @@ public override string FormatErrorMessage(string name) =>
9999
string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum);
100100
private bool NeedToConvertMinMax { get; }
101101
private bool Initialized { get; set; }
102+
private const string c_minMaxError = "The minimum and maximum values must be set to valid values.";
103+
102104
public override bool IsValid(object? value)
103105
{
104106
if (!Initialized)
105107
{
106108
if (Minimum is null || Maximum is null)
107109
{
108-
throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
110+
throw new global::System.InvalidOperationException(c_minMaxError);
109111
}
110112
if (NeedToConvertMinMax)
111113
{
112114
System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
113-
Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
114-
Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
115+
Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError);
116+
Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError);
115117
}
116118
int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum);
117119
if (cmp > 0)

src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netfx.g.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,21 @@ public override string FormatErrorMessage(string name) =>
9797
string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum);
9898
private bool NeedToConvertMinMax { get; }
9999
private bool Initialized { get; set; }
100+
private const string c_minMaxError = "The minimum and maximum values must be set to valid values.";
101+
100102
public override bool IsValid(object? value)
101103
{
102104
if (!Initialized)
103105
{
104106
if (Minimum is null || Maximum is null)
105107
{
106-
throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
108+
throw new global::System.InvalidOperationException(c_minMaxError);
107109
}
108110
if (NeedToConvertMinMax)
109111
{
110112
System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
111-
Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
112-
Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
113+
Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError);
114+
Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError);
113115
}
114116
int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum);
115117
if (cmp > 0)

0 commit comments

Comments
 (0)