Skip to content
This repository was archived by the owner on Nov 4, 2024. It is now read-only.

Commit 31afbdd

Browse files
authored
Adding diagnostic error on binary operations with incompatible types (microsoft#1254)
* Adding diagnostic error for binary operations with incompatible types - e.g 5 + 'str'
1 parent 5ed3821 commit 31afbdd

File tree

5 files changed

+115
-2
lines changed

5 files changed

+115
-2
lines changed

src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
// See the Apache Version 2.0 License for specific language governing
1414
// permissions and limitations under the License.
1515

16-
using System.Collections.Generic;
16+
using Microsoft.Python.Analysis.Diagnostics;
1717
using Microsoft.Python.Analysis.Modules;
1818
using Microsoft.Python.Analysis.Types;
1919
using Microsoft.Python.Analysis.Types.Collections;
2020
using Microsoft.Python.Analysis.Values;
21+
using Microsoft.Python.Core;
2122
using Microsoft.Python.Parsing;
2223
using Microsoft.Python.Parsing.Ast;
24+
using ErrorCodes = Microsoft.Python.Analysis.Diagnostics.ErrorCodes;
2325

2426
namespace Microsoft.Python.Analysis.Analyzer.Evaluation {
2527
internal sealed partial class ExpressionEval {
@@ -122,6 +124,9 @@ private IMember GetValueFromBinaryOp(Expression expr) {
122124

123125
if (leftIsSupported && rightIsSupported) {
124126
if (TryGetValueFromBuiltinBinaryOp(op, leftTypeId, rightTypeId, Interpreter.LanguageVersion.Is3x(), out var member)) {
127+
if (member.IsUnknown()) {
128+
ReportOperatorDiagnostics(expr, leftType, rightType, op);
129+
}
125130
return member;
126131
}
127132
}
@@ -435,5 +440,14 @@ private static (string name, string swappedName) OpMethodName(PythonOperator op)
435440

436441
return (null, null);
437442
}
443+
private void ReportOperatorDiagnostics(Expression expr, IPythonType leftType, IPythonType rightType, PythonOperator op) {
444+
ReportDiagnostics(Module.Uri, new DiagnosticsEntry(
445+
Resources.UnsupporedOperandType.FormatInvariant(op.ToCodeString(), leftType.Name, rightType.Name),
446+
GetLocation(expr).Span,
447+
ErrorCodes.UnsupportedOperandType,
448+
Severity.Error,
449+
DiagnosticSource.Analysis));
450+
}
451+
438452
}
439453
}

src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public static class ErrorCodes {
2525
public const string UndefinedVariable = "undefined-variable";
2626
public const string VariableNotDefinedGlobally= "variable-not-defined-globally";
2727
public const string VariableNotDefinedNonLocal = "variable-not-defined-nonlocal";
28+
public const string UnsupportedOperandType = "unsupported-operand-type";
2829
public const string ReturnInInit = "return-in-init";
2930
public const string TypingGenericArguments = "typing-generic-arguments";
3031
}

src/Analysis/Ast/Impl/Resources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Analysis/Ast/Impl/Resources.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@
189189
<data name="UnableToDetermineCachePathException" xml:space="preserve">
190190
<value>Unable to determine analysis cache path. Exception: {0}. Using default '{1}'.</value>
191191
</data>
192+
<data name="UnsupporedOperandType" xml:space="preserve">
193+
<value>Unsupported operand types for '{0}': '{1}' and '{2}'</value>
194+
</data>
192195
<data name="ReturnInInit" xml:space="preserve">
193196
<value>Explicit return in __init__ </value>
194197
</data>
@@ -198,4 +201,4 @@
198201
<data name="GenericNotAllUnique" xml:space="preserve">
199202
<value>Arguments to Generic must all be unique.</value>
200203
</data>
201-
</root>
204+
</root>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using System.Linq;
2+
using System.Threading.Tasks;
3+
using FluentAssertions;
4+
using Microsoft.Python.Analysis.Tests.FluentAssertions;
5+
using Microsoft.Python.Core;
6+
using Microsoft.Python.Parsing.Tests;
7+
using Microsoft.VisualStudio.TestTools.UnitTesting;
8+
using TestUtilities;
9+
10+
namespace Microsoft.Python.Analysis.Tests {
11+
[TestClass]
12+
public class LintOperatorTests : AnalysisTestBase {
13+
14+
public TestContext TestContext { get; set; }
15+
16+
[TestInitialize]
17+
public void TestInitialize()
18+
=> TestEnvironmentImpl.TestInitialize($"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}");
19+
20+
[TestCleanup]
21+
public void Cleanup() => TestEnvironmentImpl.TestCleanup();
22+
23+
[TestMethod, Priority(0)]
24+
public async Task IncompatibleTypesBinaryOpBasic() {
25+
var code = $@"
26+
a = 5 + 'str'
27+
";
28+
29+
var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X);
30+
analysis.Diagnostics.Should().HaveCount(1);
31+
32+
var diagnostic = analysis.Diagnostics.ElementAt(0);
33+
diagnostic.SourceSpan.Should().Be(2, 5, 2, 14);
34+
diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.UnsupportedOperandType);
35+
diagnostic.Message.Should().Be(Resources.UnsupporedOperandType.FormatInvariant("+", "int", "str"));
36+
}
37+
38+
[DataRow("str", "int", "+")]
39+
[DataRow("str", "int", "-")]
40+
[DataRow("str", "int", "/")]
41+
[DataRow("str", "float", "+")]
42+
[DataRow("str", "float", "-")]
43+
[DataRow("str", "float", "*")]
44+
[DataTestMethod, Priority(0)]
45+
public async Task IncompatibleTypesBinaryOp(string leftType, string rightType, string op) {
46+
var code = $@"
47+
x = 1
48+
y = 2
49+
50+
z = {leftType}(x) {op} {rightType}(y)
51+
";
52+
53+
var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X);
54+
analysis.Diagnostics.Should().HaveCount(1);
55+
56+
var diagnostic = analysis.Diagnostics.ElementAt(0);
57+
58+
59+
string line = $"z = {leftType}(x) {op} {rightType}(y)";
60+
// source span is 1 indexed
61+
diagnostic.SourceSpan.Should().Be(5, line.IndexOf(leftType) + 1, 5, line.IndexOf("(y)") + 4);
62+
diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.UnsupportedOperandType);
63+
diagnostic.Message.Should().Be(Resources.UnsupporedOperandType.FormatInvariant(op, leftType, rightType));
64+
}
65+
66+
[DataRow("str", "str", "+")]
67+
[DataRow("int", "int", "-")]
68+
[DataRow("bool", "int", "/")]
69+
[DataRow("float", "int", "+")]
70+
[DataRow("complex", "float", "-")]
71+
[DataRow("str", "int", "*")]
72+
[DataTestMethod, Priority(0)]
73+
public async Task CompatibleTypesBinaryOp(string leftType, string rightType, string op) {
74+
var code = $@"
75+
x = 1
76+
y = 2
77+
78+
z = {leftType}(x) {op} {rightType}(y)
79+
";
80+
81+
var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X);
82+
analysis.Diagnostics.Should().BeEmpty();
83+
}
84+
}
85+
86+
}

0 commit comments

Comments
 (0)