-
Notifications
You must be signed in to change notification settings - Fork 280
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
BUG: CC0118 - Unnecessary '.ToString()' call in string concatenation #866
Comments
Would result in |
Yes, this is a bug. It is confirmed. We should not offer a diagnostic in this scenario, which is your option 1. |
The underlying problem is the overload resolution of the + operator as described in the spec chapters Binary operator overload resolution and Addition operator. The overload resolution is quite complicated and involves user defined operators, lifting, implicit conversions, delegate combination and enumeration addition. The following code shows some of the problems: using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Addition has precedence over string concatination:");
Console.WriteLine($"1 + 2.ToString() : { 1 + 2.ToString() } ");
Console.WriteLine($"1 + 2 : { 1 + 2} ");
Console.WriteLine("\nDelegate combination has precedence over string concatination:");
var ea1 = new EventHandler((o, e) => { });
var ea2 = new EventHandler((o, e) => { });
Console.WriteLine($"ea1 + ea2.ToString() : { ea1 + ea2.ToString() }");
Console.WriteLine($"ea1 + ea2 : { ea1 + ea2}");
Console.WriteLine("\nUser defined operator has precedence over string concatination:");
var p1 = new Point { X = 1, Y = 2 };
var p2 = new Point { X = 100, Y = 200 };
Console.WriteLine($"p1 + p2.ToString() : { p1 + p2.ToString() }");
Console.WriteLine($"p1 + p2 : { p1 + p2 }");
Console.WriteLine("\nCompilergenerated enum operator has precedence over string concatination:");
Console.WriteLine($"AttributeTargets.Assembly + 1.ToString() : { AttributeTargets.Assembly + 1.ToString() }");
Console.WriteLine($"AttributeTargets.Assembly + 1 : { AttributeTargets.Assembly + 1 }");
Console.ReadLine();
}
}
public class Point
{
public int X;
public int Y;
public override string ToString() => $"X={X}, Y={Y}";
public static Point operator +(Point a, Point b) => new Point { X = a.X + b.X, Y = a.Y + b.Y };
}
} It produces the following output:
Look here to see in action. It is hard to predict the consequences of the removal of the In the var semModel = await document.GetSemanticModelAsync();
// pick the symbolinfo of the addexpression. The symbol info points to
// Method System.String System.String.op_Addition(System.Object left, System.String right)
var sInfo = semModel.GetSymbolInfo(addExpression);
// refactor the code
// recalcuate the semantic Model and get the symbolInfo of the addexpression for the refactored code
var sInfoAfter = semModel.GetSymbolInfo(addExpressionAfter);
// sInfoAfter should point als to one of the System.String.op_Addition overloads
// In the cases above it points to
// Method System.Int32 System.Int32.op_Addition(System.Int32 left, System.Int32 right)
// Method System.EventHandler System.EventHandler.op_Addition(System.EventHandler left, System.EventHandler right)
// Method ConsoleApplication1.Point ConsoleApplication1.Point.op_Addition(ConsoleApplication1.Point a, ConsoleApplication1.Point b)
// Method System.AttributeTargets System.AttributeTargets.op_Addition(System.AttributeTargets left, System.Int32 right) I think it is easier to go for option 2 and check whether the refactoring leads the use of an different + operator method. @giggio If you like I could create a pull request with tests and changes in the |
@MaStr11 Your comment makes sense. But we cannot offer a diagnostic if it does not make sense, otherwise it will be hanging there forever, annoying the user. If the call to |
I see three options here:
If a false positive is detected we could use a #pragma warning disable (and make an explanatory comment) to disable the diagnostic.
We could try to predict the outcome of the Fix by looking up of the existing op_Addition overloads of the types on both sides of the + operator. Then we could try to predict whether an other overload might take precedence. I have no idea whether this will work but this might be worth a try.
|
@MaStr11 Option 2 will demand a careful implementation, but I think it might work, and is the best option by far (if it works). You want to try it out? |
The string is only removed if safe to do. Fixes #866. Implemented option 2 as requested in #866. This fix now looks up the types of both sides of the plus operator and tries removes those candidates, that might break things. It turned out, that some of the checks in CheckAddOperationOverloadsOfTypes are not needed (After the second check every other check returns false). Essentially right now the removal of "ToString" is only save if either the underlying type is a string (as in 1 + "a".ToString()) or the type on the other side of the plus operator is a string (as in "a" + 1.ToString()). I kept all the other checks for future enhancements to make clear that there are several cases to consider. Further I added lots of unit tests to cover these cases. The idea is that CheckAddOperationOverloadsOfTypes can easily be expanded in the future to support more cases without breaking anything. Those redundant checks are also very cheap and are essentially just simple comparisons.
This is a fix in a new analyzer, so we I corrected the milestone. |
It is little bit more complicated while concatenating 'tostringed' objects only.
Example:
CC0118 is detected and code after fix looks like this:
which is incorrect.
Solution 1: Do not detect CC0118 in such situations.
Solution 2: Fix needs to be smarter.
This is problematic primarily when using solution-wide fix because of possibly large number of wrong fixes.
The text was updated successfully, but these errors were encountered: