From fa49bf5b748593964a0dd1fc03ca7fd80068148b Mon Sep 17 00:00:00 2001 From: Cameron Trando Date: Mon, 24 Jun 2019 13:17:06 -0700 Subject: [PATCH 1/5] Adding diagnostic error on binary operations on incompatible types --- .../Evaluation/ExpressionEval.Operators.cs | 17 +++++- .../Ast/Impl/Diagnostics/ErrorCodes.cs | 1 + src/Analysis/Ast/Impl/Resources.Designer.cs | 9 +++ src/Analysis/Ast/Impl/Resources.resx | 3 + src/Analysis/Ast/Test/LintOperatorTests.cs | 60 +++++++++++++++++++ 5 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 src/Analysis/Ast/Test/LintOperatorTests.cs diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs index b9676e19b..ffbca2f57 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs @@ -13,16 +13,28 @@ // 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 { + + 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)); + } + private IMember GetValueFromUnaryOp(UnaryExpression expr) { switch (expr.Op) { case PythonOperator.Not: @@ -122,6 +134,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; } } diff --git a/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs b/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs index 45c6a1407..48c3e54fe 100644 --- a/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs +++ b/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs @@ -25,5 +25,6 @@ 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"; } } diff --git a/src/Analysis/Ast/Impl/Resources.Designer.cs b/src/Analysis/Ast/Impl/Resources.Designer.cs index 785cbf9a8..f40ba6abb 100644 --- a/src/Analysis/Ast/Impl/Resources.Designer.cs +++ b/src/Analysis/Ast/Impl/Resources.Designer.cs @@ -275,5 +275,14 @@ internal static string UndefinedVariable { return ResourceManager.GetString("UndefinedVariable", resourceCulture); } } + + /// + /// Looks up a localized string similar to Unsupported operand type(s) 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 95cd89002..7cb60e594 100644 --- a/src/Analysis/Ast/Impl/Resources.resx +++ b/src/Analysis/Ast/Impl/Resources.resx @@ -189,4 +189,7 @@ Unable to determine analysis cache path. Exception: {0}. Using default '{1}'. + + Unsupported operand type(s) for '{0}': '{1}' and '{2}' + \ 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..8b9f91ef5 --- /dev/null +++ b/src/Analysis/Ast/Test/LintOperatorTests.cs @@ -0,0 +1,60 @@ +using FluentAssertions; +using Microsoft.Python.Parsing.Tests; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using System.Threading.Tasks; +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 IncompatibleTypesBasic() { + var code = $@" +a = 5 + 'str' +"; + + var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.UnsupportedOperandType); + diagnostic.Message.Should().Be(Resources.UnsupporedOperandType); + } + + [DataRow("str", "int", "+")] + [DataRow("str", "int", "-")] + [DataRow("str", "int", "/")] + [DataRow("str", "float", "+")] + [DataRow("str", "float", "-")] + [DataRow("str", "float", "*")] + [DataTestMethod, Priority(0)] + public async Task IncompatibleTypes(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); + diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.UnsupportedOperandType); + diagnostic.Message.Should().Be(Resources.UnsupporedOperandType); + + } + } + +} From c792f079606cefe119344b9ebe86b0169bada69f Mon Sep 17 00:00:00 2001 From: Cameron Trando Date: Wed, 26 Jun 2019 14:29:45 -0700 Subject: [PATCH 2/5] Updating position of method, fixing tests, adding some more for valid cases --- .../Evaluation/ExpressionEval.Operators.cs | 19 ++++++++------- src/Analysis/Ast/Impl/Resources.Designer.cs | 2 +- src/Analysis/Ast/Impl/Resources.resx | 2 +- src/Analysis/Ast/Test/LintOperatorTests.cs | 23 +++++++++++++++++-- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs index ffbca2f57..3ecf4db7b 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs @@ -25,16 +25,6 @@ namespace Microsoft.Python.Analysis.Analyzer.Evaluation { internal sealed partial class ExpressionEval { - - 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)); - } - private IMember GetValueFromUnaryOp(UnaryExpression expr) { switch (expr.Op) { case PythonOperator.Not: @@ -450,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/Resources.Designer.cs b/src/Analysis/Ast/Impl/Resources.Designer.cs index f40ba6abb..a18265896 100644 --- a/src/Analysis/Ast/Impl/Resources.Designer.cs +++ b/src/Analysis/Ast/Impl/Resources.Designer.cs @@ -277,7 +277,7 @@ internal static string UndefinedVariable { } /// - /// Looks up a localized string similar to Unsupported operand type(s) for '{0}': '{1}' and '{2}'. + /// Looks up a localized string similar to Unsupported operand types for '{0}': '{1}' and '{2}'. /// internal static string UnsupporedOperandType { get { diff --git a/src/Analysis/Ast/Impl/Resources.resx b/src/Analysis/Ast/Impl/Resources.resx index 7cb60e594..dfea6550a 100644 --- a/src/Analysis/Ast/Impl/Resources.resx +++ b/src/Analysis/Ast/Impl/Resources.resx @@ -190,6 +190,6 @@ Unable to determine analysis cache path. Exception: {0}. Using default '{1}'. - Unsupported operand type(s) for '{0}': '{1}' and '{2}' + Unsupported operand types for '{0}': '{1}' and '{2}' \ No newline at end of file diff --git a/src/Analysis/Ast/Test/LintOperatorTests.cs b/src/Analysis/Ast/Test/LintOperatorTests.cs index 8b9f91ef5..006249d03 100644 --- a/src/Analysis/Ast/Test/LintOperatorTests.cs +++ b/src/Analysis/Ast/Test/LintOperatorTests.cs @@ -3,6 +3,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Linq; using System.Threading.Tasks; +using Microsoft.Python.Core; using TestUtilities; namespace Microsoft.Python.Analysis.Tests { @@ -29,7 +30,7 @@ public async Task IncompatibleTypesBasic() { var diagnostic = analysis.Diagnostics.ElementAt(0); diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.UnsupportedOperandType); - diagnostic.Message.Should().Be(Resources.UnsupporedOperandType); + diagnostic.Message.Should().Be(Resources.UnsupporedOperandType.FormatInvariant("+", "int", "str")); } [DataRow("str", "int", "+")] @@ -52,8 +53,26 @@ public async Task IncompatibleTypes(string leftType, string rightType, string op var diagnostic = analysis.Diagnostics.ElementAt(0); diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.UnsupportedOperandType); - diagnostic.Message.Should().Be(Resources.UnsupporedOperandType); + 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 CompatibleTypes(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(0); } } From 806bc49e9222a70081dbfb11477a0ebe0528e312 Mon Sep 17 00:00:00 2001 From: Cameron Trando Date: Mon, 1 Jul 2019 10:35:16 -0700 Subject: [PATCH 3/5] Adding tests for source span --- src/Analysis/Ast/Test/LintOperatorTests.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Analysis/Ast/Test/LintOperatorTests.cs b/src/Analysis/Ast/Test/LintOperatorTests.cs index 006249d03..1d4963b0c 100644 --- a/src/Analysis/Ast/Test/LintOperatorTests.cs +++ b/src/Analysis/Ast/Test/LintOperatorTests.cs @@ -1,6 +1,7 @@ using FluentAssertions; using Microsoft.Python.Parsing.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Python.Analysis.Tests.FluentAssertions; using System.Linq; using System.Threading.Tasks; using Microsoft.Python.Core; @@ -29,6 +30,7 @@ public async Task IncompatibleTypesBasic() { 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")); } @@ -52,6 +54,11 @@ public async Task IncompatibleTypes(string leftType, string rightType, string op 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)); } @@ -72,7 +79,7 @@ public async Task CompatibleTypes(string leftType, string rightType, string op) "; var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X); - analysis.Diagnostics.Should().HaveCount(0); + analysis.Diagnostics.Should().BeEmpty(); } } From ec900849a966a883fad1bffd7e7c4916674c6b19 Mon Sep 17 00:00:00 2001 From: Cameron Trando Date: Tue, 2 Jul 2019 12:13:19 -0700 Subject: [PATCH 4/5] Renaming tests --- src/Analysis/Ast/Test/LintOperatorTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Analysis/Ast/Test/LintOperatorTests.cs b/src/Analysis/Ast/Test/LintOperatorTests.cs index 1d4963b0c..e9d6086d3 100644 --- a/src/Analysis/Ast/Test/LintOperatorTests.cs +++ b/src/Analysis/Ast/Test/LintOperatorTests.cs @@ -21,7 +21,7 @@ public void TestInitialize() public void Cleanup() => TestEnvironmentImpl.TestCleanup(); [TestMethod, Priority(0)] - public async Task IncompatibleTypesBasic() { + public async Task IncompatibleTypesBinaryOpBasic() { var code = $@" a = 5 + 'str' "; @@ -42,7 +42,7 @@ public async Task IncompatibleTypesBasic() { [DataRow("str", "float", "-")] [DataRow("str", "float", "*")] [DataTestMethod, Priority(0)] - public async Task IncompatibleTypes(string leftType, string rightType, string op) { + public async Task IncompatibleTypesBinaryOp(string leftType, string rightType, string op) { var code = $@" x = 1 y = 2 @@ -70,7 +70,7 @@ public async Task IncompatibleTypes(string leftType, string rightType, string op [DataRow("complex", "float", "-")] [DataRow("str", "int", "*")] [DataTestMethod, Priority(0)] - public async Task CompatibleTypes(string leftType, string rightType, string op) { + public async Task CompatibleTypesBinaryOp(string leftType, string rightType, string op) { var code = $@" x = 1 y = 2 From 8d289d8a9fb3878434e49a321b86b5b8797d9e10 Mon Sep 17 00:00:00 2001 From: Cameron Trando Date: Tue, 2 Jul 2019 12:14:14 -0700 Subject: [PATCH 5/5] fixing import order --- src/Analysis/Ast/Test/LintOperatorTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Analysis/Ast/Test/LintOperatorTests.cs b/src/Analysis/Ast/Test/LintOperatorTests.cs index e9d6086d3..0578dbdbd 100644 --- a/src/Analysis/Ast/Test/LintOperatorTests.cs +++ b/src/Analysis/Ast/Test/LintOperatorTests.cs @@ -1,10 +1,10 @@ -using FluentAssertions; -using Microsoft.Python.Parsing.Tests; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Microsoft.Python.Analysis.Tests.FluentAssertions; -using System.Linq; +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 {