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
34 changes: 29 additions & 5 deletions src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
private const string GeneratedNamespace = "TUnit.Generated";

private static readonly DiagnosticDescriptor GenerationErrorDescriptor = new(
"TUNIT0999",

Check warning on line 26 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (fr-FR)

Activer le suivi de version d'analyseur pour le projet d'analyseur contenant la règle 'TUNIT0999' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 26 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Check warning on line 26 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ar-SA)

Check warning on line 26 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ar-SA)

Check warning on line 26 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Check warning on line 26 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Check warning on line 26 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

Check warning on line 26 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

"Source Generation Error",
"Failed to generate test metadata for {0}.{1}: {2}",
"TUnit",
Expand Down Expand Up @@ -1122,7 +1122,8 @@
List<AttributeData>? inferringRows = null;
foreach (var methodArgAttr in methodArgumentsAttributes)
{
var inferredTypes = InferClassTypesFromMethodArguments(testMethod.TypeSymbol, testMethod.MethodSymbol, methodArgAttr, compilation);
var inferredTypes = InferClassTypesFromMethodArguments(testMethod.TypeSymbol, testMethod.MethodSymbol, methodArgAttr, compilation,
nullArgumentsArrayInfersObject: !classTypeArgumentsSupplied);
Comment thread
thomhurst marked this conversation as resolved.
Resolved
if (inferredTypes is { Length: > 0 })
{
(inferringRows ??= []).Add(methodArgAttr);
Expand Down Expand Up @@ -5387,7 +5388,8 @@
{
foreach (var methodArgAttr in methodArgumentsAttributes)
{
var inferredTypes = InferClassTypesFromMethodArguments(testMethod.TypeSymbol, testMethod.MethodSymbol, methodArgAttr, compilation);
var inferredTypes = InferClassTypesFromMethodArguments(testMethod.TypeSymbol, testMethod.MethodSymbol, methodArgAttr, compilation,
nullArgumentsArrayInfersObject: false);
if (inferredTypes is { Length: > 0 })
{
var typeKey = BuildTypeKey(inferredTypes);
Expand Down Expand Up @@ -5968,7 +5970,12 @@
/// </summary>
private static bool IsNullArgumentsArray(AttributeData argAttr) => argAttr.ConstructorArguments[0].IsNull;

private static ITypeSymbol[]? InferClassTypesFromMethodArguments(INamedTypeSymbol classSymbol, IMethodSymbol methodSymbol, AttributeData argAttr, Compilation compilation)
/// <param name="nullArgumentsArrayInfersObject">
/// False when [GenerateGenericTest] or a class-level data source supplies the class type arguments: an
/// [Arguments(null)] row then infers no type and runs on those instantiations. True otherwise, so the
/// row's one null value infers object like any other null value instead of being dropped (#7141).
/// </param>
private static ITypeSymbol[]? InferClassTypesFromMethodArguments(INamedTypeSymbol classSymbol, IMethodSymbol methodSymbol, AttributeData argAttr, Compilation compilation, bool nullArgumentsArrayInfersObject)
{
if (argAttr.ConstructorArguments.Length == 0)
{
Expand All @@ -5978,12 +5985,29 @@
var inferredTypes = new Dictionary<string, ITypeSymbol>();
var classTypeParameters = classSymbol.TypeParameters;

// [Arguments(null)] passes one null value, from which no class type can be inferred (as in reflection mode)
if (!HasArgumentsArray(argAttr) || IsNullArgumentsArray(argAttr))
if (!HasArgumentsArray(argAttr))
{
return null;
}

// [Arguments(null)] passes a null array, which ArgumentsAttribute stores as one null value (as in reflection mode)
if (IsNullArgumentsArray(argAttr))
{
if (nullArgumentsArrayInfersObject
&& classTypeParameters.Length == 1
&& methodSymbol.Parameters.Length > 0
// A by-reference parameter cannot be passed the row's value, and reflection mode infers
// nothing for it (its type is T&, not T), so the row stays without an instantiation.
&& methodSymbol.Parameters[0].RefKind == RefKind.None
&& methodSymbol.Parameters[0].Type is ITypeParameterSymbol { DeclaringMethod: null } nullTypeParam
&& SymbolEqualityComparer.Default.Equals(nullTypeParam, classTypeParameters[0]))
Comment thread
thomhurst marked this conversation as resolved.
Resolved
{
return [compilation.GetSpecialType(SpecialType.System_Object)];
Comment thread
thomhurst marked this conversation as resolved.
Resolved
}

return null;
}

var argumentValues = argAttr.ConstructorArguments[0].Values;
var methodParams = methodSymbol.Parameters;

Expand Down
4 changes: 3 additions & 1 deletion src/TUnit.Core/Attributes/TestData/ArgumentsAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ public sealed class ArgumentsAttribute : Attribute, IDataSourceAttribute, ITestR

/// <summary>
/// True for <c>[Arguments(null)]</c>, which passes a null <c>params</c> array rather than one null
/// element. Generic type inference treats it as inferring no type, as the source generator does.
/// element. On a generic class whose type arguments [GenerateGenericTest] or a class-level data source
/// supplies, it infers no class type argument; otherwise its one null value infers object, as the source
/// generator does.
/// </summary>
internal bool IsNullValuesArray { get; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,8 @@ public void AddMethodRowViolations()

foreach (var argumentsAttribute in testMethod.GetCustomAttributes<ArgumentsAttribute>(inherit: false))
{
// [Arguments(null)] (a null params array) infers no type, as in the source generator
if (argumentsAttribute.IsNullValuesArray)
{
continue;
}

// [Arguments(null)] (a null params array) stores one null value, which infers object
// here, as in the source generator (#7141)
var typeArguments = InferClassTypeArguments(classGenericParameters, parameters, argumentsAttribute.Values);
if (typeArguments == null)
{
Expand Down
13 changes: 9 additions & 4 deletions src/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,10 @@ private static List<TestMetadata> BuildTestsFromMethodRows(Type genericTypeDefin
inferredTypeArguments = null;
var classGenericParameters = genericTypeDefinition.GetGenericArguments();

// With no [GenerateGenericTest] instantiation for it to run on, [Arguments(null)] infers object
// like any other null value, rather than being dropped (#7141)
var nullValuesArrayInfersObject = generateGenericTestAttributes.Length == 0;

foreach (var genericMethod in testMethods)
{
if (genericMethod.IsGenericMethodDefinition)
Expand Down Expand Up @@ -1232,7 +1236,7 @@ private static List<TestMetadata> BuildTestsFromMethodRows(Type genericTypeDefin
continue;
}

var typeArguments = InferClassTypeArgumentsFromMethodRow(classGenericParameters, parameters, arguments);
var typeArguments = InferClassTypeArgumentsFromMethodRow(classGenericParameters, parameters, arguments, nullValuesArrayInfersObject);
if (typeArguments == null)
{
continue;
Expand Down Expand Up @@ -1328,10 +1332,11 @@ private static bool IsInferredFromMethodRows(Dictionary<MethodInfo, List<Type[]>
&& methodRowTypeArguments.TryGetValue(genericMethod, out var groups)
&& IndexOfTypeArguments(groups, typeArguments) >= 0;

private static Type[]? InferClassTypeArgumentsFromMethodRow(Type[] classGenericParameters, ParameterInfo[] parameters, ArgumentsAttribute arguments)
private static Type[]? InferClassTypeArgumentsFromMethodRow(Type[] classGenericParameters, ParameterInfo[] parameters, ArgumentsAttribute arguments, bool nullValuesArrayInfersObject)
{
// [Arguments(null)] (a null params array) infers no type, as in the source generator
if (arguments.IsNullValuesArray)
// [Arguments(null)] (a null params array) infers no type when [GenerateGenericTest] supplies the
// instantiations it runs on. Otherwise its one null value infers object, as in the source generator.
if (arguments.IsNullValuesArray && !nullValuesArrayInfersObject)
{
return null;
}
Expand Down
20 changes: 20 additions & 0 deletions tests/TUnit.Core.SourceGenerator.Tests/Bugs/7141/Tests7141.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace TUnit.Core.SourceGenerator.Tests;

internal class Tests7141 : TestsBase
{
[Test]
public Task Test() => RunTest(Path.Combine(
Git.TestsDirectory.FullName,
"TUnit.TestProject",
"Bugs",
"_7141",
"NullArgumentsRowOnGenericClassTests.cs"),
async generatedFiles =>
{
// The null row's C<object> instantiation is emitted, not dropped
await Assert.That(string.Join("\n", generatedFiles)).Contains("NullRowOnGenericClass<object>");

// A by-reference parameter cannot take the row's value, so no instantiation is emitted for it
await Assert.That(string.Join("\n", generatedFiles)).DoesNotContain("NullRowOnGenericClassByRef<object>");
});
}
Loading
Loading