Repository navigation
fix(engine): run untyped [Arguments(null)] rows on generic classes instead of dropping them #7203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
|
||
| "Source Generation Error", | ||
| "Failed to generate test metadata for {0}.{1}: {2}", | ||
| "TUnit", | ||
|
|
@@ -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); | ||
| if (inferredTypes is { Length: > 0 }) | ||
| { | ||
| (inferringRows ??= []).Add(methodArgAttr); | ||
|
|
@@ -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); | ||
|
|
@@ -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) | ||
| { | ||
|
|
@@ -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])) | ||
|
thomhurst marked this conversation as resolved.
Resolved
|
||
| { | ||
| return [compilation.GetSpecialType(SpecialType.System_Object)]; | ||
|
thomhurst marked this conversation as resolved.
Resolved
|
||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| var argumentValues = argAttr.ConstructorArguments[0].Values; | ||
| var methodParams = methodSymbol.Parameters; | ||
|
|
||
|
|
||
| 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>"); | ||
| }); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.