Skip to content
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
111 changes: 77 additions & 34 deletions src/libraries/Microsoft.Extensions.Options/gen/Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ public void EmitMaxLengthAttribute(string modifier, string prefix, string classN
OutGeneratedCodeAttribute();

string qualifiedClassName = $"{prefix}{suffix}_{className}";
string formatMessageOverride = GenerateFormatMessageOverride("Length");

OutLn($$"""
[global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
Expand All @@ -220,7 +221,7 @@ public void EmitMaxLengthAttribute(string modifier, string prefix, string classN
public {{qualifiedClassName}}(): base(() => DefaultErrorMessageString) { Length = MaxAllowableLength; }
public int Length { get; }
public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
public override bool IsValid(object? value)
{{formatMessageOverride}} public override bool IsValid(object? value)
{
if (Length == 0 || Length < -1)
{
Expand Down Expand Up @@ -256,6 +257,7 @@ public void EmitMinLengthAttribute(string modifier, string prefix, string classN
OutGeneratedCodeAttribute();

string qualifiedClassName = $"{prefix}{suffix}_{className}";
string formatMessageOverride = GenerateFormatMessageOverride("Length");

OutLn($$"""
[global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
Expand Down Expand Up @@ -293,7 +295,7 @@ public override bool IsValid(object? value)
return length >= Length;
}
public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
}
{{formatMessageOverride}} }
""");
}

Expand All @@ -302,6 +304,7 @@ public void EmitLengthAttribute(string modifier, string prefix, string className
OutGeneratedCodeAttribute();

string qualifiedClassName = $"{prefix}{suffix}_{className}";
string formatMessageOverride = GenerateFormatMessageOverride("MinimumLength, MaximumLength");

OutLn($$"""
[global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
Expand Down Expand Up @@ -343,7 +346,7 @@ public override bool IsValid(object? value)
return (uint)(length - MinimumLength) <= (uint)(MaximumLength - MinimumLength);
}
public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, MinimumLength, MaximumLength);
}
{{formatMessageOverride}} }
""");
}

Expand All @@ -352,6 +355,7 @@ public void EmitCompareAttribute(string modifier, string prefix, string classNam
OutGeneratedCodeAttribute();

string qualifiedClassName = $"{prefix}{suffix}_{className}";
string formatMessageOverride = GenerateFormatMessageOverride("OtherProperty");

OutLn($$"""
[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)]
Expand Down Expand Up @@ -383,7 +387,7 @@ public void EmitCompareAttribute(string modifier, string prefix, string classNam
return null;
}
public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, OtherProperty);
}
{{formatMessageOverride}} }
""");
}

Expand All @@ -392,6 +396,7 @@ public void EmitRangeAttribute(string modifier, string prefix, string className,
OutGeneratedCodeAttribute();

string qualifiedClassName = $"{prefix}{suffix}_{className}";
string formatMessageOverride = GenerateFormatMessageOverride("Minimum, Maximum", ensureInitialized: true);

string initializationString = emitTimeSpanSupport ?
"""
Expand Down Expand Up @@ -462,7 +467,50 @@ public void EmitRangeAttribute(string modifier, string prefix, string className,
}
""";

string ensureInitializedBody = $$"""
if (!_initialized)
{
lock (_lock)
{
if (!_initialized)
{
if (Minimum is null || Maximum is null)
{
throw new global::System.InvalidOperationException(MinMaxError);
}
if (_needToConvertMinMax)
{
global::System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
{{initializationString}}
}
int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum);
if (cmp > 0)
{
throw new global::System.InvalidOperationException("The maximum value '{Maximum}' must be greater than or equal to the minimum value '{Minimum}'.");
}
else if (cmp == 0 && (MinimumIsExclusive || MaximumIsExclusive))
{
throw new global::System.InvalidOperationException("Cannot use exclusive bounds when the maximum value is equal to the minimum value.");
}
_initialized = true;
}
}
}
""";

string initializeRange = ensureInitializedBody;
string ensureInitializedMethod = string.Empty;
if (_symbolHolder.HasValidationAttributeFormatMessageMethod)
{
initializeRange = " EnsureInitialized();";

StringBuilder sb = new();
sb.AppendLine(" private void EnsureInitialized()");
sb.AppendLine(" {");
sb.AppendLine(ensureInitializedBody);
sb.AppendLine(" }");
ensureInitializedMethod = sb.ToString();
}

OutLn($$"""
[global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
Expand Down Expand Up @@ -496,41 +544,14 @@ public void EmitRangeAttribute(string modifier, string prefix, string className,
public bool ConvertValueInInvariantCulture { get; set; }
public override string FormatErrorMessage(string name) =>
string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum);
private readonly bool _needToConvertMinMax;
{{formatMessageOverride}} private readonly bool _needToConvertMinMax;
private volatile bool _initialized;
private readonly object _lock = new();
private const string MinMaxError = "The minimum and maximum values must be set to valid values.";

public override bool IsValid(object? value)
{
if (!_initialized)
{
lock (_lock)
{
if (!_initialized)
{
if (Minimum is null || Maximum is null)
{
throw new global::System.InvalidOperationException(MinMaxError);
}
if (_needToConvertMinMax)
{
global::System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
{{initializationString}}
}
int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum);
if (cmp > 0)
{
throw new global::System.InvalidOperationException("The maximum value '{Maximum}' must be greater than or equal to the minimum value '{Minimum}'.");
}
else if (cmp == 0 && (MinimumIsExclusive || MaximumIsExclusive))
{
throw new global::System.InvalidOperationException("Cannot use exclusive bounds when the maximum value is equal to the minimum value.");
}
_initialized = true;
}
}
}
{{initializeRange}}

if (value is null or string { Length: 0 })
{
Expand All @@ -549,7 +570,7 @@ public override bool IsValid(object? value)
(MinimumIsExclusive ? min.CompareTo(convertedValue) < 0 : min.CompareTo(convertedValue) <= 0) &&
(MaximumIsExclusive ? max.CompareTo(convertedValue) > 0 : max.CompareTo(convertedValue) >= 0);
}
private string GetValidationErrorMessage()
{{ensureInitializedMethod}} private string GetValidationErrorMessage()
{
return (MinimumIsExclusive, MaximumIsExclusive) switch
{
Expand All @@ -575,6 +596,28 @@ private string GetValidationErrorMessage()
""");
}

private string GenerateFormatMessageOverride(string arguments, bool ensureInitialized = false)
{
if (!_symbolHolder.HasValidationAttributeFormatMessageMethod)
{
return string.Empty;
}

StringBuilder sb = new();
sb.AppendLine(""" public override string FormatMessage([global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("CompositeFormat")] string format, string name)""");
sb.AppendLine(" {");

if (ensureInitialized)
{
sb.AppendLine(" EnsureInitialized();");
sb.AppendLine();
}

sb.AppendLine($" return string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, name, {arguments});");
sb.AppendLine(" }");
return sb.ToString();
}

private string GenerateStronglyTypedCodeForLengthAttributes(HashSet<object> data)
{
if (data.Count == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ internal sealed record class SymbolHolder(
INamedTypeSymbol? IAsyncValidatableObjectSymbol = null,
bool HasTryValidateValueAsyncMethod = false,
INamedTypeSymbol? AsyncValidateOptionsSymbol = null,
INamedTypeSymbol? AsyncValidationAttributeSymbol = null);
INamedTypeSymbol? AsyncValidationAttributeSymbol = null,
bool HasValidationAttributeFormatMessageMethod = false);
}
14 changes: 13 additions & 1 deletion src/libraries/Microsoft.Extensions.Options/gen/SymbolLoader.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using Microsoft.CodeAnalysis;

namespace Microsoft.Extensions.Options.Generators
Expand Down Expand Up @@ -95,6 +96,16 @@ public static bool TryLoad(Compilation compilation, out SymbolHolder? symbolHold
var asyncValidationAttributeSymbol = GetSymbol(AsyncValidationAttributeType);
var validatorSymbol = GetSymbol(ValidatorType);
bool hasTryValidateValueAsyncMethod = validatorSymbol?.GetMembers("TryValidateValueAsync").Length > 0;
bool hasValidationAttributeFormatMessageMethod = validationAttributeSymbol
.GetMembers("FormatMessage")
.Any(static member =>
member is IMethodSymbol method &&
method.IsVirtual &&
method.DeclaredAccessibility == Accessibility.Public &&
method.ReturnType.SpecialType == SpecialType.System_String &&
method.Parameters.Length == 2 &&
method.Parameters[0].Type.SpecialType == SpecialType.System_String &&
method.Parameters[1].Type.SpecialType == SpecialType.System_String);

symbolHolder = new(
optionsValidatorSymbol,
Expand All @@ -118,7 +129,8 @@ public static bool TryLoad(Compilation compilation, out SymbolHolder? symbolHold
iAsyncValidatableObjectSymbol,
hasTryValidateValueAsyncMethod,
asyncValidateOptionsSymbol,
asyncValidationAttributeSymbol);
asyncValidationAttributeSymbol,
hasValidationAttributeFormatMessageMethod);

return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,9 @@ public override bool IsValid(object? value)
return (uint)(length - MinimumLength) <= (uint)(MaximumLength - MinimumLength);
}
public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, MinimumLength, MaximumLength);
public override string FormatMessage([global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("CompositeFormat")] string format, string name)
{
return string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, name, MinimumLength, MaximumLength);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,46 @@ public __SourceGen__RangeAttribute(global::System.Type type, string minimum, str
public bool ConvertValueInInvariantCulture { get; set; }
public override string FormatErrorMessage(string name) =>
string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum);
public override string FormatMessage([global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("CompositeFormat")] string format, string name)
{
EnsureInitialized();

return string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, name, Minimum, Maximum);
}
private readonly bool _needToConvertMinMax;
private volatile bool _initialized;
private readonly object _lock = new();
private const string MinMaxError = "The minimum and maximum values must be set to valid values.";

public override bool IsValid(object? value)
{
EnsureInitialized();

if (value is null or string { Length: 0 })
{
return true;
}

global::System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
object? convertedValue;

try
{
convertedValue = ConvertValue(value, formatProvider);
}
catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException or global::System.OverflowException)
{
return false;
}

var min = (global::System.IComparable)Minimum;
var max = (global::System.IComparable)Maximum;

return
(MinimumIsExclusive ? min.CompareTo(convertedValue) < 0 : min.CompareTo(convertedValue) <= 0) &&
(MaximumIsExclusive ? max.CompareTo(convertedValue) > 0 : max.CompareTo(convertedValue) >= 0);
}
private void EnsureInitialized()
{
if (!_initialized)
{
Expand Down Expand Up @@ -139,30 +173,6 @@ public override bool IsValid(object? value)
}
}
}

if (value is null or string { Length: 0 })
{
return true;
}

global::System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
object? convertedValue;

try
{
convertedValue = ConvertValue(value, formatProvider);
}
catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException or global::System.OverflowException)
{
return false;
}

var min = (global::System.IComparable)Minimum;
var max = (global::System.IComparable)Maximum;

return
(MinimumIsExclusive ? min.CompareTo(convertedValue) < 0 : min.CompareTo(convertedValue) <= 0) &&
(MaximumIsExclusive ? max.CompareTo(convertedValue) > 0 : max.CompareTo(convertedValue) >= 0);
}
private string GetValidationErrorMessage()
{
Expand Down
Loading
Loading