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
60 changes: 60 additions & 0 deletions src/Meziantou.Analyzer/Internals/TypeSymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,66 @@ public static bool IsNumberType(this ITypeSymbol? symbol)
}
}

/// <summary>
/// Determines whether the type is a blittable type.
/// Blittable types: byte, sbyte, short, ushort, int, uint, long, ulong,
/// float, double, IntPtr, UIntPtr, pointers, enums, and structs
/// containing only blittable fields.
/// </summary>
public static bool IsBlittableType(this ITypeSymbol? symbol)
{
if (symbol is null)
return false;

switch (symbol.SpecialType)
{
case SpecialType.System_Byte:
case SpecialType.System_SByte:
case SpecialType.System_Int16:
case SpecialType.System_UInt16:
case SpecialType.System_Int32:
case SpecialType.System_UInt32:
case SpecialType.System_Int64:
case SpecialType.System_UInt64:
case SpecialType.System_Single:
case SpecialType.System_Double:
case SpecialType.System_IntPtr:
case SpecialType.System_UIntPtr:
return true;

case SpecialType.None:
break;

default:
return false;
}

if (symbol.TypeKind is TypeKind.Pointer)
return true;

if (symbol is INamedTypeSymbol namedType)
{
if (namedType.EnumUnderlyingType is not null)
return true;

if (namedType.IsValueType)
{
foreach (var member in namedType.GetMembers())
{
if (member is not IFieldSymbol field || field.IsConst || field.IsStatic)
continue;

if (!field.Type.IsBlittableType())
return false;
}

return true;
}
}

return false;
}

public static bool IsUnitTestClass(this ITypeSymbol typeSymbol)
{
var attributes = typeSymbol.GetAttributes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ private static void Analyze(SymbolAnalysisContext context)
if (member.IsConst || member.IsStatic)
continue;

if (member.Type.IsReferenceType)
return; // When a struct contains a reference type field, the layout is automatically changed to Auto
if (!member.Type.IsBlittableType())
return;

memberCount++;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,165 @@ await CreateProjectBuilder()
.ValidateAsync();
}

[Fact]
public async Task WithBoolFields_ShouldNotReportDiagnostic()
{
const string SourceCode = """
struct TypeName
{
bool a;
bool b;
}
""";
await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}

[Fact]
public async Task WithCharFields_ShouldNotReportDiagnostic()
{
const string SourceCode = """
struct TypeName
{
char a;
char b;
}
""";
await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}

[Fact]
public async Task WithDecimalFields_ShouldNotReportDiagnostic()
{
const string SourceCode = """
struct TypeName
{
decimal a;
decimal b;
}
""";
await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}

[Fact]
public async Task WithIntPtrFields_ShouldReportDiagnostic()
{
const string SourceCode = """
struct [|TypeName|]
{
nint a;
nint b;
}
""";
await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}

[Fact]
public async Task WithUIntPtrFields_ShouldReportDiagnostic()
{
const string SourceCode = """
struct [|TypeName|]
{
nuint a;
nuint b;
}
""";
await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}

[Fact]
public async Task WithFloatAndDoubleFields_ShouldReportDiagnostic()
{
const string SourceCode = """
struct [|TypeName|]
{
float a;
double b;
}
""";
await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}

[Fact]
public async Task WithEnumFields_ShouldReportDiagnostic()
{
const string SourceCode = """
enum MyEnum { A, B }
struct [|TypeName|]
{
MyEnum a;
MyEnum b;
}
""";
await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}

[Fact]
public async Task WithBlittableNestedStruct_ShouldReportDiagnostic()
{
const string SourceCode = """
struct Inner
{
int x;
}
struct [|TypeName|]
{
Inner a;
int b;
}
""";
await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}

[Fact]
public async Task WithNonBlittableNestedStruct_ShouldNotReportDiagnostic()
{
const string SourceCode = """
struct Inner
{
bool x;
}
struct TypeName
{
Inner a;
int b;
}
""";
await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}

[Fact]
public async Task WithMixedBlittableAndNonBlittable_ShouldNotReportDiagnostic()
{
const string SourceCode = """
struct TypeName
{
int a;
bool b;
}
""";
await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}

#if CSHARP10_OR_GREATER
[Fact]
public async Task RecordStruct()
Expand Down
Loading