Skip to content

Commit 675fca8

Browse files
sailroCopilot
andcommitted
Fix #3895: keep byref-like ToString in string concatenation
String concatenation cannot box a byref-like value, so removing its explicit ToString call produces invalid C#. Preserve that call while retaining the surrounding string-concatenation reconstruction. Assisted-by: Copilot:gpt-5.6-sol:GitHub Copilot CLI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 86d2918e-5a24-48b4-9a86-41d331ec3720
1 parent 7554c1b commit 675fca8

3 files changed

Lines changed: 46 additions & 0 deletions

File tree

ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,12 @@ public async Task AnonymousTypes([ValueSource(nameof(defaultOptionsWithMcs))] Co
338338
await RunForLibrary(cscOptions: cscOptions);
339339
}
340340

341+
[Test]
342+
public async Task StringConcatenation([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions)
343+
{
344+
await RunForLibrary(cscOptions: cscOptions);
345+
}
346+
341347
[Test]
342348
public async Task Async([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
343349
{
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) 2026 Sebastien Lebreton
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4+
// software and associated documentation files (the "Software"), to deal in the Software
5+
// without restriction, including without limitation the rights to use, copy, modify, merge,
6+
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7+
// to whom the Software is furnished to do so, subject to the following conditions:
8+
//
9+
// The above copyright notice and this permission notice shall be included in all copies or
10+
// substantial portions of the Software.
11+
//
12+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14+
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17+
// DEALINGS IN THE SOFTWARE.
18+
19+
using System;
20+
21+
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
22+
{
23+
public static class StringConcatenation
24+
{
25+
public static string WithSpan(Span<char> value)
26+
{
27+
return "prefix" + value.ToString();
28+
}
29+
30+
public static string WithReadOnlySpan(ReadOnlySpan<char> value)
31+
{
32+
return "prefix" + value.ToString();
33+
}
34+
}
35+
}

ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,11 @@ internal static Expression RemoveRedundantToStringInConcat(Expression expr, IMet
366366
var toStringMethod = m.Get<Expression>("call").Single().GetSymbol() as IMethod;
367367
var target = m.Get<Expression>("target").Single();
368368
var type = target.GetResolveResult().Type;
369+
if (type.IsByRefLike)
370+
{
371+
// ref structs cannot be converted to object for use with +
372+
return expr;
373+
}
369374
if (!(isLastArgument || ToStringIsKnownEffectFree(type)))
370375
{
371376
// ToString() order of evaluation matters, see CheckArgumentsForStringConcat().

0 commit comments

Comments
 (0)