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(); + } + } + +}