Skip to content
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

Fix 'make member readonly' with inline array fields #70118

Merged
merged 3 commits into from
Sep 26, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -1404,4 +1404,336 @@ private readonly void M(ref byte b) { }
""",
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/70116")]
public async Task TestInlineArraySpan1()
{
await new VerifyCS.Test
{
TestCode = """
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;

public struct Repro
{
public ByteArray20 Data;
public ByteArray20 Hash;

public void RecalculateHash()
{
SHA1.HashData(Data, Hash);
}

[InlineArray(20)]
public struct ByteArray20
{
private byte _byte;
}
}
""",
ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
LanguageVersion = LanguageVersion.CSharp12,
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/70116")]
public async Task TestInlineArraySpan2()
{
await new VerifyCS.Test
{
TestCode = """
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;

public struct Repro
{
public ByteArray20 Data;
public ByteArray20 Hash;

public void RecalculateHash()
{
TakesSpan(Data);
}

readonly void TakesSpan(Span<byte> bytes) { }

[InlineArray(20)]
public struct ByteArray20
{
private byte _byte;
}
}
""",
ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
LanguageVersion = LanguageVersion.CSharp12,
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/70116")]
public async Task TestInlineArraySpan2_A()
{
await new VerifyCS.Test
{
TestCode = """
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;

public struct Repro
{
public ByteArray20 Data;
public ByteArray20 Hash;

public void RecalculateHash()
{
TakesSpan(this.Data);
}

readonly void TakesSpan(Span<byte> bytes) { }

[InlineArray(20)]
public struct ByteArray20
{
private byte _byte;
}
}
""",
ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
LanguageVersion = LanguageVersion.CSharp12,
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/70116")]
public async Task TestInlineArraySpan3()
{
await new VerifyCS.Test
{
TestCode = """
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;

public struct Repro
{
public ByteArray20 Data;
public ByteArray20 Hash;

public void [|RecalculateHash|]()
{
TakesReadOnlySpan(Data);
}

readonly void TakesReadOnlySpan(ReadOnlySpan<byte> bytes) { }

[InlineArray(20)]
public struct ByteArray20
{
private byte _byte;
}
}
""",
FixedCode = """
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;

public struct Repro
{
public ByteArray20 Data;
public ByteArray20 Hash;

public readonly void RecalculateHash()
{
TakesReadOnlySpan(Data);
}

readonly void TakesReadOnlySpan(ReadOnlySpan<byte> bytes) { }

[InlineArray(20)]
public struct ByteArray20
{
private byte _byte;
}
}
""",
ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
LanguageVersion = LanguageVersion.CSharp12,
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/70116")]
public async Task TestInlineArraySpan3_A()
{
await new VerifyCS.Test
{
TestCode = """
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;

public struct Repro
{
public ByteArray20 Data;
public ByteArray20 Hash;

public void [|RecalculateHash|]()
{
TakesReadOnlySpan(this.Data);
}

readonly void TakesReadOnlySpan(ReadOnlySpan<byte> bytes) { }

[InlineArray(20)]
public struct ByteArray20
{
private byte _byte;
}
}
""",
FixedCode = """
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;

public struct Repro
{
public ByteArray20 Data;
public ByteArray20 Hash;

public readonly void RecalculateHash()
{
TakesReadOnlySpan(this.Data);
}

readonly void TakesReadOnlySpan(ReadOnlySpan<byte> bytes) { }

[InlineArray(20)]
public struct ByteArray20
{
private byte _byte;
}
}
""",
ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
LanguageVersion = LanguageVersion.CSharp12,
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/70116")]
public async Task TestInlineArraySpan4()
{
await new VerifyCS.Test
{
TestCode = """
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;

public struct Repro
{
public ByteArray20 Data;
public ByteArray20 Hash;

public void [|RecalculateHash|]()
{
TakesByteArray(Data);
}

readonly void TakesByteArray(ByteArray20 bytes) { }

[InlineArray(20)]
public struct ByteArray20
{
private byte _byte;
}
}
""",
FixedCode = """
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;

public struct Repro
{
public ByteArray20 Data;
public ByteArray20 Hash;

public readonly void RecalculateHash()
{
TakesByteArray(Data);
}

readonly void TakesByteArray(ByteArray20 bytes) { }

[InlineArray(20)]
public struct ByteArray20
{
private byte _byte;
}
}
""",
ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
LanguageVersion = LanguageVersion.CSharp12,
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/70116")]
public async Task TestInlineArraySpan4_A()
{
await new VerifyCS.Test
{
TestCode = """
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;

public struct Repro
{
public ByteArray20 Data;
public ByteArray20 Hash;

public void [|RecalculateHash|]()
{
TakesByteArray(this.Data);
}

readonly void TakesByteArray(ByteArray20 bytes) { }

[InlineArray(20)]
public struct ByteArray20
{
private byte _byte;
}
}
""",
FixedCode = """
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;

public struct Repro
{
public ByteArray20 Data;
public ByteArray20 Hash;

public readonly void RecalculateHash()
{
TakesByteArray(this.Data);
}

readonly void TakesByteArray(ByteArray20 bytes) { }

[InlineArray(20)]
public struct ByteArray20
{
private byte _byte;
}
}
""",
ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
LanguageVersion = LanguageVersion.CSharp12,
}.RunAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,20 @@ public static bool IsWrittenTo(
}
}

// An inline array passed as a Span<T> can be written into by the callee, despite no ref at the callsite. e.g.:
//
// void Mutate(Span<byte> bytes);
// Mutate(this.inlineArray)
if (expression.Parent is ArgumentSyntax)
{
var expressionTypes = semanticModel.GetTypeInfo(expression, cancellationToken);
if (expressionTypes.ConvertedType.IsSpan() &&
expressionTypes.Type.IsInlineArray())
{
return true;
}
}

return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,8 @@ public static bool CanRemoveParentheses(
{
// We have either `var x = (stackalloc byte[8])` or `Span<byte> x = (stackalloc byte[8])`. The former
// is not safe to remove. the latter is.
if (semanticModel.GetTypeInfo(varDecl.Type, cancellationToken).Type is
{
Name: nameof(Span<int>) or nameof(ReadOnlySpan<int>),
ContainingNamespace: { Name: nameof(System), ContainingNamespace.IsGlobalNamespace: true }
})
{
if (semanticModel.GetTypeInfo(varDecl.Type, cancellationToken).Type.IsSpanOrReadOnlySpan())
return !varDecl.Type.IsVar;
}
}

return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -729,5 +729,25 @@ public static ITypeSymbol WithNullableAnnotationFrom(this ITypeSymbol type, ITyp

return symbol;
}

public static bool IsSpanOrReadOnlySpan([NotNullWhen(true)] this ITypeSymbol? type)
=> type is INamedTypeSymbol
{
Name: nameof(Span<int>) or nameof(ReadOnlySpan<int>),
TypeArguments.Length: 1,
ContainingNamespace: { Name: nameof(System), ContainingNamespace.IsGlobalNamespace: true }
};

public static bool IsSpan([NotNullWhen(true)] this ITypeSymbol? type)
=> type is INamedTypeSymbol
{
Name: nameof(Span<int>),
TypeArguments.Length: 1,
ContainingNamespace: { Name: nameof(System), ContainingNamespace.IsGlobalNamespace: true }
};

public static bool IsInlineArray([NotNullWhen(true)] this ITypeSymbol? type)
=> type is INamedTypeSymbol namedType &&
namedType.OriginalDefinition.GetAttributes().Any(static a => a.AttributeClass?.SpecialType == SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute);
}
}