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

Commit ea052e3

Browse files
CTrandoMikhail Arkhipov
authored and
Mikhail Arkhipov
committed
On redefinition of function, give a diagnostic message (microsoft#1273)
* Doing preliminary function redefinition check, need to add testS * Fixing up error message and cleaning up code * Adding some light tests * Reordering * Adding some more tests on function redefinition * Switching to nicer syntax in SymbolCollector * Renaming test files * Adding redefined tests when importing as well * handling properties too * Switching to switch statement * Minor syntax change * Unused import * Switching type out for interface
1 parent 706943b commit ea052e3

File tree

5 files changed

+385
-15
lines changed

5 files changed

+385
-15
lines changed

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

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using System.Collections.Generic;
1818
using System.Linq;
1919
using Microsoft.Python.Analysis.Analyzer.Evaluation;
20+
using Microsoft.Python.Analysis.Diagnostics;
2021
using Microsoft.Python.Analysis.Types;
2122
using Microsoft.Python.Analysis.Values;
2223
using Microsoft.Python.Core;
@@ -98,19 +99,29 @@ private PythonClassType CreateClass(ClassDefinition cd) {
9899

99100
private void AddFunctionOrProperty(FunctionDefinition fd) {
100101
var declaringType = fd.Parent != null && _typeMap.TryGetValue(fd.Parent, out var t) ? t : null;
101-
if (!TryAddProperty(fd, declaringType)) {
102-
AddFunction(fd, declaringType);
102+
var existing = _eval.LookupNameInScopes(fd.Name, LookupOptions.Local);
103+
104+
switch (existing?.MemberType) {
105+
case PythonMemberType.Method:
106+
case PythonMemberType.Function:
107+
case PythonMemberType.Property:
108+
ReportRedefinedFunction(fd, existing as PythonType);
109+
break;
110+
}
111+
112+
if (!TryAddProperty(fd, existing, declaringType)) {
113+
AddFunction(fd, existing, declaringType);
103114
}
104115
}
105116

106-
private void AddFunction(FunctionDefinition fd, IPythonType declaringType) {
107-
if (!(_eval.LookupNameInScopes(fd.Name, LookupOptions.Local) is PythonFunctionType existing)) {
108-
existing = new PythonFunctionType(fd, declaringType, _eval.GetLocationOfName(fd));
117+
private void AddFunction(FunctionDefinition fd, IMember existing, IPythonType declaringType) {
118+
if (!(existing is PythonFunctionType f)) {
119+
f = new PythonFunctionType(fd, declaringType, _eval.GetLocationOfName(fd));
109120
// The variable is transient (non-user declared) hence it does not have location.
110121
// Function type is tracking locations for references and renaming.
111-
_eval.DeclareVariable(fd.Name, existing, VariableSource.Declaration);
122+
_eval.DeclareVariable(fd.Name, f, VariableSource.Declaration);
112123
}
113-
AddOverload(fd, existing, o => existing.AddOverload(o));
124+
AddOverload(fd, f, o => f.AddOverload(o));
114125
}
115126

116127
private void AddOverload(FunctionDefinition fd, IPythonClassMember function, Action<IPythonFunctionOverload> addOverload) {
@@ -148,31 +159,31 @@ private PythonFunctionOverload GetOverloadFromStub(FunctionDefinition node) {
148159
return null;
149160
}
150161

151-
private bool TryAddProperty(FunctionDefinition node, IPythonType declaringType) {
162+
private bool TryAddProperty(FunctionDefinition node, IMember existing, IPythonType declaringType) {
152163
var dec = node.Decorators?.Decorators;
153164
var decorators = dec != null ? dec.ExcludeDefault().ToArray() : Array.Empty<Expression>();
154165

155166
foreach (var d in decorators.OfType<NameExpression>()) {
156167
switch (d.Name) {
157168
case @"property":
158-
AddProperty(node, declaringType, false);
169+
AddProperty(node, existing, declaringType, false);
159170
return true;
160171
case @"abstractproperty":
161-
AddProperty(node, declaringType, true);
172+
AddProperty(node, existing, declaringType, true);
162173
return true;
163174
}
164175
}
165176
return false;
166177
}
167178

168-
private void AddProperty(FunctionDefinition fd, IPythonType declaringType, bool isAbstract) {
169-
if (!(_eval.LookupNameInScopes(fd.Name, LookupOptions.Local) is PythonPropertyType existing)) {
170-
existing = new PythonPropertyType(fd, _eval.GetLocationOfName(fd), declaringType, isAbstract);
179+
private void AddProperty(FunctionDefinition fd, IMember existing, IPythonType declaringType, bool isAbstract) {
180+
if (!(existing is PythonPropertyType p)) {
181+
p = new PythonPropertyType(fd, _eval.GetLocationOfName(fd), declaringType, isAbstract);
171182
// The variable is transient (non-user declared) hence it does not have location.
172183
// Property type is tracking locations for references and renaming.
173-
_eval.DeclareVariable(fd.Name, existing, VariableSource.Declaration);
184+
_eval.DeclareVariable(fd.Name, p, VariableSource.Declaration);
174185
}
175-
AddOverload(fd, existing, o => existing.AddOverload(o));
186+
AddOverload(fd, p, o => p.AddOverload(o));
176187
}
177188

178189
private IMember GetMemberFromStub(string name) {
@@ -202,6 +213,22 @@ private IMember GetMemberFromStub(string name) {
202213
return member;
203214
}
204215

216+
private void ReportRedefinedFunction(FunctionDefinition redefined, ILocatedMember existing) {
217+
// get line number of existing function for diagnostic message
218+
var existingLoc = existing.Definition;
219+
var existingLine = existingLoc.Span.Start.Line;
220+
221+
_eval.ReportDiagnostics(
222+
_eval.Module.Uri,
223+
new DiagnosticsEntry(
224+
Resources.FunctionRedefined.FormatInvariant(existingLine),
225+
// only highlight the redefined name
226+
_eval.GetLocationInfo(redefined.NameExpression).Span,
227+
ErrorCodes.FunctionRedefined,
228+
Parsing.Severity.Error,
229+
DiagnosticSource.Analysis));
230+
}
231+
205232
private static bool IsDeprecated(ClassDefinition cd)
206233
=> cd.Decorators?.Decorators != null && IsDeprecated(cd.Decorators.Decorators);
207234

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ 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 FunctionRedefined = "function-redefined";
29+
public const string UnsupportedOperandType = "unsupported-operand-type";
2830
public const string ReturnInInit = "return-in-init";
2931
public const string TypingNewTypeArguments = "typing-newtype-arguments";
3032
public const string TypingGenericArguments = "typing-generic-arguments";

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,6 +189,9 @@
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="FunctionRedefined" xml:space="preserve">
193+
<value>Function already defined at line {0}.</value>
194+
</data>
192195
<data name="NewTypeFirstArgNotString" xml:space="preserve">
193196
<value>The first argument to NewType must be a string, but it is of type '{0}'.</value>
194197
</data>

0 commit comments

Comments
 (0)