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

Commit 4ece7cb

Browse files
authored
When NewType is called and the first argument is not a string, make a diagnostic message (#1260)
* Adding diagnostic error if the user calls NewType with the first arg not of string type
1 parent 753220f commit 4ece7cb

File tree

5 files changed

+172
-16
lines changed

5 files changed

+172
-16
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public static class ErrorCodes {
2727
public const string VariableNotDefinedNonLocal = "variable-not-defined-nonlocal";
2828
public const string UnsupportedOperandType = "unsupported-operand-type";
2929
public const string ReturnInInit = "return-in-init";
30+
public const string TypingNewTypeArguments = "typing-newtype-arguments";
3031
public const string TypingGenericArguments = "typing-generic-arguments";
3132
}
3233
}

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="NewTypeFirstArgNotString" xml:space="preserve">
193+
<value>The first argument to NewType must be a string, but it is of type '{0}'.</value>
194+
</data>
192195
<data name="UnsupporedOperandType" xml:space="preserve">
193196
<value>Unsupported operand types for '{0}': '{1}' and '{2}'</value>
194197
</data>

src/Analysis/Ast/Impl/Specializations/Typing/TypingModule.cs

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
using System.Collections.Generic;
1717
using System.Linq;
18+
using Microsoft.Python.Analysis.Diagnostics;
1819
using Microsoft.Python.Analysis.Modules;
1920
using Microsoft.Python.Analysis.Specializations.Typing.Types;
2021
using Microsoft.Python.Analysis.Types;
@@ -61,7 +62,7 @@ private void SpecializeMembers() {
6162
o = new PythonFunctionOverload(fn.Name, location);
6263
// When called, create generic parameter type. For documentation
6364
// use original TypeVar declaration so it appear as a tooltip.
64-
o.SetReturnValueProvider((interpreter, overload, args) => CreateTypeAlias(args.Values<IMember>()));
65+
o.SetReturnValueProvider((interpreter, overload, args) => CreateTypeAlias(args));
6566
fn.AddOverload(o);
6667
_members["NewType"] = fn;
6768

@@ -81,41 +82,41 @@ private void SpecializeMembers() {
8182

8283
_members["Iterable"] = new GenericType("Iterable", typeArgs => CreateListType("Iterable", BuiltinTypeId.List, typeArgs, false), this);
8384
_members["Sequence"] = new GenericType("Sequence", typeArgs => CreateListType("Sequence", BuiltinTypeId.List, typeArgs, false), this);
84-
_members["MutableSequence"] = new GenericType("MutableSequence",
85+
_members["MutableSequence"] = new GenericType("MutableSequence",
8586
typeArgs => CreateListType("MutableSequence", BuiltinTypeId.List, typeArgs, true), this);
86-
_members["List"] = new GenericType("List",
87+
_members["List"] = new GenericType("List",
8788
typeArgs => CreateListType("List", BuiltinTypeId.List, typeArgs, true), this);
8889

89-
_members["MappingView"] = new GenericType("MappingView",
90+
_members["MappingView"] = new GenericType("MappingView",
9091
typeArgs => CreateDictionary("MappingView", typeArgs, false), this);
9192

9293
_members["KeysView"] = new GenericType("KeysView", CreateKeysViewType, this);
9394
_members["ValuesView"] = new GenericType("ValuesView", CreateValuesViewType, this);
9495
_members["ItemsView"] = new GenericType("ItemsView", CreateItemsViewType, this);
9596

96-
_members["Set"] = new GenericType("Set",
97+
_members["Set"] = new GenericType("Set",
9798
typeArgs => CreateListType("Set", BuiltinTypeId.Set, typeArgs, true), this);
98-
_members["MutableSet"] = new GenericType("MutableSet",
99+
_members["MutableSet"] = new GenericType("MutableSet",
99100
typeArgs => CreateListType("MutableSet", BuiltinTypeId.Set, typeArgs, true), this);
100-
_members["FrozenSet"] = new GenericType("FrozenSet",
101+
_members["FrozenSet"] = new GenericType("FrozenSet",
101102
typeArgs => CreateListType("FrozenSet", BuiltinTypeId.Set, typeArgs, false), this);
102103

103104
_members["Tuple"] = new GenericType("Tuple", CreateTupleType, this);
104105

105-
_members["Mapping"] = new GenericType("Mapping",
106+
_members["Mapping"] = new GenericType("Mapping",
106107
typeArgs => CreateDictionary("Mapping", typeArgs, false), this);
107-
_members["MutableMapping"] = new GenericType("MutableMapping",
108+
_members["MutableMapping"] = new GenericType("MutableMapping",
108109
typeArgs => CreateDictionary("MutableMapping", typeArgs, true), this);
109-
_members["Dict"] = new GenericType("Dict",
110+
_members["Dict"] = new GenericType("Dict",
110111
typeArgs => CreateDictionary("Dict", typeArgs, true), this);
111-
_members["OrderedDict"] = new GenericType("OrderedDict",
112+
_members["OrderedDict"] = new GenericType("OrderedDict",
112113
typeArgs => CreateDictionary("OrderedDict", typeArgs, true), this);
113-
_members["DefaultDict"] = new GenericType("DefaultDict",
114+
_members["DefaultDict"] = new GenericType("DefaultDict",
114115
typeArgs => CreateDictionary("DefaultDict", typeArgs, true), this);
115116

116117
_members["Union"] = new GenericType("Union", CreateUnion, this);
117118

118-
_members["Counter"] = Specialized.Function("Counter", this, GetMemberDocumentation("Counter"),
119+
_members["Counter"] = Specialized.Function("Counter", this, GetMemberDocumentation("Counter"),
119120
new PythonInstance(Interpreter.GetBuiltinType(BuiltinTypeId.Int)));
120121

121122
_members["SupportsInt"] = Interpreter.GetBuiltinType(BuiltinTypeId.Int);
@@ -217,13 +218,25 @@ private IPythonType CreateItemsViewType(IReadOnlyList<IPythonType> typeArgs) {
217218
return Interpreter.UnknownType;
218219
}
219220

220-
private IPythonType CreateTypeAlias(IReadOnlyList<IMember> typeArgs) {
221+
private IPythonType CreateTypeAlias(IArgumentSet args) {
222+
var typeArgs = args.Values<IMember>();
221223
if (typeArgs.Count == 2) {
222224
var typeName = (typeArgs[0] as IPythonConstant)?.Value as string;
223225
if (!string.IsNullOrEmpty(typeName)) {
224226
return new TypeAlias(typeName, typeArgs[1].GetPythonType() ?? Interpreter.UnknownType);
225227
}
226-
// TODO: report incorrect first argument to NewVar
228+
229+
var firstArgType = (typeArgs[0] as PythonInstance)?.Type.Name;
230+
var eval = args.Eval;
231+
var expression = args.Expression;
232+
233+
eval.ReportDiagnostics(
234+
eval.Module?.Uri,
235+
new DiagnosticsEntry(Resources.NewTypeFirstArgNotString.FormatInvariant(firstArgType),
236+
expression?.GetLocation(eval.Module)?.Span ?? default,
237+
Diagnostics.ErrorCodes.TypingNewTypeArguments,
238+
Severity.Error, DiagnosticSource.Analysis)
239+
);
227240
}
228241
// TODO: report wrong number of arguments
229242
return Interpreter.UnknownType;
@@ -331,7 +344,7 @@ private IPythonType CreateGenericClassParameter(IReadOnlyList<IPythonType> typeA
331344
return Interpreter.UnknownType;
332345
}
333346

334-
private IPythonType ToGenericTemplate(string typeName, IGenericTypeDefinition[] typeArgs, BuiltinTypeId typeId)
347+
private IPythonType ToGenericTemplate(string typeName, IGenericTypeDefinition[] typeArgs, BuiltinTypeId typeId)
335348
=> _members[typeName] is GenericType gt
336349
? new GenericType(CodeFormatter.FormatSequence(typeName, '[', typeArgs), gt.SpecificTypeConstructor, this, typeId, typeArgs)
337350
: Interpreter.UnknownType;
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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.Core;
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 LintNewTypeTests : 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 NewTypeIntFirstArg() {
39+
const string code = @"
40+
from typing import NewType
41+
42+
T = NewType(5, int)
43+
";
44+
var analysis = await GetAnalysisAsync(code);
45+
analysis.Diagnostics.Should().HaveCount(1);
46+
47+
var diagnostic = analysis.Diagnostics.ElementAt(0);
48+
diagnostic.SourceSpan.Should().Be(4, 5, 4, 20);
49+
diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.TypingNewTypeArguments);
50+
diagnostic.Message.Should().Be(Resources.NewTypeFirstArgNotString.FormatInvariant("int"));
51+
}
52+
53+
[DataRow("float", "float")]
54+
[DataRow("int", "int")]
55+
[DataRow("complex", "str")]
56+
[DataTestMethod, Priority(0)]
57+
public async Task DifferentTypesFirstArg(string nameType, string type) {
58+
string code = $@"
59+
from typing import NewType
60+
61+
T = NewType({nameType}(10), {type})
62+
63+
";
64+
var analysis = await GetAnalysisAsync(code);
65+
analysis.Diagnostics.Should().HaveCount(1);
66+
67+
var diagnostic = analysis.Diagnostics.ElementAt(0);
68+
diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.TypingNewTypeArguments);
69+
diagnostic.Message.Should().Be(Resources.NewTypeFirstArgNotString.FormatInvariant(nameType));
70+
}
71+
72+
[TestMethod, Priority(0)]
73+
public async Task ObjectFirstArg() {
74+
string code = $@"
75+
from typing import NewType
76+
77+
class X:
78+
def hello():
79+
pass
80+
81+
h = X()
82+
83+
T = NewType(h, int)
84+
";
85+
var analysis = await GetAnalysisAsync(code);
86+
analysis.Diagnostics.Should().HaveCount(1);
87+
88+
var diagnostic = analysis.Diagnostics.ElementAt(0);
89+
diagnostic.SourceSpan.Should().Be(10, 5, 10, 20);
90+
diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.TypingNewTypeArguments);
91+
diagnostic.Message.Should().Be(Resources.NewTypeFirstArgNotString.FormatInvariant("X"));
92+
}
93+
94+
[TestMethod, Priority(0)]
95+
public async Task GenericFirstArg() {
96+
string code = $@"
97+
from typing import NewType, Generic, TypeVar
98+
99+
T = TypeVar('T', str, int)
100+
101+
class X(Generic[T]):
102+
def __init__(self, p: T):
103+
self.x = p
104+
105+
h = X(5)
106+
T = NewType(h, int)
107+
";
108+
var analysis = await GetAnalysisAsync(code);
109+
analysis.Diagnostics.Should().HaveCount(1);
110+
111+
var diagnostic = analysis.Diagnostics.ElementAt(0);
112+
diagnostic.SourceSpan.Should().Be(11, 5, 11, 20);
113+
diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.TypingNewTypeArguments);
114+
diagnostic.Message.Should().Be(Resources.NewTypeFirstArgNotString.FormatInvariant("X[int]"));
115+
}
116+
117+
[DataRow("test", "float")]
118+
[DataRow("testing", "int")]
119+
[DataTestMethod, Priority(0)]
120+
public async Task NoDiagnosticOnStringFirstArg(string name, string type) {
121+
string code = $@"
122+
from typing import NewType
123+
124+
T = NewType('{name}', {type})
125+
";
126+
var analysis = await GetAnalysisAsync(code);
127+
analysis.Diagnostics.Should().HaveCount(0);
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)