Skip to content

Commit 36ab9a4

Browse files
Copilotthomhurst
andcommitted
Expand generic type fix to handle all open generic types, not just System.Nullable<>
Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
1 parent d031dea commit 36ab9a4

File tree

3 files changed

+23
-37
lines changed

3 files changed

+23
-37
lines changed

TUnit.Core.SourceGenerator.Tests/Bugs2971NullableTypeTest.Test.verified.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#pragma warning disable
66
#nullable enable
77
namespace TUnit.Generated;
8-
internal sealed class Tests_SimpleTest_TestSource_9c97c20c9f124d82999b00e7d24a75b1 : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource
8+
internal sealed class Tests_SimpleTest_TestSource_38be5ee0b5554088ba7b233e82649e27 : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource
99
{
1010
public async global::System.Collections.Generic.IAsyncEnumerable<global::TUnit.Core.TestMetadata> GetTestsAsync(string testSessionId, [global::System.Runtime.CompilerServices.EnumeratorCancellation] global::System.Threading.CancellationToken cancellationToken = default)
1111
{
@@ -18,7 +18,7 @@ internal sealed class Tests_SimpleTest_TestSource_9c97c20c9f124d82999b00e7d24a75
1818
AttributeFactory = () =>
1919
[
2020
new global::TUnit.Core.TestAttribute(),
21-
new global::TUnit.TestProject.Bugs._2971.SomeAttribute(typeof(T?))
21+
new global::TUnit.TestProject.Bugs._2971.SomeAttribute(typeof(global::System.Nullable<>))
2222
],
2323
DataSources = new global::TUnit.Core.IDataSourceAttribute[]
2424
{
@@ -84,11 +84,11 @@ internal sealed class Tests_SimpleTest_TestSource_9c97c20c9f124d82999b00e7d24a75
8484
yield break;
8585
}
8686
}
87-
internal static class Tests_SimpleTest_ModuleInitializer_9c97c20c9f124d82999b00e7d24a75b1
87+
internal static class Tests_SimpleTest_ModuleInitializer_38be5ee0b5554088ba7b233e82649e27
8888
{
8989
[global::System.Runtime.CompilerServices.ModuleInitializer]
9090
public static void Initialize()
9191
{
92-
global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.Bugs._2971.Tests), new Tests_SimpleTest_TestSource_9c97c20c9f124d82999b00e7d24a75b1());
92+
global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.Bugs._2971.Tests), new Tests_SimpleTest_TestSource_38be5ee0b5554088ba7b233e82649e27());
9393
}
9494
}

TUnit.Core.SourceGenerator/CodeGenerators/Formatting/TypedConstantFormatter.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,6 @@ public string FormatForCode(TypedConstant constant, ITypeSymbol? targetType = nu
2424

2525
case TypedConstantKind.Type:
2626
var type = (ITypeSymbol)constant.Value!;
27-
28-
// Special handling for System.Nullable<> when it would be displayed as "T?"
29-
if (type is INamedTypeSymbol namedType &&
30-
(namedType.SpecialType == SpecialType.System_Nullable_T ||
31-
namedType.ConstructedFrom?.SpecialType == SpecialType.System_Nullable_T) &&
32-
namedType.TypeArguments.Length == 1 &&
33-
namedType.TypeArguments[0].TypeKind == TypeKind.TypeParameter)
34-
{
35-
return "typeof(global::System.Nullable<>)";
36-
}
37-
3827
return $"typeof({type.GloballyQualified()})";
3928

4029
case TypedConstantKind.Array:

TUnit.Core.SourceGenerator/Extensions/TypeExtensions.cs

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -208,35 +208,32 @@ public static bool EnumerableGenericTypeIs(this ITypeSymbol enumerable, Generato
208208

209209
public static string GloballyQualified(this ISymbol typeSymbol)
210210
{
211-
// Special handling for System.Nullable<> generic type definition
212-
// When Roslyn encounters System.Nullable<>, it displays it as "T?" which is not valid C# syntax
213-
if (typeSymbol is INamedTypeSymbol namedTypeSymbol)
211+
// Handle open generic types where type arguments are type parameters
212+
// This prevents invalid C# like List<T>, Dictionary<TKey, TValue>, T? where type parameters are undefined
213+
if (typeSymbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType)
214214
{
215-
// Check if this is System.Nullable<T> where T is unbound/type parameter
216-
if (namedTypeSymbol.SpecialType == SpecialType.System_Nullable_T ||
217-
namedTypeSymbol.ConstructedFrom?.SpecialType == SpecialType.System_Nullable_T)
215+
// Check if this is an unbound generic type or has type parameter arguments
216+
bool hasTypeParameters = namedTypeSymbol.TypeArguments.Any(t => t.TypeKind == TypeKind.TypeParameter);
217+
bool isUnboundGeneric = namedTypeSymbol.IsUnboundGenericType;
218+
219+
if (hasTypeParameters || isUnboundGeneric)
218220
{
219-
// For the unbound generic case like System.Nullable<> or Nullable<T> where T is a type parameter
220-
if (namedTypeSymbol.TypeArguments.Length == 1 &&
221-
namedTypeSymbol.TypeArguments[0].TypeKind == TypeKind.TypeParameter)
221+
// Special case for System.Nullable<> - Roslyn displays it as "T?" even for open generic
222+
if (namedTypeSymbol.SpecialType == SpecialType.System_Nullable_T ||
223+
namedTypeSymbol.ConstructedFrom?.SpecialType == SpecialType.System_Nullable_T)
222224
{
223225
return "global::System.Nullable<>";
224226
}
227+
228+
// General case for other open generic types
229+
var typeBuilder = new StringBuilder(typeSymbol.ToDisplayString(DisplayFormats.FullyQualifiedNonGenericWithGlobalPrefix));
230+
typeBuilder.Append('<');
231+
typeBuilder.Append(new string(',', namedTypeSymbol.TypeArguments.Length - 1));
232+
typeBuilder.Append('>');
233+
234+
return typeBuilder.ToString();
225235
}
226236
}
227-
228-
// Only generate open generic form for types with unresolved type parameters
229-
// This ensures we get BaseClass<> for generic definitions but List<int> for constructed types
230-
if(typeSymbol is INamedTypeSymbol { IsGenericType: true } namedTypeSymbol2 &&
231-
namedTypeSymbol2.TypeArguments.Any(t => t.TypeKind == TypeKind.TypeParameter))
232-
{
233-
var typeBuilder = new StringBuilder(typeSymbol.ToDisplayString(DisplayFormats.FullyQualifiedNonGenericWithGlobalPrefix));
234-
typeBuilder.Append('<');
235-
typeBuilder.Append(new string(',', namedTypeSymbol2.TypeArguments.Length - 1));
236-
typeBuilder.Append('>');
237-
238-
return typeBuilder.ToString();
239-
}
240237

241238
return typeSymbol.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix);
242239
}

0 commit comments

Comments
 (0)