Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Commit 721de14

Browse files
committed
When creating a class, give diagnostic if bases are not class types
1 parent 5f322f0 commit 721de14

File tree

6 files changed

+251
-14
lines changed

6 files changed

+251
-14
lines changed

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

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
using System.Collections.Generic;
1717
using System.Linq;
1818
using Microsoft.Python.Analysis.Analyzer.Evaluation;
19+
using Microsoft.Python.Analysis.Diagnostics;
1920
using Microsoft.Python.Analysis.Types;
2021
using Microsoft.Python.Analysis.Values;
2122
using Microsoft.Python.Core;
23+
using Microsoft.Python.Parsing;
2224
using Microsoft.Python.Parsing.Ast;
2325

2426
namespace Microsoft.Python.Analysis.Analyzer.Symbols {
@@ -50,22 +52,13 @@ public void EvaluateClass() {
5052
EvaluateInnerClasses(_classDef);
5153

5254
_class = classInfo;
53-
// Set bases to the class.
54-
var bases = new List<IPythonType>();
55-
foreach (var a in _classDef.Bases.Where(a => string.IsNullOrEmpty(a.Name))) {
56-
// We cheat slightly and treat base classes as annotations.
57-
var b = Eval.GetTypeFromAnnotation(a.Expression);
58-
if (b != null) {
59-
var t = b.GetPythonType();
60-
bases.Add(t);
61-
t.AddReference(Eval.GetLocationOfName(a.Expression));
62-
}
63-
}
64-
_class.SetBases(bases);
6555

56+
List<IPythonType> bases = new List<IPythonType>();
57+
ProcessBases(bases, outerScope);
58+
59+
_class.SetBases(bases);
6660
// Declare __class__ variable in the scope.
6761
Eval.DeclareVariable("__class__", _class, VariableSource.Declaration);
68-
6962
ProcessClassBody();
7063
}
7164
}
@@ -118,6 +111,51 @@ private void ProcessClassBody() {
118111
UpdateClassMembers();
119112
}
120113

114+
private void ProcessBases(List<IPythonType> bases, Scope outerScope) {
115+
foreach (var a in _classDef.Bases.Where(a => string.IsNullOrEmpty(a.Name))) {
116+
var expr = a.Expression;
117+
118+
// A base can either be the name of a class declared in the current module or a class declared in some other module
119+
switch (expr) {
120+
// class declared in the current module
121+
case NameExpression nameExpression:
122+
var name = Eval.GetInScope(nameExpression.Name, outerScope);
123+
switch (name) {
124+
case PythonClassType classType:
125+
bases.Add(classType);
126+
break;
127+
case IPythonConstant constant:
128+
ReportInvalidBase(constant.Value);
129+
break;
130+
default:
131+
TryAddBase(bases, a);
132+
break;
133+
}
134+
break;
135+
default:
136+
TryAddBase(bases, a);
137+
break;
138+
}
139+
}
140+
}
141+
142+
private IPythonType TryAddBase(List<IPythonType> bases, Arg arg) {
143+
var b = Eval.GetTypeFromAnnotation(arg.Expression);
144+
if (b != null) {
145+
var t = b.GetPythonType();
146+
bases.Add(t);
147+
t.AddReference(Eval.GetLocationOfName(arg.Expression));
148+
return t;
149+
} else {
150+
ReportInvalidBase(arg.ToCodeString(Eval.Ast, CodeFormattingOptions.Traditional));
151+
return null;
152+
}
153+
}
154+
155+
156+
157+
158+
121159
private void EvaluateConstructors(ClassDefinition cd) {
122160
// Do not use foreach since walker list is dynamically modified and walkers are removed
123161
// after processing. Handle __init__ and __new__ first so class variables are initialized.
@@ -152,6 +190,17 @@ private void UpdateClassMembers() {
152190
_class.AddMembers(members, false);
153191
}
154192

193+
private void ReportInvalidBase(object argVal) {
194+
Eval.ReportDiagnostics(Eval.Module.Uri,
195+
new DiagnosticsEntry(
196+
Resources.InheritNonClass.FormatInvariant(argVal),
197+
_classDef.NameExpression.GetLocation(Eval)?.Span ?? default,
198+
Diagnostics.ErrorCodes.InheritNonClass,
199+
Severity.Error,
200+
DiagnosticSource.Analysis
201+
));
202+
}
203+
155204
// Classes and functions are walked by their respective evaluators
156205
public override bool Walk(ClassDefinition node) => false;
157206
public override bool Walk(FunctionDefinition node) => false;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ public static class ErrorCodes {
2929
public const string ReturnInInit = "return-in-init";
3030
public const string TypingNewTypeArguments = "typing-newtype-arguments";
3131
public const string TypingGenericArguments = "typing-generic-arguments";
32+
public const string InheritNonClass = "inherit-non-class";
3233
}
3334
}

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
@@ -204,4 +204,7 @@
204204
<data name="GenericNotAllUnique" xml:space="preserve">
205205
<value>Arguments to Generic must all be unique.</value>
206206
</data>
207-
</root>
207+
<data name="InheritNonClass" xml:space="preserve">
208+
<value>Inheriting '{0}', which is not a class.</value>
209+
</data>
210+
</root>

