Skip to content

Commit

Permalink
[GH-57] - proper way of checking value assignment in ref/out methods
Browse files Browse the repository at this point in the history
  • Loading branch information
tpodolak committed Dec 22, 2018
1 parent cbeafd3 commit 7b06554
Show file tree
Hide file tree
Showing 28 changed files with 280 additions and 289 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,11 @@ protected override bool CanCast(Compilation compilation, ITypeSymbol sourceSymbo
{
return compilation.ClassifyConversion(sourceSymbol, destinationSymbol).Exists;
}

protected override bool IsAssignableTo(Compilation compilation, ITypeSymbol fromSymbol, ITypeSymbol toSymbol)
{
var conversion = compilation.ClassifyConversion(fromSymbol, toSymbol);
return conversion.IsExplicit == false && conversion.IsNumeric == false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ public override void Initialize(AnalysisContext context)

protected abstract bool CanCast(Compilation compilation, ITypeSymbol sourceSymbol, ITypeSymbol destinationSymbol);

protected abstract bool IsAssignableTo(Compilation compilation, ITypeSymbol fromSymbol, ITypeSymbol toSymbol);

private void AnalyzeInvocation(SyntaxNodeAnalysisContext syntaxNodeContext)
{
var invocationExpression = (TInvocationExpressionSyntax)syntaxNodeContext.Node;
Expand Down Expand Up @@ -245,7 +247,7 @@ private bool AnalyzeAssignment(SyntaxNodeAnalysisContext syntaxNodeContext, ILis
}

var typeInfo = syntaxNodeContext.SemanticModel.GetTypeInfo(assignmentExpressionSyntax);
if (typeInfo.Type != null && Equals(typeInfo.Type, substituteCallParameters[position.Value].Type) == false)
if (typeInfo.Type != null && IsAssignableTo(syntaxNodeContext.Compilation, typeInfo.Type, substituteCallParameters[position.Value].Type) == false)
{
var diagnostic = Diagnostic.Create(
DiagnosticDescriptorsProvider.CallInfoArgumentSetWithIncompatibleValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ protected override bool CanCast(Compilation compilation, ITypeSymbol sourceSymbo
return compilation.ClassifyConversion(sourceSymbol, destinationSymbol).Exists;
}

protected override bool IsAssignableTo(Compilation compilation, ITypeSymbol fromSymbol, ITypeSymbol toSymbol)
{
var conversion = compilation.ClassifyConversion(fromSymbol, toSymbol);
return conversion.IsWidening == false && conversion.IsNumeric == false;
}

private static int? ExtractPositionFromInvocation(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, InvocationExpressionSyntax invocationExpressionSyntax)
{
var argAtPosition = syntaxNodeAnalysisContext.SemanticModel.GetConstantValue(invocationExpressionSyntax.ArgumentList.Arguments.First().GetExpression());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers;
Expand All @@ -6,6 +7,7 @@

namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.CallInfoAnalyzerTests
{
[SuppressMessage("ReSharper", "xUnit1013", Justification = "Reviewed")]
public abstract class CallInfoDiagnosticVerifier : CSharpDiagnosticVerifier, ICallInfoDiagnosticVerifier
{
public abstract Task ReportsNoDiagnostics_WhenSubstituteMethodCannotBeInferred(string call, string argAccess);
Expand Down Expand Up @@ -42,9 +44,21 @@ public abstract class CallInfoDiagnosticVerifier : CSharpDiagnosticVerifier, ICa

public abstract Task ReportsDiagnostic_WhenAssigningValueToOutOfBoundsArgument();

public abstract Task ReportsDiagnostic_WhenAssigningWrongTypeToArgument();

public abstract Task ReportsNoDiagnostic_WhenAssigningProperTypeToArgument();
[Theory]
[InlineData("decimal", "1", "Could not set value of type int to argument 0 (decimal) because the types are incompatible.")]
[InlineData("string", "new object()", "Could not set value of type object to argument 0 (string) because the types are incompatible.")]
public abstract Task ReportsDiagnostic_WhenAssigningWrongTypeToArgument(string left, string right, string expectedMessage);

[Theory]
[InlineData("object", @"""string""")]
[InlineData("int", "1")]
[InlineData("int?", "1")]
[InlineData("decimal", "1M")]
[InlineData("double", "1D")]
[InlineData("IEnumerable<object>", "new List<object>()")]
[InlineData("List<object>", "new object[] { new object() }")]
[InlineData("IDictionary<string, object>", "new Dictionary<string, object>()")]
public abstract Task ReportsNoDiagnostic_WhenAssigningType_AssignableTo_Argument(string left, string right);

protected override DiagnosticAnalyzer GetDiagnosticAnalyzer()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -764,38 +764,37 @@ public void Test()
await VerifyDiagnostic(source, expectedDiagnostic);
}

[Fact]
public override async Task ReportsDiagnostic_WhenAssigningWrongTypeToArgument()
public override async Task ReportsDiagnostic_WhenAssigningWrongTypeToArgument(string left, string right, string expectedMessage)
{
var source = @"using NSubstitute;
var source = $@"using NSubstitute;
namespace MyNamespace
{
{{
public interface Foo
{
int Bar(out decimal x);
}
{{
int Bar(out {left} x);
}}
public class FooTests
{
{{
public void Test()
{
decimal value = 0;
{{
{left} value = default({left});
var substitute = NSubstitute.Substitute.For<Foo>();
substitute.Bar(out value).Returns(callInfo =>
{
callInfo[0] = 1;
{{
callInfo[0] = {right};
return 1;
});
}
}
}";
}});
}}
}}
}}";

var expectedDiagnostic = new DiagnosticResult
{
Id = DiagnosticIdentifiers.CallInfoArgumentSetWithIncompatibleValue,
Severity = DiagnosticSeverity.Warning,
Message = "Could not set value of type int to argument 0 (decimal) because the types are incompatible.",
Message = expectedMessage,
Locations = new[]
{
new DiagnosticResultLocation(18, 17)
Expand All @@ -805,32 +804,32 @@ public void Test()
await VerifyDiagnostic(source, expectedDiagnostic);
}

[Fact]
public override async Task ReportsNoDiagnostic_WhenAssigningProperTypeToArgument()
public override async Task ReportsNoDiagnostic_WhenAssigningType_AssignableTo_Argument(string left, string right)
{
var source = @"using NSubstitute;
var source = $@"using NSubstitute;
using System.Collections.Generic;
namespace MyNamespace
{
{{
public interface Foo
{
int Bar(out decimal x);
}
{{
int Bar(out {left} x);
}}
public class FooTests
{
{{
public void Test()
{
decimal value = 0;
{{
{left} value = default({left});
var substitute = NSubstitute.Substitute.For<Foo>();
substitute.Bar(out value).Returns(callInfo =>
{
callInfo[0] = 1M;
{{
callInfo[0] = {right};
return 1;
});
}
}
}";
}});
}}
}}
}}";

await VerifyDiagnostic(source);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -764,8 +764,7 @@ public void Test()
await VerifyDiagnostic(source, expectedDiagnostic);
}

[Fact]
public override async Task ReportsDiagnostic_WhenAssigningWrongTypeToArgument()
public override async Task ReportsDiagnostic_WhenAssigningWrongTypeToArgument(string left, string right, string expectedMessage)
{
var source = @"using NSubstitute;
Expand Down Expand Up @@ -805,32 +804,32 @@ public void Test()
await VerifyDiagnostic(source, expectedDiagnostic);
}

[Fact]
public override async Task ReportsNoDiagnostic_WhenAssigningProperTypeToArgument()
public override async Task ReportsNoDiagnostic_WhenAssigningType_AssignableTo_Argument(string left, string right)
{
var source = @"using NSubstitute;
var source = $@"using NSubstitute;
using System.Collections.Generic;
namespace MyNamespace
{
{{
public interface Foo
{
int Bar(out decimal x);
}
{{
int Bar(out {left} x);
}}
public class FooTests
{
{{
public void Test()
{
decimal value = 0;
{{
{left} value = default({left});
var substitute = NSubstitute.Substitute.For<Foo>();
substitute.Bar(out value).Returns<int>(callInfo =>
{
callInfo[0] = 1M;
{{
callInfo[0] = {right};
return 1;
});
}
}
}";
}});
}}
}}
}}";

await VerifyDiagnostic(source);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -764,8 +764,7 @@ public void Test()
await VerifyDiagnostic(source, expectedDiagnostic);
}

[Fact]
public override async Task ReportsDiagnostic_WhenAssigningWrongTypeToArgument()
public override async Task ReportsDiagnostic_WhenAssigningWrongTypeToArgument(string left, string right, string expectedMessage)
{
var source = @"using NSubstitute;
Expand Down Expand Up @@ -805,33 +804,32 @@ public void Test()
await VerifyDiagnostic(source, expectedDiagnostic);
}

[Fact]
public override async Task ReportsNoDiagnostic_WhenAssigningProperTypeToArgument()
public override async Task ReportsNoDiagnostic_WhenAssigningType_AssignableTo_Argument(string left, string right)
{
var source = @"using NSubstitute;
var source = $@"using NSubstitute;
using System.Collections.Generic;
namespace MyNamespace
{
{{
public interface Foo
{
int Bar(out decimal x);
}
{{
int Bar(out {left} x);
}}
public class FooTests
{
{{
public void Test()
{
decimal value = 0;
{{
{left} value = default({left});
var substitute = NSubstitute.Substitute.For<Foo>();
SubstituteExtensions.Returns(substitute.Bar(out value), callInfo =>
{
callInfo[0] = 1M;
{{
callInfo[0] = {right};
return 1;
});
}
}
}";

}});
}}
}}
}}";
await VerifyDiagnostic(source);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -764,8 +764,7 @@ public void Test()
await VerifyDiagnostic(source, expectedDiagnostic);
}

[Fact]
public override async Task ReportsDiagnostic_WhenAssigningWrongTypeToArgument()
public override async Task ReportsDiagnostic_WhenAssigningWrongTypeToArgument(string left, string right, string expectedMessage)
{
var source = @"using NSubstitute;
Expand Down Expand Up @@ -805,33 +804,32 @@ public void Test()
await VerifyDiagnostic(source, expectedDiagnostic);
}

[Fact]
public override async Task ReportsNoDiagnostic_WhenAssigningProperTypeToArgument()
public override async Task ReportsNoDiagnostic_WhenAssigningType_AssignableTo_Argument(string left, string right)
{
var source = @"using NSubstitute;
var source = $@"using NSubstitute;
using System.Collections.Generic;
namespace MyNamespace
{
{{
public interface Foo
{
int Bar(out decimal x);
}
{{
int Bar(out {left} x);
}}
public class FooTests
{
{{
public void Test()
{
decimal value = 0;
{{
{left} value = default({left});
var substitute = NSubstitute.Substitute.For<Foo>();
SubstituteExtensions.Returns<int>(substitute.Bar(out value), callInfo =>
{
callInfo[0] = 1M;
{{
callInfo[0] = {right};
return 1;
});
}
}
}";

}});
}}
}}
}}";
await VerifyDiagnostic(source);
}
}
Expand Down
Loading

0 comments on commit 7b06554

Please sign in to comment.