Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1bcba75

Browse files
authored
Adding diagnostic message when using TypeVar improperly (microsoft#1230)
1 parent c88754b commit 1bcba75

File tree

9 files changed

+205
-17
lines changed

9 files changed

+205
-17
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public static class ErrorCodes {
2828
public const string FunctionRedefined = "function-redefined";
2929
public const string UnsupportedOperandType = "unsupported-operand-type";
3030
public const string ReturnInInit = "return-in-init";
31+
public const string TypingTypeVarArguments = "typing-typevar-arguments";
3132
public const string TypingNewTypeArguments = "typing-newtype-arguments";
3233
public const string TypingGenericArguments = "typing-generic-arguments";
3334
public const string InheritNonClass = "inherit-non-class";

src/Analysis/Ast/Impl/Extensions/ArgumentSetExtensions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ public static T GetArgumentValue<T>(this IArgumentSet args, string name) where T
4141
return value as T;
4242
}
4343

44+
public static void ReportErrors(this IArgumentSet args) {
45+
foreach (var err in args.Errors) {
46+
args.Eval?.ReportDiagnostics(args.Eval?.Module?.Uri, err);
47+
}
48+
}
49+
4450
internal static void DeclareParametersInScope(this IArgumentSet args, ExpressionEval eval) {
4551
if (eval == null) {
4652
return;

src/Analysis/Ast/Impl/Resources.Designer.cs

Lines changed: 21 additions & 3 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: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,12 @@
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="TypeVarFirstArgumentNotString" xml:space="preserve">
193+
<value>The first argument to TypeVar must be a string.</value>
194+
</data>
195+
<data name="TypeVarSingleConstraint" xml:space="preserve">
196+
<value>A single constraint to TypeVar is not allowed.</value>
197+
</data>
192198
<data name="FunctionRedefined" xml:space="preserve">
193199
<value>Function already defined at line {0}.</value>
194200
</data>
@@ -210,4 +216,4 @@
210216
<data name="DuplicateArgumentName" xml:space="preserve">
211217
<value>Duplicate argument name '{0}' in function definition.</value>
212218
</data>
213-
</root>
219+
</root>

src/Analysis/Ast/Impl/Specializations/Typing/Types/GenericTypeParameter.cs

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
using System;
1717
using System.Collections.Generic;
1818
using System.Linq;
19+
using Microsoft.Python.Analysis.Diagnostics;
1920
using Microsoft.Python.Analysis.Types;
2021
using Microsoft.Python.Analysis.Utilities;
2122
using Microsoft.Python.Analysis.Values;
2223
using Microsoft.Python.Core.Text;
24+
using Microsoft.Python.Parsing;
2325

2426
namespace Microsoft.Python.Analysis.Specializations.Typing.Types {
2527
internal sealed class GenericTypeParameter : PythonType, IGenericTypeDefinition {
@@ -33,32 +35,63 @@ public GenericTypeParameter(string name, IPythonModule declaringModule, IReadOnl
3335
public override PythonMemberType MemberType => PythonMemberType.Generic;
3436
public override bool IsSpecialized => true;
3537

36-
37-
public static IPythonType FromTypeVar(IArgumentSet argSet, IPythonModule declaringModule, IndexSpan location = default) {
38+
private static bool TypeVarArgumentsValid(IArgumentSet argSet) {
3839
var args = argSet.Arguments;
39-
var constraintArgs = argSet.ListArgument?.Values ?? Array.Empty<IMember>();
40+
var constraints = argSet.ListArgument?.Values ?? Array.Empty<IMember>();
4041

41-
if (args.Count == 0) {
42-
// TODO: report that at least one argument is required.
43-
return declaringModule.Interpreter.UnknownType;
42+
var eval = argSet.Eval;
43+
var expr = argSet.Expression;
44+
var callLocation = expr?.GetLocation(eval);
45+
46+
if (argSet.Errors.Count > 0) {
47+
argSet.ReportErrors();
48+
return false;
4449
}
4550

51+
// Report diagnostic if user passed in a value for name and it is not a string
4652
var name = (args[0].Value as IPythonConstant)?.GetString();
4753
if (string.IsNullOrEmpty(name)) {
48-
// TODO: report that type name is not a string.
54+
eval.ReportDiagnostics(
55+
eval.Module.Uri,
56+
new DiagnosticsEntry(Resources.TypeVarFirstArgumentNotString,
57+
callLocation?.Span ?? default,
58+
Diagnostics.ErrorCodes.TypingTypeVarArguments,
59+
Severity.Warning, DiagnosticSource.Analysis)
60+
);
61+
return false;
62+
}
63+
64+
// Python gives runtime error when TypeVar has one constraint
65+
// e.g. T = TypeVar('T', int)
66+
if (constraints.Count == 1) {
67+
eval.ReportDiagnostics(
68+
eval.Module.Uri,
69+
new DiagnosticsEntry(Resources.TypeVarSingleConstraint,
70+
callLocation?.Span ?? default,
71+
Diagnostics.ErrorCodes.TypingTypeVarArguments,
72+
Severity.Error, DiagnosticSource.Analysis)
73+
);
74+
return false;
75+
}
76+
77+
return true;
78+
}
79+
80+
public static IPythonType FromTypeVar(IArgumentSet argSet, IPythonModule declaringModule, IndexSpan location = default) {
81+
if (!TypeVarArgumentsValid(argSet)) {
4982
return declaringModule.Interpreter.UnknownType;
5083
}
5184

85+
var args = argSet.Arguments;
86+
var constraintArgs = argSet.ListArgument?.Values ?? Array.Empty<IMember>();
87+
88+
var name = (args[0].Value as IPythonConstant)?.GetString();
5289
var constraints = constraintArgs.Select(a => {
5390
// Type constraints may be specified as type name strings.
5491
var typeString = (a as IPythonConstant)?.GetString();
5592
return !string.IsNullOrEmpty(typeString) ? argSet.Eval.GetTypeFromString(typeString) : a.GetPythonType();
5693
}).ToArray() ?? Array.Empty<IPythonType>();
5794

58-
if (constraints.Any(c => c.IsUnknown())) {
59-
// TODO: report that some constraints could not be resolved.
60-
}
61-
6295
var documentation = GetDocumentation(args, constraints);
6396
return new GenericTypeParameter(name, declaringModule, constraints, documentation, location);
6497
}

src/Analysis/Ast/Impl/Types/Definitions/IArgumentSet.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
using System.Collections.Generic;
1818
using Microsoft.Python.Analysis.Analyzer;
19+
using Microsoft.Python.Analysis.Diagnostics;
1920
using Microsoft.Python.Parsing.Ast;
2021

2122
namespace Microsoft.Python.Analysis.Types {
@@ -147,6 +148,11 @@ public interface IArgumentSet {
147148
/// </summary>
148149
IExpressionEvaluator Eval { get; }
149150

151+
/// <summary>
152+
/// Errors upon building the argument set
153+
/// </summary>
154+
IReadOnlyList<DiagnosticsEntry> Errors { get; }
155+
150156
/// <summary>
151157
/// Expression associated with the argument set
152158
/// </summary>

src/Analysis/Ast/Impl/Types/Definitions/IPythonFunctionOverload.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public interface IPythonFunctionOverload {
5454
/// <param name="self">Invoking class instance. In case of generics it is instance of the specific type
5555
/// as opposed to declaring type which is the generic template class.</param>
5656
/// <param name="callLocation">Call expression location, if any.</param>
57-
IMember Call(IArgumentSet args, IPythonType self, Node callLocation = null);
57+
IMember Call(IArgumentSet args, IPythonType self);
5858

5959
/// <summary>
6060
/// Return value documentation.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public string GetReturnDocumentation(IPythonType self = null) {
113113
public IReadOnlyList<IParameterInfo> Parameters { get; private set; } = Array.Empty<IParameterInfo>();
114114
public IMember StaticReturnValue { get; private set; }
115115

116-
public IMember Call(IArgumentSet args, IPythonType self, Node callLocation = null) {
116+
public IMember Call(IArgumentSet args, IPythonType self) {
117117
if (!_fromAnnotation) {
118118
// First try supplied specialization callback.
119119
var rt = _returnValueProvider?.Invoke(DeclaringModule, this, args);
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
using System.Linq;
2+
using System.Threading.Tasks;
3+
using FluentAssertions;
4+
using Microsoft.Python.Analysis.Tests.FluentAssertions;
5+
using Microsoft.Python.Core;
6+
using Microsoft.Python.Parsing.Tests;
7+
using Microsoft.VisualStudio.TestTools.UnitTesting;
8+
using TestUtilities;
9+
10+
namespace Microsoft.Python.Analysis.Tests {
11+
12+
[TestClass]
13+
public class LintTypeVarTests : AnalysisTestBase {
14+
public const string TypeVarImport = @"
15+
from typing import TypeVar
16+
";
17+
18+
public TestContext TestContext { get; set; }
19+
20+
[TestInitialize]
21+
public void TestInitialize()
22+
=> TestEnvironmentImpl.TestInitialize($"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}");
23+
24+
[TestCleanup]
25+
public void Cleanup() => TestEnvironmentImpl.TestCleanup();
26+
27+
[DataRow(TypeVarImport + @"
28+
T = TypeVar(1, 2, 3)")]
29+
[DataRow(TypeVarImport + @"
30+
T = TypeVar(2.0, 3)")]
31+
[DataRow(TypeVarImport + @"
32+
class C:
33+
int: t
34+
__init__(t):
35+
self.x = t
36+
37+
test = C(5)
38+
T = TypeVar(C, 3)
39+
")]
40+
[DataRow(TypeVarImport + @"
41+
T = TypeVar(1f)
42+
")]
43+
[DataTestMethod, Priority(0)]
44+
public async Task TypeVarFirstArgumentNotString(string code) {
45+
var analysis = await GetAnalysisAsync(code);
46+
analysis.Diagnostics.Should().HaveCount(1);
47+
48+
var diagnostic = analysis.Diagnostics.ElementAt(0);
49+
diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.TypingTypeVarArguments);
50+
diagnostic.Message.Should().Be(Resources.TypeVarFirstArgumentNotString);
51+
}
52+
53+
[TestMethod, Priority(0)]
54+
public async Task TypeVarFirstArgumentFunction() {
55+
const string code = @"
56+
from typing import TypeVar
57+
58+
def temp():
59+
return 'str'
60+
61+
T = TypeVar(temp(), int, str)
62+
";
63+
64+
var analysis = await GetAnalysisAsync(code);
65+
analysis.Diagnostics.Should().BeEmpty();
66+
}
67+
68+
[DataRow(TypeVarImport + @"
69+
T = TypeVar('T', int, str)")]
70+
[DataRow(TypeVarImport + @"
71+
F = TypeVar('F',double, complex)")]
72+
[DataRow(TypeVarImport + @"
73+
T = TypeVar('T')
74+
")]
75+
[DataRow(TypeVarImport + @"
76+
T = TypeVar('T', float, int)
77+
")]
78+
[DataTestMethod, Priority(0)]
79+
public async Task TypeVarNoDiagnosticOnValidUse(string code) {
80+
var analysis = await GetAnalysisAsync(code);
81+
analysis.Diagnostics.Should().BeEmpty();
82+
}
83+
84+
85+
[TestMethod, Priority(0)]
86+
public async Task TypeVarNoArguments() {
87+
const string code = @"
88+
from typing import TypeVar
89+
90+
T = TypeVar()
91+
";
92+
var analysis = await GetAnalysisAsync(code);
93+
analysis.Diagnostics.Should().HaveCount(1);
94+
95+
var diagnostic = analysis.Diagnostics.ElementAt(0);
96+
diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.ParameterMissing);
97+
diagnostic.Message.Should().Be(Resources.Analysis_ParameterMissing.FormatInvariant("name"));
98+
diagnostic.SourceSpan.Should().Be(4, 5, 4, 14);
99+
}
100+
101+
[DataRow("T = TypeVar('T', 'test_constraint')")]
102+
[DataRow("T = TypeVar('T', int)")]
103+
[DataRow("T = TypeVar('T', complex)")]
104+
[DataRow("T = TypeVar('T', str)")]
105+
[DataRow("T = TypeVar('T', 5)")]
106+
[DataTestMethod, Priority(0)]
107+
public async Task TypeVarOneConstraint(string decl) {
108+
string code = TypeVarImport + decl;
109+
var analysis = await GetAnalysisAsync(code);
110+
analysis.Diagnostics.Should().HaveCount(1);
111+
112+
var diagnostic = analysis.Diagnostics.ElementAt(0);
113+
diagnostic.ErrorCode.Should().Be(Diagnostics.ErrorCodes.TypingTypeVarArguments);
114+
diagnostic.Message.Should().Be(Resources.TypeVarSingleConstraint);
115+
diagnostic.SourceSpan.Should().Be(3, 5, 3, decl.IndexOf(")") + 2);
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)