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
16 changes: 16 additions & 0 deletions TUnit.Core.SourceGenerator/Generators/AotConverterGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,22 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
return null;
}

// Skip conversion operators where the containing type is not publicly accessible
// The generated code won't be able to reference private/internal types
if (containingType.DeclaredAccessibility != Accessibility.Public)
{
return null;
}

// Also skip if the target type of the conversion is not publicly accessible
// (unless it's a built-in type)
if (targetType is INamedTypeSymbol namedTargetType &&
namedTargetType.SpecialType == SpecialType.None &&
namedTargetType.DeclaredAccessibility != Accessibility.Public)
{
return null;
}

return new ConversionInfo
{
ContainingType = containingType,
Expand Down
29 changes: 29 additions & 0 deletions TUnit.TestProject/Bugs/Issue2993/CompilationFailureTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace TUnit.TestProject.Bugs.Issue2993;

internal class CompilationFailureTests
{
private record TestItem(int? Value)
{
public static implicit operator TestItem(int? value) => new(value);
}

[Test]
public async Task AssertEmptyIsEmpty()
{
var items = Enumerable.Empty<TestItem>();
await Assert.That(items).IsEmpty();
}

// Also test with non-nullable int to verify both work
private record TestItem2(int Value)
{
public static implicit operator TestItem2(int value) => new(value);
}

[Test]
public async Task AssertEmptyIsEmpty_NonNullable()
{
var items = Enumerable.Empty<TestItem2>();
await Assert.That(items).IsEmpty();
}
}
79 changes: 79 additions & 0 deletions TUnit.TestProject/Bugs/Issue2993/ImplicitConversionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
namespace TUnit.TestProject.Bugs.Issue2993;

/// <summary>
/// Tests for issue #2993: Private types with implicit int? operator cause compilation failure
/// https://github.com/thomhurst/TUnit/issues/2993
/// </summary>
internal class ImplicitConversionTests
{
// Test with nullable value type
private record NullableIntRecord(int? Value)
{
public static implicit operator NullableIntRecord(int? value) => new(value);
public static implicit operator int?(NullableIntRecord record) => record?.Value;
}

[Test]
public async Task PrivateType_WithNullableIntImplicitOperator_ShouldCompile()
{
var items = Enumerable.Empty<NullableIntRecord>();
await Assert.That(items).IsEmpty();
}

[Test]
public async Task PrivateType_WithNullableIntImplicitOperator_NonEmptyCollection()
{
NullableIntRecord item1 = 42;
NullableIntRecord item2 = null;
var items = new[] { item1, item2 };

await Assert.That(items).IsNotEmpty();
await Assert.That(items).HasCount(2);
}

// Test with non-nullable value type
private record IntRecord(int Value)
{
public static implicit operator IntRecord(int value) => new(value);
public static implicit operator int(IntRecord record) => record.Value;
}

[Test]
public async Task PrivateType_WithIntImplicitOperator_ShouldCompile()
{
var items = Enumerable.Empty<IntRecord>();
await Assert.That(items).IsEmpty();
}

// Test with nullable reference type
private record StringRecord(string? Value)
{
public static implicit operator StringRecord(string? value) => new(value);
public static implicit operator string?(StringRecord record) => record?.Value;
}

[Test]
public async Task PrivateType_WithNullableStringImplicitOperator_ShouldCompile()
{
var items = Enumerable.Empty<StringRecord>();
await Assert.That(items).IsEmpty();
}

// Test with nested private type
private class OuterClass
{
internal record InnerRecord(double? Value)
{
public static implicit operator InnerRecord(double? value) => new(value);
}

public static IEnumerable<InnerRecord> GetEmptyCollection() => Enumerable.Empty<InnerRecord>();
}

[Test]
public async Task NestedPrivateType_WithImplicitOperator_ShouldCompile()
{
var items = OuterClass.GetEmptyCollection();
await Assert.That(items).IsEmpty();
}
}
Loading