diff --git a/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs b/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs
index 78acd0234..b3e7b90fe 100644
--- a/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs
+++ b/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs
@@ -27,6 +27,7 @@ public static class ErrorCodes {
public const string VariableNotDefinedNonLocal = "variable-not-defined-nonlocal";
public const string UnsupportedOperandType = "unsupported-operand-type";
public const string ReturnInInit = "return-in-init";
+ public const string TypingNewTypeArguments = "typing-newtype-arguments";
public const string TypingGenericArguments = "typing-generic-arguments";
}
}
diff --git a/src/Analysis/Ast/Impl/Resources.Designer.cs b/src/Analysis/Ast/Impl/Resources.Designer.cs
index 3d8e6a207..26b0bcdff 100644
--- a/src/Analysis/Ast/Impl/Resources.Designer.cs
+++ b/src/Analysis/Ast/Impl/Resources.Designer.cs
@@ -249,6 +249,15 @@ internal static string InterpreterNotFound {
}
}
+ ///
+ /// Looks up a localized string similar to The first argument to NewType must be a string, but it is of type '{0}'..
+ ///
+ internal static string NewTypeFirstArgNotString {
+ get {
+ return ResourceManager.GetString("NewTypeFirstArgNotString", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to property of type {0}.
///
diff --git a/src/Analysis/Ast/Impl/Resources.resx b/src/Analysis/Ast/Impl/Resources.resx
index 48c944283..4e0a81fee 100644
--- a/src/Analysis/Ast/Impl/Resources.resx
+++ b/src/Analysis/Ast/Impl/Resources.resx
@@ -189,6 +189,9 @@
Unable to determine analysis cache path. Exception: {0}. Using default '{1}'.
+
+ The first argument to NewType must be a string, but it is of type '{0}'.
+
Unsupported operand types for '{0}': '{1}' and '{2}'
diff --git a/src/Analysis/Ast/Impl/Specializations/Typing/TypingModule.cs b/src/Analysis/Ast/Impl/Specializations/Typing/TypingModule.cs
index 6fd4cb1e8..960f507b2 100644
--- a/src/Analysis/Ast/Impl/Specializations/Typing/TypingModule.cs
+++ b/src/Analysis/Ast/Impl/Specializations/Typing/TypingModule.cs
@@ -15,6 +15,7 @@
using System.Collections.Generic;
using System.Linq;
+using Microsoft.Python.Analysis.Diagnostics;
using Microsoft.Python.Analysis.Modules;
using Microsoft.Python.Analysis.Specializations.Typing.Types;
using Microsoft.Python.Analysis.Types;
@@ -61,7 +62,7 @@ private void SpecializeMembers() {
o = new PythonFunctionOverload(fn.Name, location);
// When called, create generic parameter type. For documentation
// use original TypeVar declaration so it appear as a tooltip.
- o.SetReturnValueProvider((interpreter, overload, args) => CreateTypeAlias(args.Values()));
+ o.SetReturnValueProvider((interpreter, overload, args) => CreateTypeAlias(args));
fn.AddOverload(o);
_members["NewType"] = fn;
@@ -81,41 +82,41 @@ private void SpecializeMembers() {
_members["Iterable"] = new GenericType("Iterable", typeArgs => CreateListType("Iterable", BuiltinTypeId.List, typeArgs, false), this);
_members["Sequence"] = new GenericType("Sequence", typeArgs => CreateListType("Sequence", BuiltinTypeId.List, typeArgs, false), this);
- _members["MutableSequence"] = new GenericType("MutableSequence",
+ _members["MutableSequence"] = new GenericType("MutableSequence",
typeArgs => CreateListType("MutableSequence", BuiltinTypeId.List, typeArgs, true), this);
- _members["List"] = new GenericType("List",
+ _members["List"] = new GenericType("List",
typeArgs => CreateListType("List", BuiltinTypeId.List, typeArgs, true), this);
- _members["MappingView"] = new GenericType("MappingView",
+ _members["MappingView"] = new GenericType("MappingView",
typeArgs => CreateDictionary("MappingView", typeArgs, false), this);
_members["KeysView"] = new GenericType("KeysView", CreateKeysViewType, this);
_members["ValuesView"] = new GenericType("ValuesView", CreateValuesViewType, this);
_members["ItemsView"] = new GenericType("ItemsView", CreateItemsViewType, this);
- _members["Set"] = new GenericType("Set",
+ _members["Set"] = new GenericType("Set",
typeArgs => CreateListType("Set", BuiltinTypeId.Set, typeArgs, true), this);
- _members["MutableSet"] = new GenericType("MutableSet",
+ _members["MutableSet"] = new GenericType("MutableSet",
typeArgs => CreateListType("MutableSet", BuiltinTypeId.Set, typeArgs, true), this);
- _members["FrozenSet"] = new GenericType("FrozenSet",
+ _members["FrozenSet"] = new GenericType("FrozenSet",
typeArgs => CreateListType("FrozenSet", BuiltinTypeId.Set, typeArgs, false), this);
_members["Tuple"] = new GenericType("Tuple", CreateTupleType, this);
- _members["Mapping"] = new GenericType("Mapping",
+ _members["Mapping"] = new GenericType("Mapping",
typeArgs => CreateDictionary("Mapping", typeArgs, false), this);
- _members["MutableMapping"] = new GenericType("MutableMapping",
+ _members["MutableMapping"] = new GenericType("MutableMapping",
typeArgs => CreateDictionary("MutableMapping", typeArgs, true), this);
- _members["Dict"] = new GenericType("Dict",
+ _members["Dict"] = new GenericType("Dict",
typeArgs => CreateDictionary("Dict", typeArgs, true), this);
- _members["OrderedDict"] = new GenericType("OrderedDict",
+ _members["OrderedDict"] = new GenericType("OrderedDict",
typeArgs => CreateDictionary("OrderedDict", typeArgs, true), this);
- _members["DefaultDict"] = new GenericType("DefaultDict",
+ _members["DefaultDict"] = new GenericType("DefaultDict",
typeArgs => CreateDictionary("DefaultDict", typeArgs, true), this);
_members["Union"] = new GenericType("Union", CreateUnion, this);
- _members["Counter"] = Specialized.Function("Counter", this, GetMemberDocumentation("Counter"),
+ _members["Counter"] = Specialized.Function("Counter", this, GetMemberDocumentation("Counter"),
new PythonInstance(Interpreter.GetBuiltinType(BuiltinTypeId.Int)));
_members["SupportsInt"] = Interpreter.GetBuiltinType(BuiltinTypeId.Int);
@@ -217,13 +218,25 @@ private IPythonType CreateItemsViewType(IReadOnlyList typeArgs) {
return Interpreter.UnknownType;
}
- private IPythonType CreateTypeAlias(IReadOnlyList typeArgs) {
+ private IPythonType CreateTypeAlias(IArgumentSet args) {
+ var typeArgs = args.Values();
if (typeArgs.Count == 2) {
var typeName = (typeArgs[0] as IPythonConstant)?.Value as string;
if (!string.IsNullOrEmpty(typeName)) {
return new TypeAlias(typeName, typeArgs[1].GetPythonType() ?? Interpreter.UnknownType);
}
- // TODO: report incorrect first argument to NewVar
+
+ var firstArgType = (typeArgs[0] as PythonInstance)?.Type.Name;
+ var eval = args.Eval;
+ var expression = args.Expression;
+
+ eval.ReportDiagnostics(
+ eval.Module?.Uri,
+ new DiagnosticsEntry(Resources.NewTypeFirstArgNotString.FormatInvariant(firstArgType),
+ expression?.GetLocation(eval.Module)?.Span ?? default,
+ Diagnostics.ErrorCodes.TypingNewTypeArguments,
+ Severity.Error, DiagnosticSource.Analysis)
+ );
}
// TODO: report wrong number of arguments
return Interpreter.UnknownType;
@@ -331,7 +344,7 @@ private IPythonType CreateGenericClassParameter(IReadOnlyList typeA
return Interpreter.UnknownType;
}
- private IPythonType ToGenericTemplate(string typeName, IGenericTypeDefinition[] typeArgs, BuiltinTypeId typeId)
+ private IPythonType ToGenericTemplate(string typeName, IGenericTypeDefinition[] typeArgs, BuiltinTypeId typeId)
=> _members[typeName] is GenericType gt
? new GenericType(CodeFormatter.FormatSequence(typeName, '[', typeArgs), gt.SpecificTypeConstructor, this, typeId, typeArgs)
: Interpreter.UnknownType;
diff --git a/src/Analysis/Ast/Test/LintNewTypeTests.cs b/src/Analysis/Ast/Test/LintNewTypeTests.cs
new file mode 100644
index 000000000..aacddbf87
--- /dev/null
+++ b/src/Analysis/Ast/Test/LintNewTypeTests.cs
@@ -0,0 +1,130 @@
+// Copyright(c) Microsoft Corporation
+// All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the License); you may not use
+// this file except in compliance with the License. You may obtain a copy of the
+// License at http://www.apache.org/licenses/LICENSE-2.0
+//
+// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
+// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
+// MERCHANTABILITY OR NON-INFRINGEMENT.
+//
+// See the Apache Version 2.0 License for specific language governing
+// permissions and limitations under the License.
+
+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 {
+ [TestClass]
+ public class LintNewTypeTests : 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 NewTypeIntFirstArg() {
+ const string code = @"
+from typing import NewType
+
+T = NewType(5, int)
+";
+ var analysis = await GetAnalysisAsync(code);
+ analysis.Diagnostics.Should().HaveCount(1);
+
+ var diagnostic = analysis.Diagnostics.ElementAt(0);
+ diagnostic.SourceSpan.Should().Be(4, 5, 4, 20);
+ diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.TypingNewTypeArguments);
+ diagnostic.Message.Should().Be(Resources.NewTypeFirstArgNotString.FormatInvariant("int"));
+ }
+
+ [DataRow("float", "float")]
+ [DataRow("int", "int")]
+ [DataRow("complex", "str")]
+ [DataTestMethod, Priority(0)]
+ public async Task DifferentTypesFirstArg(string nameType, string type) {
+ string code = $@"
+from typing import NewType
+
+T = NewType({nameType}(10), {type})
+
+";
+ var analysis = await GetAnalysisAsync(code);
+ analysis.Diagnostics.Should().HaveCount(1);
+
+ var diagnostic = analysis.Diagnostics.ElementAt(0);
+ diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.TypingNewTypeArguments);
+ diagnostic.Message.Should().Be(Resources.NewTypeFirstArgNotString.FormatInvariant(nameType));
+ }
+
+ [TestMethod, Priority(0)]
+ public async Task ObjectFirstArg() {
+ string code = $@"
+from typing import NewType
+
+class X:
+ def hello():
+ pass
+
+h = X()
+
+T = NewType(h, int)
+";
+ var analysis = await GetAnalysisAsync(code);
+ analysis.Diagnostics.Should().HaveCount(1);
+
+ var diagnostic = analysis.Diagnostics.ElementAt(0);
+ diagnostic.SourceSpan.Should().Be(10, 5, 10, 20);
+ diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.TypingNewTypeArguments);
+ diagnostic.Message.Should().Be(Resources.NewTypeFirstArgNotString.FormatInvariant("X"));
+ }
+
+ [TestMethod, Priority(0)]
+ public async Task GenericFirstArg() {
+ string code = $@"
+from typing import NewType, Generic, TypeVar
+
+T = TypeVar('T', str, int)
+
+class X(Generic[T]):
+ def __init__(self, p: T):
+ self.x = p
+
+h = X(5)
+T = NewType(h, int)
+";
+ var analysis = await GetAnalysisAsync(code);
+ analysis.Diagnostics.Should().HaveCount(1);
+
+ var diagnostic = analysis.Diagnostics.ElementAt(0);
+ diagnostic.SourceSpan.Should().Be(11, 5, 11, 20);
+ diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.TypingNewTypeArguments);
+ diagnostic.Message.Should().Be(Resources.NewTypeFirstArgNotString.FormatInvariant("X[int]"));
+ }
+
+ [DataRow("test", "float")]
+ [DataRow("testing", "int")]
+ [DataTestMethod, Priority(0)]
+ public async Task NoDiagnosticOnStringFirstArg(string name, string type) {
+ string code = $@"
+from typing import NewType
+
+T = NewType('{name}', {type})
+";
+ var analysis = await GetAnalysisAsync(code);
+ analysis.Diagnostics.Should().HaveCount(0);
+ }
+ }
+}