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
10 changes: 10 additions & 0 deletions TUnit.Analyzers/TestDataAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,16 @@ private static bool CanConvert(SymbolAnalysisContext context, TypedConstant argu
return true;
}

if (methodParameterType?.SpecialType == SpecialType.System_Decimal &&
argument.Type?.SpecialType == SpecialType.System_String &&
argument.Value is string strValue &&
decimal.TryParse(strValue, out _))
{
// Allow string literals for decimal parameters for values that can't be expressed as C# numeric literals
// e.g. [Arguments("79228162514264337593543950335")] for decimal.MaxValue
return true;
}

return CanConvert(context, argument.Type, methodParameterType);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ internal sealed class ConstantArgumentsTests_Double_TestSource_GUID : global::TU
],
DataSources = new global::TUnit.Core.IDataSourceAttribute[]
{
new global::TUnit.Core.ArgumentsAttribute(1.23d),
new global::TUnit.Core.ArgumentsAttribute(1.23),
},
ClassDataSources = global::System.Array.Empty<global::TUnit.Core.IDataSourceAttribute>(),
PropertyDataSources = global::System.Array.Empty<global::TUnit.Core.PropertyDataSource>(),
Expand Down Expand Up @@ -593,7 +593,7 @@ internal sealed class ConstantArgumentsTests_UInt_TestSource_GUID : global::TUni
],
DataSources = new global::TUnit.Core.IDataSourceAttribute[]
{
new global::TUnit.Core.ArgumentsAttribute(123U),
new global::TUnit.Core.ArgumentsAttribute(123u),
},
ClassDataSources = global::System.Array.Empty<global::TUnit.Core.IDataSourceAttribute>(),
PropertyDataSources = global::System.Array.Empty<global::TUnit.Core.PropertyDataSource>(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ internal sealed class DataDrivenTests_EnumValue_TestSource_GUID : global::TUnit.
{
new global::TUnit.Core.ArgumentsAttribute(global::TUnit.TestProject.TestEnum.One),
new global::TUnit.Core.ArgumentsAttribute(global::TUnit.TestProject.TestEnum.Two),
new global::TUnit.Core.ArgumentsAttribute(-1),
new global::TUnit.Core.ArgumentsAttribute((global::TUnit.TestProject.TestEnum)(-1)),
},
ClassDataSources = global::System.Array.Empty<global::TUnit.Core.IDataSourceAttribute>(),
PropertyDataSources = global::System.Array.Empty<global::TUnit.Core.PropertyDataSource>(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ internal sealed class NullableByteArgumentTests_Test2_TestSource_GUID : global::
],
DataSources = new global::TUnit.Core.IDataSourceAttribute[]
{
new global::TUnit.Core.ArgumentsAttribute(1, 1),
new global::TUnit.Core.ArgumentsAttribute(1, null),
new global::TUnit.Core.ArgumentsAttribute((byte)1, 1),
new global::TUnit.Core.ArgumentsAttribute((byte)1, null),
},
ClassDataSources = global::System.Array.Empty<global::TUnit.Core.IDataSourceAttribute>(),
PropertyDataSources = global::System.Array.Empty<global::TUnit.Core.PropertyDataSource>(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ internal sealed class NumberArgumentTests_Double_TestSource_GUID : global::TUnit
],
DataSources = new global::TUnit.Core.IDataSourceAttribute[]
{
new global::TUnit.Core.ArgumentsAttribute(1.1d),
new global::TUnit.Core.ArgumentsAttribute(1.1),
},
ClassDataSources = global::System.Array.Empty<global::TUnit.Core.IDataSourceAttribute>(),
PropertyDataSources = global::System.Array.Empty<global::TUnit.Core.PropertyDataSource>(),
Expand Down Expand Up @@ -603,7 +603,7 @@ internal sealed class NumberArgumentTests_UInt_TestSource_GUID : global::TUnit.C
],
DataSources = new global::TUnit.Core.IDataSourceAttribute[]
{
new global::TUnit.Core.ArgumentsAttribute(1U),
new global::TUnit.Core.ArgumentsAttribute(1u),
},
ClassDataSources = global::System.Array.Empty<global::TUnit.Core.IDataSourceAttribute>(),
PropertyDataSources = global::System.Array.Empty<global::TUnit.Core.PropertyDataSource>(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ internal sealed class NumberArgumentTests_Double_TestSource_GUID : global::TUnit
],
DataSources = new global::TUnit.Core.IDataSourceAttribute[]
{
new global::TUnit.Core.ArgumentsAttribute(1.1d),
new global::TUnit.Core.ArgumentsAttribute(1,1),
},
ClassDataSources = global::System.Array.Empty<global::TUnit.Core.IDataSourceAttribute>(),
PropertyDataSources = global::System.Array.Empty<global::TUnit.Core.PropertyDataSource>(),
Expand Down Expand Up @@ -255,7 +255,7 @@ internal sealed class NumberArgumentTests_Float_TestSource_GUID : global::TUnit.
],
DataSources = new global::TUnit.Core.IDataSourceAttribute[]
{
new global::TUnit.Core.ArgumentsAttribute(1.1f),
new global::TUnit.Core.ArgumentsAttribute(1,1f),
},
ClassDataSources = global::System.Array.Empty<global::TUnit.Core.IDataSourceAttribute>(),
PropertyDataSources = global::System.Array.Empty<global::TUnit.Core.PropertyDataSource>(),
Expand Down Expand Up @@ -603,7 +603,7 @@ internal sealed class NumberArgumentTests_UInt_TestSource_GUID : global::TUnit.C
],
DataSources = new global::TUnit.Core.IDataSourceAttribute[]
{
new global::TUnit.Core.ArgumentsAttribute(1U),
new global::TUnit.Core.ArgumentsAttribute(1u),
},
ClassDataSources = global::System.Array.Empty<global::TUnit.Core.IDataSourceAttribute>(),
PropertyDataSources = global::System.Array.Empty<global::TUnit.Core.PropertyDataSource>(),
Expand Down
4 changes: 2 additions & 2 deletions TUnit.Core.SourceGenerator.Tests/Tests1603.Test.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal sealed class Tests_Casted_Integer_To_Short_Converts_TestSource_GUID : g
],
DataSources = new global::TUnit.Core.IDataSourceAttribute[]
{
new global::TUnit.Core.ArgumentsAttribute(-123),
new global::TUnit.Core.ArgumentsAttribute((short)-123),
},
ClassDataSources = global::System.Array.Empty<global::TUnit.Core.IDataSourceAttribute>(),
PropertyDataSources = global::System.Array.Empty<global::TUnit.Core.PropertyDataSource>(),
Expand Down Expand Up @@ -137,7 +137,7 @@ internal sealed class Tests_Integer_To_Short_Converts_TestSource_GUID : global::
],
DataSources = new global::TUnit.Core.IDataSourceAttribute[]
{
new global::TUnit.Core.ArgumentsAttribute(-123),
new global::TUnit.Core.ArgumentsAttribute((short)-123),
},
ClassDataSources = global::System.Array.Empty<global::TUnit.Core.IDataSourceAttribute>(),
PropertyDataSources = global::System.Array.Empty<global::TUnit.Core.PropertyDataSource>(),
Expand Down
10 changes: 5 additions & 5 deletions TUnit.Core.SourceGenerator.Tests/Tests2083.Test.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ internal sealed class Tests_MyTest_TestSource_GUID : global::TUnit.Core.Interfac
],
DataSources = new global::TUnit.Core.IDataSourceAttribute[]
{
new global::TUnit.Core.ArgumentsAttribute(0),
new global::TUnit.Core.ArgumentsAttribute(255),
new global::TUnit.Core.ArgumentsAttribute(32767),
new global::TUnit.Core.ArgumentsAttribute('\uffff'),
new global::TUnit.Core.ArgumentsAttribute(2147483647),
new global::TUnit.Core.ArgumentsAttribute(0L),
new global::TUnit.Core.ArgumentsAttribute(255L),
new global::TUnit.Core.ArgumentsAttribute(32767L),
new global::TUnit.Core.ArgumentsAttribute(65535L),
new global::TUnit.Core.ArgumentsAttribute(2147483647L),
new global::TUnit.Core.ArgumentsAttribute(9223372036854775807L),
},
ClassDataSources = global::System.Array.Empty<global::TUnit.Core.IDataSourceAttribute>(),
Expand Down
113 changes: 108 additions & 5 deletions TUnit.Core.SourceGenerator/CodeGenerationHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using TUnit.Core.SourceGenerator.CodeGenerators.Helpers;
using TUnit.Core.SourceGenerator.CodeGenerators.Writers;
using TUnit.Core.SourceGenerator.Extensions;
Expand Down Expand Up @@ -54,17 +55,32 @@ public static string GenerateParameterMetadataArray(IMethodSymbol method)
/// <summary>
/// Generates direct instantiation code for attributes.
/// </summary>
public static string GenerateAttributeInstantiation(AttributeData attr)
public static string GenerateAttributeInstantiation(AttributeData attr, IMethodSymbol? targetMethod = null)
{
var typeName = attr.AttributeClass!.GloballyQualified();
using var writer = new CodeWriter("", includeHeader: false);
writer.SetIndentLevel(1);
writer.Append($"new {typeName}(");

// Try to get the original syntax for better precision with decimal literals
var syntax = attr.ApplicationSyntaxReference?.GetSyntax();
var syntaxArguments = syntax?.ChildNodes()
.OfType<AttributeArgumentListSyntax>()
.FirstOrDefault()
?.Arguments.Where(x => x.NameEquals == null).ToList();

if (attr.ConstructorArguments.Length > 0)
{
var argStrings = new List<string>();

// Determine if this is an Arguments attribute and get parameter types
ITypeSymbol[]? parameterTypes = null;
if (attr.AttributeClass?.Name == "ArgumentsAttribute" && targetMethod != null)
{
parameterTypes = targetMethod.Parameters.Select(p => p.Type).ToArray();
}

var syntaxIndex = 0;
for (var i = 0; i < attr.ConstructorArguments.Length; i++)
{
var arg = attr.ConstructorArguments[i];
Expand All @@ -76,18 +92,105 @@ public static string GenerateAttributeInstantiation(AttributeData attr)
{
if (!arg.Values.IsDefault)
{
var elements = arg.Values.Select(TypedConstantParser.GetRawTypedConstantValue);
var elementIndex = 0;
var elements = arg.Values.Select(v =>
{
var paramType = parameterTypes != null && elementIndex < parameterTypes.Length
? parameterTypes[elementIndex]
: null;

// For decimal parameters with syntax available, use the original text
if (paramType?.SpecialType == SpecialType.System_Decimal &&
syntaxArguments != null && syntaxIndex < syntaxArguments.Count)
{
var originalText = syntaxArguments[syntaxIndex].Expression.ToString();
syntaxIndex++;
// Check if it's a string literal (starts and ends with quotes)
if (originalText.StartsWith("\"") && originalText.EndsWith("\""))
{
// For string literals, let the normal processing handle it (will use decimal.Parse)
syntaxIndex--; // Back up so normal processing can handle it
elementIndex++;
return TypedConstantParser.GetRawTypedConstantValue(v, paramType);
}
else
{
// For numeric literals, remove any suffix and add 'm' for decimal
originalText = originalText.TrimEnd('d', 'D', 'f', 'F', 'l', 'L', 'u', 'U', 'm', 'M');
return $"{originalText}m";
}
}

syntaxIndex++;
elementIndex++;
return TypedConstantParser.GetRawTypedConstantValue(v, paramType);
}).ToList();
argStrings.AddRange(elements);
}
}
else
{
argStrings.Add(TypedConstantParser.GetRawTypedConstantValue(arg));
var paramType = parameterTypes != null && i < parameterTypes.Length ? parameterTypes[i] : null;

// For decimal parameters with syntax available, use the original text
if (paramType?.SpecialType == SpecialType.System_Decimal &&
syntaxArguments != null && syntaxIndex < syntaxArguments.Count)
{
var originalText = syntaxArguments[syntaxIndex].Expression.ToString();
syntaxIndex++;
// Check if it's a string literal (starts and ends with quotes)
if (originalText.StartsWith("\"") && originalText.EndsWith("\""))
{
// For string literals, let the normal processing handle it (will use decimal.Parse)
syntaxIndex--; // Back up so normal processing can handle it
argStrings.Add(TypedConstantParser.GetRawTypedConstantValue(arg, paramType));
}
else
{
// For numeric literals, remove any suffix and add 'm' for decimal
originalText = originalText.TrimEnd('d', 'D', 'f', 'F', 'l', 'L', 'u', 'U', 'm', 'M');
argStrings.Add($"{originalText}m");
}
}
else
{
syntaxIndex++;
argStrings.Add(TypedConstantParser.GetRawTypedConstantValue(arg, paramType));
}
}
}
else
{
argStrings.Add(TypedConstantParser.GetRawTypedConstantValue(arg));
var paramType = parameterTypes != null && i < parameterTypes.Length ? parameterTypes[i] : null;

// For decimal parameters with syntax available, use the original text
if (paramType?.SpecialType == SpecialType.System_Decimal &&
syntaxArguments != null && syntaxIndex < syntaxArguments.Count)
{
var originalText = syntaxArguments[syntaxIndex].Expression.ToString();
syntaxIndex++;
// Check if it's a string literal (starts and ends with quotes)
if (originalText.StartsWith("\"") && originalText.EndsWith("\""))
{
// For string literals, let the normal processing handle it (will use decimal.Parse)
syntaxIndex--; // Back up so normal processing can handle it
argStrings.Add(TypedConstantParser.GetRawTypedConstantValue(arg, paramType));
}
else
{
// For numeric literals, remove any suffix and add 'm' for decimal
originalText = originalText.TrimEnd('d', 'D', 'f', 'F', 'l', 'L', 'u', 'U', 'm', 'M');
argStrings.Add($"{originalText}m");
}
}
else
{
if (syntaxArguments != null && syntaxIndex < syntaxArguments.Count)
{
syntaxIndex++;
}
argStrings.Add(TypedConstantParser.GetRawTypedConstantValue(arg, paramType));
}
}
}

Expand Down Expand Up @@ -311,7 +414,7 @@ private static string GenerateInlineDataProvider(AttributeData attr)
using var writer = new CodeWriter("", includeHeader: false);
writer.Append("new global::TUnit.Core.StaticTestDataSource(new object?[][] { new object?[] { ");

var args = attr.ConstructorArguments.Select(TypedConstantParser.GetRawTypedConstantValue).ToList();
var args = attr.ConstructorArguments.Select(arg => TypedConstantParser.GetRawTypedConstantValue(arg)).ToList();
writer.Append(string.Join(", ", args));
writer.Append(" } })");
return writer.ToString().Trim();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,33 @@ private string FormatPrimitiveForCode(object? value, ITypeSymbol? targetType)
// Double is default for floating-point literals
return value.ToString()!;
case SpecialType.System_Decimal:
// Handle string to decimal conversion for values that can't be expressed as literals
if (value is string s)
{
// Generate code that parses the string at runtime
// This allows for maximum precision decimal values
return $"decimal.Parse(\"{s}\", System.Globalization.CultureInfo.InvariantCulture)";
}
// When target is decimal but value is double/float/int, convert and format with m suffix
else if (value is double d)
{
// Use the full precision by formatting with sufficient digits
// The 'G29' format gives us the maximum precision for decimal
var decimalValue = (decimal)d;
return $"{decimalValue.ToString("G29", System.Globalization.CultureInfo.InvariantCulture)}m";
}
else if (value is float f)
{
var decimalValue = (decimal)f;
return $"{decimalValue.ToString("G29", System.Globalization.CultureInfo.InvariantCulture)}m";
}
else if (value is int || value is long || value is short || value is byte ||
value is uint || value is ulong || value is ushort || value is sbyte)
{
// For integer types, convert to decimal
var decimalValue = Convert.ToDecimal(value);
return $"{decimalValue.ToString(System.Globalization.CultureInfo.InvariantCulture)}m";
}
return $"{value}m";
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ public static string GetFullyQualifiedTypeNameFromTypedConstantValue(TypedConsta
return typedConstant.Type!.GloballyQualified();
}

public static string GetRawTypedConstantValue(TypedConstant typedConstant)
public static string GetRawTypedConstantValue(TypedConstant typedConstant, ITypeSymbol? targetType = null)
{
// Use the formatter for consistent handling
return _formatter.FormatForCode(typedConstant);
return _formatter.FormatForCode(typedConstant, targetType);
}

private static string FormatPrimitive(TypedConstant typedConstant)
Expand Down
Loading
Loading