diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs
index d8f5c8a2f..80b71ce00 100644
--- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs
@@ -13,13 +13,15 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.
-using System.Collections.Generic;
+using Microsoft.Python.Analysis.Diagnostics;
using Microsoft.Python.Analysis.Modules;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Analysis.Types.Collections;
using Microsoft.Python.Analysis.Values;
+using Microsoft.Python.Core;
using Microsoft.Python.Parsing;
using Microsoft.Python.Parsing.Ast;
+using ErrorCodes = Microsoft.Python.Analysis.Diagnostics.ErrorCodes;
namespace Microsoft.Python.Analysis.Analyzer.Evaluation {
internal sealed partial class ExpressionEval {
@@ -122,6 +124,9 @@ private IMember GetValueFromBinaryOp(Expression expr) {
if (leftIsSupported && rightIsSupported) {
if (TryGetValueFromBuiltinBinaryOp(op, leftTypeId, rightTypeId, Interpreter.LanguageVersion.Is3x(), out var member)) {
+ if (member.IsUnknown()) {
+ ReportOperatorDiagnostics(expr, leftType, rightType, op);
+ }
return member;
}
}
@@ -435,5 +440,14 @@ private static (string name, string swappedName) OpMethodName(PythonOperator op)
return (null, null);
}
+ private void ReportOperatorDiagnostics(Expression expr, IPythonType leftType, IPythonType rightType, PythonOperator op) {
+ ReportDiagnostics(Module.Uri, new DiagnosticsEntry(
+ Resources.UnsupporedOperandType.FormatInvariant(op.ToCodeString(), leftType.Name, rightType.Name),
+ GetLocation(expr).Span,
+ ErrorCodes.UnsupportedOperandType,
+ Severity.Error,
+ DiagnosticSource.Analysis));
+ }
+
}
}
diff --git a/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs b/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs
index fd6d5f0c9..78acd0234 100644
--- a/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs
+++ b/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs
@@ -25,6 +25,7 @@ public static class ErrorCodes {
public const string UndefinedVariable = "undefined-variable";
public const string VariableNotDefinedGlobally= "variable-not-defined-globally";
public const string VariableNotDefinedNonLocal = "variable-not-defined-nonlocal";
+ public const string UnsupportedOperandType = "unsupported-operand-type";
public const string ReturnInInit = "return-in-init";
public const string TypingGenericArguments = "typing-generic-arguments";
}
diff --git a/src/Analysis/Ast/Impl/Resources.Designer.cs b/src/Analysis/Ast/Impl/Resources.Designer.cs
index df406b0eb..3d8e6a207 100644
--- a/src/Analysis/Ast/Impl/Resources.Designer.cs
+++ b/src/Analysis/Ast/Impl/Resources.Designer.cs
@@ -302,5 +302,14 @@ internal static string UndefinedVariable {
return ResourceManager.GetString("UndefinedVariable", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Unsupported operand types for '{0}': '{1}' and '{2}'.
+ ///
+ internal static string UnsupporedOperandType {
+ get {
+ return ResourceManager.GetString("UnsupporedOperandType", resourceCulture);
+ }
+ }
}
}
diff --git a/src/Analysis/Ast/Impl/Resources.resx b/src/Analysis/Ast/Impl/Resources.resx
index e9a661561..48c944283 100644
--- a/src/Analysis/Ast/Impl/Resources.resx
+++ b/src/Analysis/Ast/Impl/Resources.resx
@@ -189,6 +189,9 @@
Unable to determine analysis cache path. Exception: {0}. Using default '{1}'.
+
+ Unsupported operand types for '{0}': '{1}' and '{2}'
+
Explicit return in __init__
@@ -198,4 +201,4 @@
Arguments to Generic must all be unique.
-
\ No newline at end of file
+
diff --git a/src/Analysis/Ast/Test/LintOperatorTests.cs b/src/Analysis/Ast/Test/LintOperatorTests.cs
new file mode 100644
index 000000000..0578dbdbd
--- /dev/null
+++ b/src/Analysis/Ast/Test/LintOperatorTests.cs
@@ -0,0 +1,86 @@
+using System.Linq;
+using System.Threading.Tasks;
+using FluentAssertions;
+using Microsoft.Python.Analysis.Tests.FluentAssertions;
+using Microsoft.Python.Core;
+using Microsoft.Python.Parsing.Tests;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using TestUtilities;
+
+namespace Microsoft.Python.Analysis.Tests {
+ [TestClass]
+ public class LintOperatorTests : AnalysisTestBase {
+
+ public TestContext TestContext { get; set; }
+
+ [TestInitialize]
+ public void TestInitialize()
+ => TestEnvironmentImpl.TestInitialize($"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}");
+
+ [TestCleanup]
+ public void Cleanup() => TestEnvironmentImpl.TestCleanup();
+
+ [TestMethod, Priority(0)]
+ public async Task IncompatibleTypesBinaryOpBasic() {
+ var code = $@"
+a = 5 + 'str'
+";
+
+ var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X);
+ analysis.Diagnostics.Should().HaveCount(1);
+
+ var diagnostic = analysis.Diagnostics.ElementAt(0);
+ diagnostic.SourceSpan.Should().Be(2, 5, 2, 14);
+ diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.UnsupportedOperandType);
+ diagnostic.Message.Should().Be(Resources.UnsupporedOperandType.FormatInvariant("+", "int", "str"));
+ }
+
+ [DataRow("str", "int", "+")]
+ [DataRow("str", "int", "-")]
+ [DataRow("str", "int", "/")]
+ [DataRow("str", "float", "+")]
+ [DataRow("str", "float", "-")]
+ [DataRow("str", "float", "*")]
+ [DataTestMethod, Priority(0)]
+ public async Task IncompatibleTypesBinaryOp(string leftType, string rightType, string op) {
+ var code = $@"
+x = 1
+y = 2
+
+z = {leftType}(x) {op} {rightType}(y)
+";
+
+ var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X);
+ analysis.Diagnostics.Should().HaveCount(1);
+
+ var diagnostic = analysis.Diagnostics.ElementAt(0);
+
+
+ string line = $"z = {leftType}(x) {op} {rightType}(y)";
+ // source span is 1 indexed
+ diagnostic.SourceSpan.Should().Be(5, line.IndexOf(leftType) + 1, 5, line.IndexOf("(y)") + 4);
+ diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.UnsupportedOperandType);
+ diagnostic.Message.Should().Be(Resources.UnsupporedOperandType.FormatInvariant(op, leftType, rightType));
+ }
+
+ [DataRow("str", "str", "+")]
+ [DataRow("int", "int", "-")]
+ [DataRow("bool", "int", "/")]
+ [DataRow("float", "int", "+")]
+ [DataRow("complex", "float", "-")]
+ [DataRow("str", "int", "*")]
+ [DataTestMethod, Priority(0)]
+ public async Task CompatibleTypesBinaryOp(string leftType, string rightType, string op) {
+ var code = $@"
+x = 1
+y = 2
+
+z = {leftType}(x) {op} {rightType}(y)
+";
+
+ var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X);
+ analysis.Diagnostics.Should().BeEmpty();
+ }
+ }
+
+}