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

Commit 5d56c83

Browse files
authored
When returning in init method, report a diagnostic error (microsoft#1261)
* When returning in init method, report a diagnostic error * Adding check and tests for returning None before adding error for a return statement in the init function * Cleaning up tests
1 parent c646be4 commit 5d56c83

File tree

5 files changed

+155
-1
lines changed

5 files changed

+155
-1
lines changed

src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
using System.Diagnostics;
1919
using System.Linq;
2020
using Microsoft.Python.Analysis.Analyzer.Evaluation;
21-
using Microsoft.Python.Analysis.Extensions;
21+
using Microsoft.Python.Analysis.Diagnostics;
2222
using Microsoft.Python.Analysis.Modules;
2323
using Microsoft.Python.Analysis.Types;
2424
using Microsoft.Python.Analysis.Values;
2525
using Microsoft.Python.Analysis.Values.Collections;
2626
using Microsoft.Python.Core;
27+
using Microsoft.Python.Parsing;
2728
using Microsoft.Python.Parsing.Ast;
29+
using ErrorCodes = Microsoft.Python.Analysis.Diagnostics.ErrorCodes;
2830

2931
namespace Microsoft.Python.Analysis.Analyzer.Symbols {
3032
[DebuggerDisplay("{FunctionDefinition.Name}")]
@@ -116,6 +118,18 @@ public override bool Walk(AssignmentStatement node) {
116118
public override bool Walk(ReturnStatement node) {
117119
var value = Eval.GetValueFromExpression(node.Expression);
118120
if (value != null) {
121+
// although technically legal, __init__ in a constructor should not have a not-none return value
122+
if (FunctionDefinition.Name.EqualsOrdinal("__init__") && _function.DeclaringType.MemberType == PythonMemberType.Class
123+
&& !value.IsOfType(BuiltinTypeId.NoneType)) {
124+
125+
Eval.ReportDiagnostics(Module.Uri, new Diagnostics.DiagnosticsEntry(
126+
Resources.ReturnInInit,
127+
node.GetLocation(Module).Span,
128+
ErrorCodes.ReturnInInit,
129+
Severity.Warning,
130+
DiagnosticSource.Analysis));
131+
}
132+
119133
_overload.AddReturnValue(value);
120134
}
121135
return true; // We want to evaluate all code so all private variables in __new__ get defined

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ 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 ReturnInInit = "return-in-init";
2829
}
2930
}

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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,7 @@
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="ReturnInInit" xml:space="preserve">
193+
<value>Explicit return in __init__ </value>
194+
</data>
192195
</root>
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Copyright(c) Microsoft Corporation
2+
// All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the License); you may not use
5+
// this file except in compliance with the License. You may obtain a copy of the
6+
// License at http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
9+
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
10+
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
11+
// MERCHANTABILITY OR NON-INFRINGEMENT.
12+
//
13+
// See the Apache Version 2.0 License for specific language governing
14+
// permissions and limitations under the License.
15+
16+
using System.Linq;
17+
using System.Threading.Tasks;
18+
using FluentAssertions;
19+
using Microsoft.Python.Analysis.Tests.FluentAssertions;
20+
using Microsoft.Python.Parsing;
21+
using Microsoft.Python.Parsing.Tests;
22+
using Microsoft.VisualStudio.TestTools.UnitTesting;
23+
using TestUtilities;
24+
25+
namespace Microsoft.Python.Analysis.Tests {
26+
[TestClass]
27+
public class LintReturnInInitTests : AnalysisTestBase {
28+
public TestContext TestContext { get; set; }
29+
30+
[TestInitialize]
31+
public void TestInitialize()
32+
=> TestEnvironmentImpl.TestInitialize($"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}");
33+
34+
[TestCleanup]
35+
public void Cleanup() => TestEnvironmentImpl.TestCleanup();
36+
37+
[TestMethod, Priority(0)]
38+
public async Task ReturnInInit() {
39+
const string code = @"
40+
class Rectangle:
41+
def __init__(self, width, height):
42+
self.width = width
43+
self.height = height
44+
self.area = width * height
45+
return self.area
46+
47+
r = Rectangle(10, 10)
48+
";
49+
var analysis = await GetAnalysisAsync(code);
50+
analysis.Diagnostics.Should().HaveCount(1);
51+
52+
var diagnostic = analysis.Diagnostics.ElementAt(0);
53+
diagnostic.SourceSpan.Should().Be(7, 9, 7, 25);
54+
diagnostic.Severity.Should().Be(Severity.Warning);
55+
diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.ReturnInInit);
56+
diagnostic.Message.Should().Be(Resources.ReturnInInit);
57+
}
58+
59+
[TestMethod, Priority(0)]
60+
public async Task ReturnInitBasic() {
61+
const string code = @"
62+
class Rectangle:
63+
def __init__(self, width, height):
64+
return 2
65+
66+
r = Rectangle(10, 10)
67+
";
68+
var analysis = await GetAnalysisAsync(code);
69+
analysis.Diagnostics.Should().HaveCount(1);
70+
71+
var diagnostic = analysis.Diagnostics.ElementAt(0);
72+
diagnostic.SourceSpan.Should().Be(4, 9, 4, 17);
73+
diagnostic.Severity.Should().Be(Severity.Warning);
74+
diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.ReturnInInit);
75+
diagnostic.Message.Should().Be(Resources.ReturnInInit);
76+
}
77+
78+
[TestMethod, Priority(0)]
79+
public async Task ReturnInInitConditional() {
80+
const string code = @"
81+
class A:
82+
def __init__(self, x):
83+
self.x = x
84+
if x > 0:
85+
return 10
86+
87+
a = A(1)
88+
";
89+
var analysis = await GetAnalysisAsync(code);
90+
analysis.Diagnostics.Should().HaveCount(1);
91+
92+
var diagnostic = analysis.Diagnostics.ElementAt(0);
93+
diagnostic.SourceSpan.Should().Be(6, 13, 6, 22);
94+
diagnostic.Severity.Should().Be(Severity.Warning);
95+
diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.ReturnInInit);
96+
diagnostic.Message.Should().Be(Resources.ReturnInInit);
97+
}
98+
99+
[TestMethod, Priority(0)]
100+
public async Task ReturnNoneInInit() {
101+
const string code = @"
102+
class A:
103+
def __init__(self, x):
104+
self.x = x
105+
self.x += 1
106+
return None
107+
108+
a = A(1)
109+
";
110+
var analysis = await GetAnalysisAsync(code);
111+
analysis.Diagnostics.Should().BeEmpty();
112+
}
113+
114+
[TestMethod, Priority(0)]
115+
public async Task EmptyReturnInInit() {
116+
const string code = @"
117+
class A:
118+
def __init__(self, x):
119+
self.x = x
120+
return
121+
a = A(1)
122+
";
123+
var analysis = await GetAnalysisAsync(code);
124+
analysis.Diagnostics.Should().BeEmpty();
125+
}
126+
}
127+
}

0 commit comments

Comments
 (0)