src/Analysis/Ast/Impl/Types/PythonClassType.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
using System.Collections.Generic;
1818
using System.Diagnostics;
1919
using System.Linq;
20+
using Microsoft.Python.Analysis.Analyzer;
21+
using Microsoft.Python.Analysis.Diagnostics;
2022
using Microsoft.Python.Analysis.Modules;
2123
using Microsoft.Python.Analysis.Specializations.Typing;
2224
using Microsoft.Python.Analysis.Types.Collections;
@@ -190,6 +192,7 @@ internal void SetBases(IEnumerable<IPythonType> bases) {
190192
}
191193

192194
bases = bases != null ? bases.Where(b => !b.GetPythonType().IsUnknown()).ToArray() : Array.Empty<IPythonType>();
195+
193196
// For Python 3+ attach object as a base class by default except for the object class itself.
194197
if (DeclaringModule.Interpreter.LanguageVersion.Is3x() && DeclaringModule.ModuleType != ModuleType.Builtins) {
195198
var objectType = DeclaringModule.Interpreter.GetBuiltinType(BuiltinTypeId.Object);
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
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.Diagnostics;
20+
using Microsoft.Python.Analysis.Tests.FluentAssertions;
21+
using Microsoft.Python.Core;
22+
using Microsoft.Python.Parsing.Tests;
23+
using Microsoft.VisualStudio.TestTools.UnitTesting;
24+
using TestUtilities;
25+
26+
namespace Microsoft.Python.Analysis.Tests {
27+
[TestClass]
28+
public class InheritNonClassTests : AnalysisTestBase {
29+
public TestContext TestContext { get; set; }
30+
31+
[TestInitialize]
32+
public void TestInitialize()
33+
=> TestEnvironmentImpl.TestInitialize($"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}");
34+
35+
[TestCleanup]
36+
public void Cleanup() => TestEnvironmentImpl.TestCleanup();
37+
38+
[TestMethod, Priority(0)]
39+
public async Task InheritFromRenamedBuiltin() {
40+
const string code = @"
41+
tmp = str
42+
43+
class C(tmp):
44+
def method(self):
45+
return 'test'
46+
";
47+
var analysis = await GetAnalysisAsync(code);
48+
analysis.Diagnostics.Should().BeEmpty();
49+
}
50+
51+
52+
[TestMethod, Priority(0)]
53+
public async Task InheritFromBuiltin() {
54+
const string code = @"
55+
class C(str):
56+
def method(self):
57+
return 'test'
58+
";
59+
var analysis = await GetAnalysisAsync(code);
60+
analysis.Diagnostics.Should().BeEmpty();
61+
}
62+
63+
64+
[TestMethod, Priority(0)]
65+
public async Task InheritFromUserClass() {
66+
const string code = @"
67+
class D:
68+
def hello(self):
69+
pass
70+
71+
class C(D):
72+
def method(self):
73+
return 'test'
74+
";
75+
var analysis = await GetAnalysisAsync(code);
76+
analysis.Diagnostics.Should().BeEmpty();
77+
}
78+
79+
80+
[TestMethod, Priority(0)]
81+
public async Task InheritFromConstant() {
82+
const string code = @"
83+
class C(5):
84+
def method(self):
85+
return 'test'
86+
";
87+
var analysis = await GetAnalysisAsync(code);
88+
analysis.Diagnostics.Should().HaveCount(1);
89+
90+
var diagnostic = analysis.Diagnostics.ElementAt(0);
91+
diagnostic.SourceSpan.Should().Be(2, 7, 2, 8);
92+
diagnostic.Message.Should().Be(Resources.InheritNonClass.FormatInvariant("5"));
93+
diagnostic.ErrorCode.Should().Be(ErrorCodes.InheritNonClass);
94+
}
95+
96+
97+
[TestMethod, Priority(0)]
98+
public async Task InheritFromConstantVar() {
99+
const string code = @"
100+
x = 'str'
101+
102+
class C(x):
103+
def method(self):
104+
return 'test'
105+
106+
x = 5
107+
108+
class D(x):
109+
def method(self):
110+
return 'test'
111+
";
112+
var analysis = await GetAnalysisAsync(code);
113+
analysis.Diagnostics.Should().HaveCount(2);
114+
115+
var diagnostic = analysis.Diagnostics.ElementAt(0);
116+
diagnostic.SourceSpan.Should().Be(4, 7, 4, 8);
117+
diagnostic.Message.Should().Be(Resources.InheritNonClass.FormatInvariant("str"));
118+
diagnostic.ErrorCode.Should().Be(ErrorCodes.InheritNonClass);
119+
120+
diagnostic = analysis.Diagnostics.ElementAt(1);
121+
diagnostic.SourceSpan.Should().Be(10, 7, 10, 8);
122+
diagnostic.Message.Should().Be(Resources.InheritNonClass.FormatInvariant("5"));
123+
diagnostic.ErrorCode.Should().Be(ErrorCodes.InheritNonClass);
124+
}
125+
126+
[TestMethod, Priority(0)]
127+
public async Task InheritFromBinaryOp() {
128+
const string code = @"
129+
x = 5
130+
131+
class C(x + 2):
132+
def method(self):
133+
return 'test'
134+
";
135+
var analysis = await GetAnalysisAsync(code);
136+
analysis.Diagnostics.Should().HaveCount(1);
137+
138+
var diagnostic = analysis.Diagnostics.ElementAt(0);
139+
diagnostic.SourceSpan.Should().Be(4, 7, 4, 8);
140+
diagnostic.Message.Should().Be(Resources.InheritNonClass.FormatInvariant("x + 2"));
141+
diagnostic.ErrorCode.Should().Be(ErrorCodes.InheritNonClass);
142+
}
143+
144+
145+
[TestMethod, Priority(0)]
146+
public async Task InheritFromOtherModule() {
147+
const string code = @"
148+
import typing
149+
150+
class C(typing.TypeVar):
151+
def method(self):
152+
return 'test'
153+
";
154+
var analysis = await GetAnalysisAsync(code);
155+
analysis.Diagnostics.Should().BeEmpty();
156+
}
157+
158+
[TestMethod, Priority(0)]
159+
public async Task InheritFromRenamedOtherModule() {
160+
const string code = @"
161+
import typing
162+
163+
tmp = typing.TypeVar
164+
class C(tmp):
165+
def method(self):
166+
return 'test'
167+
";
168+
var analysis = await GetAnalysisAsync(code);
169+
analysis.Diagnostics.Should().BeEmpty();
170+
}
171+
}
172+
}

0 commit comments

Comments
 (0)