From 4afc1efb2a0ff120de6f4d92d42a791ef3db1fc4 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Fri, 15 Feb 2019 16:57:31 -0800 Subject: [PATCH] Resolve issue when specific types shared data after creation off generic template (#600) * Buildable * Part 14 * First passing test * Simplify configuration * Style * Fix test and move code to folders * Builtins import * Fix #470 * Fluents * Add search path * Import analysis, part I * Simplify builtins handling * Remove IMember * Handle import specific * More tests * Add typeshed * Renames * Make sure lazy modules are loaded * Renames * Move/rename * Rework importing * Derivation rework * Part 2 * Part 3 * Buildable * Module members * Async walk * Imports test pass * Remove lazy types * Fix from import * Stubs * Double overloads * Fix datetime test * Couple more tests + fluents * Few more tests * Additionl test + union type * Built-in scrape tests * Full stdlib scrape test * Complete async AST walker * Conditional defines test + variable loc cleanup * More stub tests Fix stub loading for packages (port from DDG) Split walker into multiple files * Add some (broken mostly) tests from DDG * Move document tests * Function arg eval, part I * Instance/factory * Builds * Test fixes * Fix static and instance call eval * More tests * More ported tests * Specialize builtin functions * Make walkers common and handle nested functions * Moar tests * Parser fixes + more tests * Handle negative numbers * Fix null ref * Basic list support * Few more list tests * Basic iterators * Support __iter__ * Iterators * Fix couple of tests * Add decorator test * Generics, part I * Generics, part 2 * Generics, part 3 * Basic TypeVar test * Typings, part 4 * Fix test * Generics, part 6 * Generics, part 7 * More tests (failing) * Forward ref fixes * Reorg * Improve symbol resolution + test fixes * Test fixes * Dictionary, part I * Part 11 * Fix test * Tests * Tests * More dict work * List ctor * Skip some tests for now * Fix iterators * Tuple slicing * Polish type comparo in return types * Add Mapping and tests * Add Iterable * Fix typo * Add Iterator[T] + test * Simplify typing types * Class reduction * Fix tests * Test fix * Handle 'with' statement * Handle try-except * Class method inheritance + NewType * Container types * Containers test * Tests * Handle generic type alias * Named tuple * Global/non-local * Handle tuples in for Handle custom iterators * Basic generator * Any/AnyStr * Test fixes * Type/Optional/etc handling * Proper doc population * Tests + range * Argument match * Basic argset and diagnostics * Argset tests * Exclude WIP * Exclude WIP * Arg eval * Arg match, part 2 * Tests and generic arg comparisons * Function eval with arguments * Baselines * Fix test * Undo AST formatting change and update baseline * LS cleanup 1 * Fix list ctor argument unpacking * Cleanup 2 * Builds * Partial completions * Partial * Partial * Simple test * Tests * Basic startup * Clean up a bit * Remove debug code * Port formatter tests * Fix tokenizer crash * Async fixes * Hover * Basic hover * Adjust expression options * Basic signature help * Fix class/instance * Update test * Fix builtin creation exception * Fix tests * Actually provide declared module * Completion test (partial) * Undo * Fix null await Fix override completions + test * Exports filtering Prevent augmenting imported types * Filter variables & exports * Ported tests * Test fixes * More ported tests * Fix exception completions * Import completions * Scope completions * With completions * Test fixes * WIP * Test fix * Better arg match * Temp disable WIP * First cut * Fix type leak * WIP * Remove ConfigureAwait and handle canceled and failed in the analysis notifications * WIP * Generic class base * Generic forward reference resolution * Suppress completion in strings + test * Prevent recursion on generic resolution Better match arguments * Handle call expression in generics * Relax condition as it happens in tensorflow * Fix typeshed version search Make writing cached modules async Fix module doc fetching * Hover tests * Fix prom import hover * Hover tests * Synchronize test cache writing * First cut * Test * Fixes * Add tests for os.path Null ref fix * Fix cache check * Improve resolution of builtins and typing in stubs * Merge tests * Add ntst for requests * Handle typeshed better * Fix custom stub handling * Better sync * Move files * Fix parameter locations * Hover improvement * PEP hints * One more test for PEP hints * Better handle hover over import as * Text based generic constraints * Handle with better with generic stubs * Undo debug * Handle non-binary open() Temporary fix 'with' handler since we haven't specialized IO/TextIO/BinaryIO yet. * Output syntax errors * Properly clear * - Fix async issue with analysis completion - Clean up diagnostics service interface - Use real DS in tests * Use proper scope when analyzing module * Severity mapping and reporting * Add publishing test Add NSubstitute Move all services to the same namespace. * Unused var * Test forced publish on close * Fix typo * Update test framework * Import location * Remove incorrect reference * Diagnostic severity mapping test Fix post-mortem earlier PR comment * Minor fixes * Better handle return types in classes created from templates. * Move interface to the main class part * Dynamic return type * Add hover and signature tests * Baseline update * Flicker reduction * - Correct reported unresolved import name - Add tests * PR feedback * Resolve merge issues * Fix completion doc + test * Restore formatting * Bunch of null checks * Fix generic base classes Add tests and changes to couple more cases * PR feedback --- .../Evaluation/ExpressionEval.Callables.cs | 4 +- .../Evaluation/ExpressionEval.Collections.cs | 8 +- .../Evaluation/ExpressionEval.Generics.cs | 33 ++-- .../Evaluation/ExpressionEval.Operators.cs | 8 +- .../Analyzer/Evaluation/ExpressionEval.cs | 4 +- .../Analyzer/Handlers/AssignmentHandler.cs | 2 +- .../Analyzer/Handlers/TryExceptHandler.cs | 2 +- .../Handlers/TupleExpressionHandler.cs | 8 +- .../Ast/Impl/Analyzer/Handlers/WithHandler.cs | 2 +- .../Analyzer/Symbols/FunctionEvaluator.cs | 4 +- .../Ast/Impl/Extensions/AnalysisExtensions.cs | 2 +- .../Ast/Impl/Extensions/MemberExtensions.cs | 1 - .../Ast/Impl/Specializations/Specialized.cs | 4 +- .../Definitions/IGenericClassBaseType.cs | 3 +- .../Typing/Types/GenericClassBaseType.cs | 3 + .../Typing/Types/GenericType.cs | 53 +++++-- .../Typing/Types/TypingListType.cs | 1 - .../Specializations/Typing/TypingModule.cs | 52 +++++-- src/Analysis/Ast/Impl/Types/ArgumentSet.cs | 11 +- .../Types/Definitions/IPythonClassType.cs | 6 + .../Definitions/IPythonFunctionOverload.cs | 17 ++- .../Types/Definitions/IPythonTemplateType.cs | 6 +- .../Ast/Impl/Types/PythonClassType.cs | 97 ++++++++---- .../Ast/Impl/Types/PythonFunctionOverload.cs | 121 +++++++++------ .../Ast/Impl/Types/PythonFunctionType.cs | 2 +- .../Ast/Impl/Types/PythonPropertyType.cs | 4 +- .../Ast/Impl/Values/PythonInstance.cs | 2 +- .../PythonFunctionOverloadAssertions.cs | 13 +- src/Analysis/Ast/Test/LibraryTests.cs | 2 +- src/Analysis/Ast/Test/TypeshedTests.cs | 2 +- src/Analysis/Ast/Test/TypingTests.cs | 143 ++++++++++++++---- .../Impl/Completion/CompletionItemSource.cs | 8 +- .../Impl/Completion/ExpressionCompletion.cs | 4 +- .../Impl/Definitions/IDocumentationSource.cs | 4 +- .../Impl/Sources/DefinitionSource.cs | 2 - .../Impl/Sources/HoverSource.cs | 22 ++- .../Sources/MarkdownDocumentationSource.cs | 13 +- .../Sources/PlainTextDocumentationSource.cs | 13 +- .../Impl/Sources/SignatureSource.cs | 7 +- src/LanguageServer/Test/CompletionTests.cs | 66 +++++++- src/LanguageServer/Test/HoverTests.cs | 30 +++- src/LanguageServer/Test/SignatureTests.cs | 88 +++++++++++ 42 files changed, 637 insertions(+), 240 deletions(-) create mode 100644 src/LanguageServer/Test/SignatureTests.cs diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Callables.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Callables.cs index fd65b523a..2f45ad985 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Callables.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Callables.cs @@ -157,7 +157,7 @@ public async Task GetValueFromFunctionTypeAsync(IPythonFunctionType fn, if (instanceType == null || fn.DeclaringType == null || fn.IsSpecialized || instanceType.IsSpecialized || fn.DeclaringType.IsSpecialized || instanceType.Equals(fn.DeclaringType) || - fn.IsStub || !string.IsNullOrEmpty(fn.Overloads[args.OverloadIndex].ReturnDocumentation)) { + fn.IsStub || !string.IsNullOrEmpty(fn.Overloads[args.OverloadIndex].GetReturnDocumentation(null))) { if (fn.IsSpecialized && fn is PythonFunctionType ft) { foreach (var module in ft.Dependencies) { @@ -218,7 +218,7 @@ private async Task FindOverloadAsync(IPythonFunctionType fn, IPytho var result = noErrorsMatches.Any() ? noErrorsMatches.FirstOrDefault(args => IsMatch(args, fn.Overloads[args.OverloadIndex].Parameters)) : null; - + // Optimistically pick the best available. return result ?? orderedSets.FirstOrDefault(); } diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Collections.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Collections.cs index 1ff8f4936..e42622aa4 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Collections.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Collections.cs @@ -31,7 +31,7 @@ public async Task GetValueFromIndexAsync(IndexExpression expr, Cancella } var target = await GetValueFromExpressionAsync(expr.Target, cancellationToken); - // Try generics + // Try generics first since this may be an expression like Dict[int, str] var result = await GetValueFromGenericAsync(target, expr, cancellationToken); if (result != null) { return result; @@ -48,7 +48,9 @@ public async Task GetValueFromIndexAsync(IndexExpression expr, Cancella instance = new PythonInstance(type); } var index = await GetValueFromExpressionAsync(expr.Index, cancellationToken); - return type.Index(instance, index); + if (index != null) { + return type.Index(instance, index); + } } return UnknownType; @@ -93,7 +95,7 @@ public async Task GetValueFromSetAsync(SetExpression expression, Cancel public async Task GetValueFromGeneratorAsync(GeneratorExpression expression, CancellationToken cancellationToken = default) { var iter = expression.Iterators.OfType().FirstOrDefault(); - if(iter != null) { + if (iter != null) { return await GetValueFromExpressionAsync(iter.List, cancellationToken) ?? UnknownType; } return UnknownType; diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Generics.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Generics.cs index 59471e632..84b229719 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Generics.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Generics.cs @@ -13,7 +13,6 @@ // See the Apache Version 2.0 License for specific language governing // permissions and limitations under the License. -using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -21,6 +20,7 @@ using Microsoft.Python.Analysis.Specializations.Typing; using Microsoft.Python.Analysis.Specializations.Typing.Types; using Microsoft.Python.Analysis.Types; +using Microsoft.Python.Analysis.Values; using Microsoft.Python.Core; using Microsoft.Python.Parsing.Ast; @@ -47,8 +47,10 @@ private async Task GetValueFromGenericAsync(IMember target, Expression // rather than specific type instantiation as in List[str]. IPythonType[] specificTypes; + var returnInstance = false; switch (expr) { - case IndexExpression indexExpr: { + // Indexing returns type as from A[int] + case IndexExpression indexExpr: // Generic[T1, T2, ...] or A[type]() var indices = await EvaluateIndexAsync(indexExpr, cancellationToken); // See which ones are generic parameters as defined by TypeVar() @@ -66,6 +68,7 @@ private async Task GetValueFromGenericAsync(IMember target, Expression if (genericTypeArgs.Length > 0 && genericTypeArgs.Length != indices.Count) { // TODO: report that some type arguments are not declared with TypeVar. } + if (specificTypes.Length > 0 && specificTypes.Length != indices.Count) { // TODO: report that arguments are not specific types or are not declared. } @@ -84,20 +87,21 @@ private async Task GetValueFromGenericAsync(IMember target, Expression if (specificTypes.Length > 0) { // If target is a generic type and indexes are specific types, create specific class - return await gt.CreateSpecificTypeAsync(new ArgumentSet(specificTypes), Module, GetLoc(expr), cancellationToken); + return gt.CreateSpecificType(new ArgumentSet(specificTypes), Module, GetLoc(expr)); } else { // TODO: report too few type arguments for the Generic[]. return UnknownType; } } - break; - } + case CallExpression callExpr: // Alternative instantiation: // class A(Generic[T]): ... // x = A(1234) - specificTypes = (await EvaluateICallArgsAsync(callExpr, cancellationToken)).Select(x => x.GetPythonType()).ToArray(); + specificTypes = (await EvaluateCallArgsAsync(callExpr, cancellationToken)).Select(x => x.GetPythonType()).ToArray(); + // Callable returns instance (as opposed to a type with index expression) + returnInstance = true; break; default: @@ -109,9 +113,12 @@ private async Task GetValueFromGenericAsync(IMember target, Expression // as we resolve classes on demand. Therefore we don't know if class is generic // or not at the time of the PythonClassType creation. // TODO: figure out if we could make GenericClassType: PythonClassType, IGenericType instead. - return target is PythonClassType cls - ? await cls.CreateSpecificTypeAsync(new ArgumentSet(specificTypes), Module, GetLoc(expr), cancellationToken) - : null; + if (target is PythonClassType cls) { + var location = GetLoc(expr); + var type = cls.CreateSpecificType(new ArgumentSet(specificTypes), Module, location); + return returnInstance ? new PythonInstance(type, GetLoc(expr)) : (IMember)type; + } + return null; } private async Task> EvaluateIndexAsync(IndexExpression expr, CancellationToken cancellationToken = default) { @@ -124,16 +131,16 @@ private async Task> EvaluateIndexAsync(IndexExpression ex } } else { var index = await GetValueFromExpressionAsync(expr.Index, cancellationToken); - indices.Add(index); + indices.Add(index ?? UnknownType); } return indices; } - private async Task> EvaluateICallArgsAsync(CallExpression expr, CancellationToken cancellationToken = default) { + private async Task> EvaluateCallArgsAsync(CallExpression expr, CancellationToken cancellationToken = default) { var indices = new List(); cancellationToken.ThrowIfCancellationRequested(); - foreach (var e in expr.Args.Select(a => a.Expression).ExcludeDefault()) { - var value = await GetValueFromExpressionAsync(e, cancellationToken); + foreach (var e in expr.Args.Select(a => a.Expression)) { + var value = await GetValueFromExpressionAsync(e, cancellationToken) ?? UnknownType; indices.Add(value); } return indices; diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs index cb0f6a65e..632edfa3c 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs @@ -39,7 +39,7 @@ private async Task GetValueFromUnaryOpAsync(UnaryExpression expr, Cance case PythonOperator.Pos: return await GetValueFromUnaryOpAsync(expr, "__pos__", cancellationToken); } - return null; + return UnknownType; } private async Task GetValueFromUnaryOpAsync(UnaryExpression expr, string op, CancellationToken cancellationToken = default) { @@ -57,7 +57,7 @@ private async Task GetValueFromUnaryOpAsync(UnaryExpression expr, strin ? new PythonConstant(-value, c.Type, GetLoc(expr)) : instance; } - return null; + return UnknownType; } private async Task GetValueFromBinaryOpAsync(Expression expr, CancellationToken cancellationToken = default) { @@ -87,8 +87,8 @@ private async Task GetValueFromBinaryOpAsync(Expression expr, Cancellat return Interpreter.GetBuiltinType(BuiltinTypeId.Bool); } - var left = await GetValueFromExpressionAsync(binop.Left, cancellationToken); - var right = await GetValueFromExpressionAsync(binop.Right, cancellationToken); + var left = await GetValueFromExpressionAsync(binop.Left, cancellationToken) ?? UnknownType; + var right = await GetValueFromExpressionAsync(binop.Right, cancellationToken) ?? UnknownType; switch (binop.Operator) { case PythonOperator.Divide: diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs index 68c130a34..9d3596d33 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs @@ -192,7 +192,7 @@ private async Task GetValueFromMemberAsync(MemberExpression expr, Cance } instance = instance ?? m as IPythonInstance; - var type = m.GetPythonType(); // Try inner type + var type = m?.GetPythonType(); // Try inner type var value = type?.GetMember(expr.Name); // Class type GetMember returns a type. However, class members are @@ -226,7 +226,7 @@ private async Task GetValueFromConditionalAsync(ConditionalExpression e var trueValue = await GetValueFromExpressionAsync(expr.TrueExpression, cancellationToken); var falseValue = await GetValueFromExpressionAsync(expr.FalseExpression, cancellationToken); - return trueValue ?? falseValue; + return trueValue ?? falseValue ?? UnknownType; } public void ReportDiagnostics(Uri documentUri, DiagnosticsEntry entry) { diff --git a/src/Analysis/Ast/Impl/Analyzer/Handlers/AssignmentHandler.cs b/src/Analysis/Ast/Impl/Analyzer/Handlers/AssignmentHandler.cs index 25ae6e427..a889ec063 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Handlers/AssignmentHandler.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Handlers/AssignmentHandler.cs @@ -32,7 +32,7 @@ public async Task HandleAssignmentAsync(AssignmentStatement node, CancellationTo return; } - var value = await Eval.GetValueFromExpressionAsync(node.Right, cancellationToken); + var value = await Eval.GetValueFromExpressionAsync(node.Right, cancellationToken) ?? Eval.UnknownType; // Check PEP hint first var valueType = Eval.GetTypeFromPepHint(node.Right); if (valueType != null) { diff --git a/src/Analysis/Ast/Impl/Analyzer/Handlers/TryExceptHandler.cs b/src/Analysis/Ast/Impl/Analyzer/Handlers/TryExceptHandler.cs index 6a95acdfb..522e1cd4d 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Handlers/TryExceptHandler.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Handlers/TryExceptHandler.cs @@ -28,7 +28,7 @@ public async Task HandleTryExceptAsync(TryStatement node, CancellationToke foreach (var handler in node.Handlers.MaybeEnumerate()) { if (handler.Test != null && handler.Target is NameExpression nex) { var value = await Eval.GetValueFromExpressionAsync(handler.Test, cancellationToken); - Eval.DeclareVariable(nex.Name, value, VariableSource.Declaration, nex); + Eval.DeclareVariable(nex.Name, value ?? Eval.UnknownType, VariableSource.Declaration, nex); } await handler.Body.WalkAsync(Walker, cancellationToken); } diff --git a/src/Analysis/Ast/Impl/Analyzer/Handlers/TupleExpressionHandler.cs b/src/Analysis/Ast/Impl/Analyzer/Handlers/TupleExpressionHandler.cs index e0bc9eb71..bc664ad63 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Handlers/TupleExpressionHandler.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Handlers/TupleExpressionHandler.cs @@ -22,8 +22,8 @@ using Microsoft.Python.Parsing.Ast; namespace Microsoft.Python.Analysis.Analyzer.Handlers { - internal sealed class TupleExpressionHandler: StatementHandler { - public TupleExpressionHandler(AnalysisWalker walker): base(walker) { } + internal sealed class TupleExpressionHandler : StatementHandler { + public TupleExpressionHandler(AnalysisWalker walker) : base(walker) { } public async Task HandleTupleAssignmentAsync(TupleExpression lhs, Expression rhs, IMember value, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -34,9 +34,7 @@ public async Task HandleTupleAssignmentAsync(TupleExpression lhs, Expression rhs for (var i = 0; i < Math.Min(names.Length, returnedExpressions.Length); i++) { if (returnedExpressions[i] != null && !string.IsNullOrEmpty(names[i])) { var v = await Eval.GetValueFromExpressionAsync(returnedExpressions[i], cancellationToken); - if (v != null) { - Eval.DeclareVariable(names[i], v, VariableSource.Declaration, returnedExpressions[i]); - } + Eval.DeclareVariable(names[i], v ?? Eval.UnknownType, VariableSource.Declaration, returnedExpressions[i]); } } return; diff --git a/src/Analysis/Ast/Impl/Analyzer/Handlers/WithHandler.cs b/src/Analysis/Ast/Impl/Analyzer/Handlers/WithHandler.cs index 6cf89220e..496560b4e 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Handlers/WithHandler.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Handlers/WithHandler.cs @@ -28,7 +28,7 @@ public async Task HandleWithAsync(WithStatement node, CancellationToken cancella cancellationToken.ThrowIfCancellationRequested(); foreach (var item in node.Items.Where(x => x.Variable != null)) { var contextManager = await Eval.GetValueFromExpressionAsync(item.ContextManager, cancellationToken); - var cmType = contextManager.GetPythonType(); + var cmType = contextManager?.GetPythonType(); var enter = cmType?.GetMember(node.IsAsync ? @"__aenter__" : @"__enter__")?.GetPythonType(); if (enter != null) { diff --git a/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs b/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs index e9929825c..40af80599 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs @@ -87,7 +87,7 @@ public override async Task EvaluateAsync(CancellationToken cancellationToken = d } public override async Task WalkAsync(AssignmentStatement node, CancellationToken cancellationToken = default) { - var value = await Eval.GetValueFromExpressionAsync(node.Right, cancellationToken); + var value = await Eval.GetValueFromExpressionAsync(node.Right, cancellationToken) ?? Eval.UnknownType; foreach (var lhs in node.Left) { switch (lhs) { @@ -180,7 +180,7 @@ private async Task DeclareParameterAsync(Parameter p, int index, ParameterInfo p } else { var defaultValue = await Eval.GetValueFromExpressionAsync(p.DefaultValue, cancellationToken) ?? Eval.UnknownType; - paramType = defaultValue.GetPythonType(); + paramType = defaultValue?.GetPythonType(); if (!paramType.IsUnknown()) { pi?.SetDefaultValueType(paramType); } diff --git a/src/Analysis/Ast/Impl/Extensions/AnalysisExtensions.cs b/src/Analysis/Ast/Impl/Extensions/AnalysisExtensions.cs index 924665968..74173a3ae 100644 --- a/src/Analysis/Ast/Impl/Extensions/AnalysisExtensions.cs +++ b/src/Analysis/Ast/Impl/Extensions/AnalysisExtensions.cs @@ -54,7 +54,7 @@ private static PythonFunctionType GetOrCreateFunction(this IDocumentAnalysis ana // 'type()' in code is a function call, not a type class instantiation. if (!(analysis.GlobalScope.Variables[name]?.Value is PythonFunctionType f)) { f = PythonFunctionType.ForSpecialization(name, analysis.Document); - f.AddOverload(new PythonFunctionOverload(name, analysis.Document, LocationInfo.Empty)); + f.AddOverload(new PythonFunctionOverload(name, analysis.Document, _ => LocationInfo.Empty)); analysis.GlobalScope.DeclareVariable(name, f, VariableSource.Declaration, LocationInfo.Empty); } return f; diff --git a/src/Analysis/Ast/Impl/Extensions/MemberExtensions.cs b/src/Analysis/Ast/Impl/Extensions/MemberExtensions.cs index ae87c5d80..18306bcbf 100644 --- a/src/Analysis/Ast/Impl/Extensions/MemberExtensions.cs +++ b/src/Analysis/Ast/Impl/Extensions/MemberExtensions.cs @@ -16,7 +16,6 @@ using Microsoft.Python.Analysis.Specializations.Typing; using Microsoft.Python.Analysis.Types; using Microsoft.Python.Analysis.Values; -using Microsoft.Python.Parsing.Ast; namespace Microsoft.Python.Analysis { public static class MemberExtensions { diff --git a/src/Analysis/Ast/Impl/Specializations/Specialized.cs b/src/Analysis/Ast/Impl/Specializations/Specialized.cs index 169b57b60..9b918e69b 100644 --- a/src/Analysis/Ast/Impl/Specializations/Specialized.cs +++ b/src/Analysis/Ast/Impl/Specializations/Specialized.cs @@ -19,7 +19,7 @@ namespace Microsoft.Python.Analysis.Specializations { internal static class Specialized { public static IPythonPropertyType Property(string name, IPythonModule declaringModule, IPythonType declaringType, IMember returnValue) { var prop = new PythonPropertyType(name, declaringModule, declaringType, false, LocationInfo.Empty); - var o = new PythonFunctionOverload(prop.Name, declaringModule, LocationInfo.Empty); + var o = new PythonFunctionOverload(prop.Name, declaringModule, _ => LocationInfo.Empty); o.AddReturnValue(returnValue); prop.AddOverload(o); return prop; @@ -27,7 +27,7 @@ public static IPythonPropertyType Property(string name, IPythonModule declaringM public static IPythonFunctionType Function(string name, IPythonModule declaringModule, IPythonType declaringType, string documentation, IMember returnValue) { var prop = new PythonFunctionType(name, declaringModule, declaringType, documentation, LocationInfo.Empty); - var o = new PythonFunctionOverload(prop.Name, declaringModule, LocationInfo.Empty); + var o = new PythonFunctionOverload(prop.Name, declaringModule, _ => LocationInfo.Empty); o.AddReturnValue(returnValue); prop.AddOverload(o); return prop; diff --git a/src/Analysis/Ast/Impl/Specializations/Typing/Definitions/IGenericClassBaseType.cs b/src/Analysis/Ast/Impl/Specializations/Typing/Definitions/IGenericClassBaseType.cs index 8c0afd9a2..ca6f563e4 100644 --- a/src/Analysis/Ast/Impl/Specializations/Typing/Definitions/IGenericClassBaseType.cs +++ b/src/Analysis/Ast/Impl/Specializations/Typing/Definitions/IGenericClassBaseType.cs @@ -14,12 +14,13 @@ // permissions and limitations under the License. using System.Collections.Generic; +using Microsoft.Python.Analysis.Types; namespace Microsoft.Python.Analysis.Specializations.Typing { /// /// Represents Generic[T1, T2, ...]. Used as a base class to generic classes. /// - public interface IGenericClassBaseType { + public interface IGenericClassBaseType: IPythonType { IReadOnlyList TypeArgs { get; } } } diff --git a/src/Analysis/Ast/Impl/Specializations/Typing/Types/GenericClassBaseType.cs b/src/Analysis/Ast/Impl/Specializations/Typing/Types/GenericClassBaseType.cs index 48676c160..8327d7644 100644 --- a/src/Analysis/Ast/Impl/Specializations/Typing/Types/GenericClassBaseType.cs +++ b/src/Analysis/Ast/Impl/Specializations/Typing/Types/GenericClassBaseType.cs @@ -17,6 +17,9 @@ using Microsoft.Python.Analysis.Types; namespace Microsoft.Python.Analysis.Specializations.Typing.Types { + /// + /// Represents Generic[T1, T2, ...] + /// internal sealed class GenericClassBaseType: PythonClassType, IGenericClassBaseType { internal GenericClassBaseType(IReadOnlyList typeArgs, IPythonModule declaringModule, LocationInfo location) : base("Generic", declaringModule, location) { diff --git a/src/Analysis/Ast/Impl/Specializations/Typing/Types/GenericType.cs b/src/Analysis/Ast/Impl/Specializations/Typing/Types/GenericType.cs index 1fc67e787..5c7b59cea 100644 --- a/src/Analysis/Ast/Impl/Specializations/Typing/Types/GenericType.cs +++ b/src/Analysis/Ast/Impl/Specializations/Typing/Types/GenericType.cs @@ -16,25 +16,23 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Python.Analysis.Types; using Microsoft.Python.Analysis.Values; namespace Microsoft.Python.Analysis.Specializations.Typing.Types { internal delegate IPythonType SpecificTypeConstructor( - IReadOnlyList typeArgs, - IPythonModule declaringModule, + IReadOnlyList typeArgs, + IPythonModule declaringModule, LocationInfo location); /// /// Base class for generic types and type declarations. /// internal class GenericType : IGenericType { - private readonly SpecificTypeConstructor _specificTypeConstructor; + internal SpecificTypeConstructor SpecificTypeConstructor { get; } /// - /// Constructs generic type with generic parameters. Typically used + /// Constructs generic type with generic type parameters. Typically used /// in generic classes such as when handling Generic[_T] base. /// public GenericType(string name, IPythonModule declaringModule, IReadOnlyList parameters) @@ -46,9 +44,22 @@ public GenericType(string name, IPythonModule declaringModule, IReadOnlyList - public GenericType(string name, IPythonModule declaringModule, SpecificTypeConstructor specificTypeConstructor) - : this(name, declaringModule) { - _specificTypeConstructor = specificTypeConstructor ?? throw new ArgumentNullException(nameof(specificTypeConstructor)); + /// Type name including parameters, such as Iterator[T] + /// Declaring module. + /// Constructor of specific types. + /// Type id. Used in type comparisons such as when matching + /// function arguments. For example, Iterator[T] normally has type id of ListIterator. + /// Optional type parameters as declared by TypeVar. + public GenericType( + string name, + IPythonModule declaringModule, + SpecificTypeConstructor specificTypeConstructor, + BuiltinTypeId typeId = BuiltinTypeId.Unknown, + IReadOnlyList parameters = null + ) : this(name, declaringModule) { + SpecificTypeConstructor = specificTypeConstructor ?? throw new ArgumentNullException(nameof(specificTypeConstructor)); + TypeId = typeId; + Parameters = parameters ?? Array.Empty(); } private GenericType(string name, IPythonModule declaringModule) { @@ -60,14 +71,14 @@ private GenericType(string name, IPythonModule declaringModule) { /// Type parameters such as in Tuple[T1, T2. ...] or /// Generic[_T1, _T2, ...] as returned by TypeVar. /// - public IReadOnlyList Parameters { get; } = Array.Empty(); + public IReadOnlyList Parameters { get; } /// /// Creates instance of a type information with the specific /// type arguments from a generic template. /// public IPythonType CreateSpecificType(IReadOnlyList typeArguments, IPythonModule declaringModule, LocationInfo location = null) - => _specificTypeConstructor(typeArguments, declaringModule, location); + => SpecificTypeConstructor(typeArguments, declaringModule, location); #region IPythonType public string Name { get; } @@ -75,7 +86,7 @@ public IPythonType CreateSpecificType(IReadOnlyList typeArguments, public PythonMemberType MemberType => PythonMemberType.Generic; public IMember GetMember(string name) => null; public IEnumerable GetMemberNames() => Enumerable.Empty(); - public BuiltinTypeId TypeId => BuiltinTypeId.Unknown; + public BuiltinTypeId TypeId { get; } = BuiltinTypeId.Unknown; public virtual string Documentation => Name; public bool IsBuiltin => false; public bool IsAbstract => true; @@ -95,9 +106,21 @@ public IMember CreateInstance(string typeName, LocationInfo location, IArgumentS public virtual IMember Call(IPythonInstance instance, string memberName, IArgumentSet args) => DeclaringModule.Interpreter.UnknownType; public virtual IMember Index(IPythonInstance instance, object index) => DeclaringModule.Interpreter.UnknownType; - public Task CreateSpecificTypeAsync(IArgumentSet typeArguments, IPythonModule declaringModule, LocationInfo location, CancellationToken cancellationToken = default) - => Task.FromResult(CreateSpecificType(typeArguments.Arguments.Select(a => a.Value).OfType().ToArray(), declaringModule, location)); - + public IPythonType CreateSpecificType(IArgumentSet typeArguments, IPythonModule declaringModule, LocationInfo location) + => CreateSpecificType(typeArguments.Arguments.Select(a => a.Value).OfType().ToArray(), declaringModule, location); #endregion + + public override bool Equals(object other) { + if (other == null) { + return false; + } + if (TypeId != BuiltinTypeId.Unknown && other is IPythonType t && t.TypeId == TypeId) { + return true; + } + return this == other; + } + + public override int GetHashCode() + => TypeId != BuiltinTypeId.Unknown ? TypeId.GetHashCode() : base.GetHashCode(); } } diff --git a/src/Analysis/Ast/Impl/Specializations/Typing/Types/TypingListType.cs b/src/Analysis/Ast/Impl/Specializations/Typing/Types/TypingListType.cs index 400e7c4ab..7b0e27857 100644 --- a/src/Analysis/Ast/Impl/Specializations/Typing/Types/TypingListType.cs +++ b/src/Analysis/Ast/Impl/Specializations/Typing/Types/TypingListType.cs @@ -13,7 +13,6 @@ // See the Apache Version 2.0 License for specific language governing // permissions and limitations under the License. -using System; using Microsoft.Python.Analysis.Specializations.Typing.Values; using Microsoft.Python.Analysis.Types; using Microsoft.Python.Analysis.Types.Collections; diff --git a/src/Analysis/Ast/Impl/Specializations/Typing/TypingModule.cs b/src/Analysis/Ast/Impl/Specializations/Typing/TypingModule.cs index 10dd98583..1e0704816 100644 --- a/src/Analysis/Ast/Impl/Specializations/Typing/TypingModule.cs +++ b/src/Analysis/Ast/Impl/Specializations/Typing/TypingModule.cs @@ -15,11 +15,10 @@ using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Python.Analysis.Modules; using Microsoft.Python.Analysis.Specializations.Typing.Types; using Microsoft.Python.Analysis.Types; +using Microsoft.Python.Analysis.Utilities; using Microsoft.Python.Analysis.Values; using Microsoft.Python.Core; using Microsoft.Python.Parsing; @@ -49,7 +48,7 @@ private void SpecializeMembers() { var o = new PythonFunctionOverload(fn.Name, this, _ => fn.Location); // When called, create generic parameter type. For documentation // use original TypeVar declaration so it appear as a tooltip. - o.SetReturnValueProvider((interpreter, overload, location, args) + o.SetReturnValueProvider((interpreter, overload, location, args) => GenericTypeParameter.FromTypeVar(args, interpreter, location)); fn.AddOverload(o); @@ -151,7 +150,7 @@ private void SpecializeMembers() { _members["Optional"] = new GenericType("Optional", this, (typeArgs, module, location) => CreateOptional(typeArgs)); _members["Type"] = new GenericType("Type", this, (typeArgs, module, location) => CreateType(typeArgs)); - _members["Generic"] = new GenericType("Generic", this, (typeArgs, module, location) => CreateGenericBase(typeArgs, module)); + _members["Generic"] = new GenericType("Generic", this, CreateGenericBase); } @@ -162,18 +161,28 @@ private LocationInfo GetMemberLocation(string name) private IPythonType CreateListType(string typeName, BuiltinTypeId typeId, IReadOnlyList typeArgs, bool isMutable) { if (typeArgs.Count == 1) { - return TypingTypeFactory.CreateListType(Interpreter, typeName, typeId, typeArgs[0], isMutable); + // If argument is generic type parameter then this is still a generic specification + // except instead of 'List' as in 'from typing import List' it is a template + // like in 'class A(Generic[T], List[T]) + return typeArgs[0] is IGenericTypeParameter + ? ToGenericTemplate(typeName, typeArgs.OfType().ToArray(), BuiltinTypeId.List) + : TypingTypeFactory.CreateListType(Interpreter, typeName, typeId, typeArgs[0], isMutable); } // TODO: report wrong number of arguments return Interpreter.UnknownType; } private IPythonType CreateTupleType(IReadOnlyList typeArgs) - => TypingTypeFactory.CreateTupleType(Interpreter, typeArgs); + => typeArgs.Any(a => a is IGenericTypeParameter) + ? ToGenericTemplate("Tuple", typeArgs.OfType().ToArray(), BuiltinTypeId.Tuple) + : TypingTypeFactory.CreateTupleType(Interpreter, typeArgs); private IPythonType CreateIteratorType(IReadOnlyList typeArgs) { if (typeArgs.Count == 1) { - return TypingTypeFactory.CreateIteratorType(Interpreter, typeArgs[0]); + // If argument is generic type parameter then this is still a generic specification + return typeArgs[0] is IGenericTypeParameter + ? ToGenericTemplate("Iterator", typeArgs.OfType().ToArray(), BuiltinTypeId.ListIterator) + : TypingTypeFactory.CreateIteratorType(Interpreter, typeArgs[0]); } // TODO: report wrong number of arguments return Interpreter.UnknownType; @@ -181,7 +190,10 @@ private IPythonType CreateIteratorType(IReadOnlyList typeArgs) { private IPythonType CreateDictionary(string typeName, IReadOnlyList typeArgs, bool isMutable) { if (typeArgs.Count == 2) { - return TypingTypeFactory.CreateDictionary(Interpreter, typeName, typeArgs[0], typeArgs[1], isMutable); + // If argument is generic type parameter then this is still a generic specification + return typeArgs.Any(a => a is IGenericTypeParameter) + ? ToGenericTemplate(typeName, typeArgs.OfType().ToArray(), BuiltinTypeId.Dict) + : TypingTypeFactory.CreateDictionary(Interpreter, typeName, typeArgs[0], typeArgs[1], isMutable); } // TODO: report wrong number of arguments return Interpreter.UnknownType; @@ -189,7 +201,10 @@ private IPythonType CreateDictionary(string typeName, IReadOnlyList private IPythonType CreateKeysViewType(IReadOnlyList typeArgs) { if (typeArgs.Count == 1) { - return TypingTypeFactory.CreateKeysViewType(Interpreter, typeArgs[0]); + // If argument is generic type parameter then this is still a generic specification + return typeArgs[0] is IGenericTypeParameter + ? ToGenericTemplate("KeysView", typeArgs.OfType().ToArray(), BuiltinTypeId.ListIterator) + : TypingTypeFactory.CreateKeysViewType(Interpreter, typeArgs[0]); } // TODO: report wrong number of arguments return Interpreter.UnknownType; @@ -197,7 +212,10 @@ private IPythonType CreateKeysViewType(IReadOnlyList typeArgs) { private IPythonType CreateValuesViewType(IReadOnlyList typeArgs) { if (typeArgs.Count == 1) { - return TypingTypeFactory.CreateValuesViewType(Interpreter, typeArgs[0]); + // If argument is generic type parameter then this is still a generic specification + return typeArgs[0] is IGenericTypeParameter + ? ToGenericTemplate("ValuesView", typeArgs.OfType().ToArray(), BuiltinTypeId.ListIterator) + : TypingTypeFactory.CreateValuesViewType(Interpreter, typeArgs[0]); } // TODO: report wrong number of arguments return Interpreter.UnknownType; @@ -205,7 +223,10 @@ private IPythonType CreateValuesViewType(IReadOnlyList typeArgs) { private IPythonType CreateItemsViewType(IReadOnlyList typeArgs) { if (typeArgs.Count == 2) { - return TypingTypeFactory.CreateItemsViewType(Interpreter, typeArgs[0], typeArgs[1]); + // If argument is generic type parameter then this is still a generic specification + return typeArgs.Any(a => a is IGenericTypeParameter) + ? ToGenericTemplate("ItemsView", typeArgs.OfType().ToArray(), BuiltinTypeId.ListIterator) + : TypingTypeFactory.CreateItemsViewType(Interpreter, typeArgs[0], typeArgs[1]); } // TODO: report wrong number of arguments return Interpreter.UnknownType; @@ -295,14 +316,14 @@ private IPythonType CreateType(IReadOnlyList typeArgs) { return Interpreter.UnknownType; } - private IPythonType CreateGenericBase(IReadOnlyList typeArgs, IPythonModule declaringModule) { + private IPythonType CreateGenericBase(IReadOnlyList typeArgs, IPythonModule declaringModule, LocationInfo location) { // Handle Generic[_T1, _T2, ...]. _T1, et al are IGenericTypeParameter from TypeVar. // Hold the parameter until concrete type is provided at the time // of the class instantiation. if (typeArgs.Count > 0) { var genericTypes = typeArgs.OfType().ToArray(); if (genericTypes.Length == typeArgs.Count) { - return new GenericType("Generic", declaringModule, genericTypes); + return new GenericClassBaseType(genericTypes, declaringModule, location); } else { // TODO: report some type arguments are undefined. } @@ -310,5 +331,10 @@ private IPythonType CreateGenericBase(IReadOnlyList typeArgs, IPyth // TODO: report wrong number of arguments return Interpreter.UnknownType; } + + private IPythonType ToGenericTemplate(string typeName, IGenericTypeParameter[] typeArgs, BuiltinTypeId typeId) + => _members[typeName] is GenericType gt + ? new GenericType(CodeFormatter.FormatSequence(typeName, '[', typeArgs), this, gt.SpecificTypeConstructor, typeId, typeArgs) + : Interpreter.UnknownType; } } diff --git a/src/Analysis/Ast/Impl/Types/ArgumentSet.cs b/src/Analysis/Ast/Impl/Types/ArgumentSet.cs index 460127466..5ee457d64 100644 --- a/src/Analysis/Ast/Impl/Types/ArgumentSet.cs +++ b/src/Analysis/Ast/Impl/Types/ArgumentSet.cs @@ -37,6 +37,7 @@ internal sealed class ArgumentSet : IArgumentSet { private readonly List _errors = new List(); private readonly ListArg _listArgument; private readonly DictArg _dictArgument; + private readonly ExpressionEval _eval; private bool _evaluated; public static IArgumentSet Empty = new ArgumentSet(); @@ -46,7 +47,7 @@ internal sealed class ArgumentSet : IArgumentSet { public IDictionaryArgument DictionaryArgument => _dictArgument; public IReadOnlyList Errors => _errors; public int OverloadIndex { get; } - public IExpressionEvaluator Eval { get; } + public IExpressionEvaluator Eval => _eval; private ArgumentSet() { } @@ -77,7 +78,7 @@ public ArgumentSet(IPythonFunctionType fn, int overloadIndex, IPythonInstance in /// Module that contains the call expression. /// Evaluator that can calculate values of arguments from their respective expressions. public ArgumentSet(IPythonFunctionType fn, int overloadIndex, IPythonInstance instance, CallExpression callExpr, IPythonModule module, ExpressionEval eval) { - Eval = eval; + _eval = eval; OverloadIndex = overloadIndex; var overload = fn.Overloads[overloadIndex]; @@ -271,19 +272,19 @@ public async Task EvaluateAsync(CancellationToken cancellationToken } foreach (var a in _arguments.Where(x => x.Value == null)) { - a.Value = await Eval.GetValueFromExpressionAsync(a.Expression, cancellationToken); + a.Value = await Eval.GetValueFromExpressionAsync(a.Expression, cancellationToken) ?? _eval.UnknownType; } if (_listArgument != null) { foreach (var e in _listArgument.Expressions) { - var value = await Eval.GetValueFromExpressionAsync(e, cancellationToken); + var value = await Eval.GetValueFromExpressionAsync(e, cancellationToken) ?? _eval.UnknownType; _listArgument._Values.Add(value); } } if (_dictArgument != null) { foreach (var e in _dictArgument.Expressions) { - var value = await Eval.GetValueFromExpressionAsync(e.Value, cancellationToken); + var value = await Eval.GetValueFromExpressionAsync(e.Value, cancellationToken) ?? _eval.UnknownType; _dictArgument._Args[e.Key] = value; } } diff --git a/src/Analysis/Ast/Impl/Types/Definitions/IPythonClassType.cs b/src/Analysis/Ast/Impl/Types/Definitions/IPythonClassType.cs index 046c52886..852860bef 100644 --- a/src/Analysis/Ast/Impl/Types/Definitions/IPythonClassType.cs +++ b/src/Analysis/Ast/Impl/Types/Definitions/IPythonClassType.cs @@ -35,5 +35,11 @@ public interface IPythonClassType : IPythonType { /// Base types. /// IReadOnlyList Bases { get; } + + /// + /// If class is created off generic template, name/type + /// pairs of the generic parameter name / actual supplied type. + /// + IReadOnlyDictionary GenericParameters { get; } } } diff --git a/src/Analysis/Ast/Impl/Types/Definitions/IPythonFunctionOverload.cs b/src/Analysis/Ast/Impl/Types/Definitions/IPythonFunctionOverload.cs index a284a982f..aab6fa5bf 100644 --- a/src/Analysis/Ast/Impl/Types/Definitions/IPythonFunctionOverload.cs +++ b/src/Analysis/Ast/Impl/Types/Definitions/IPythonFunctionOverload.cs @@ -50,17 +50,20 @@ public interface IPythonFunctionOverload { /// Determines return value type given arguments for the particular call. /// For annotated or stubbed functions the annotation type is always returned. /// - IMember GetReturnValue(LocationInfo callLocation, IArgumentSet args); + /// Call arguments or type arguments. + /// Invoking class instance. In case of generics it is instance of the specific type + /// as opposed to declaring type which is the generic template class. + /// Call expression location, if any. + IMember Call(IArgumentSet args, IPythonType self, LocationInfo callLocation = null); /// /// Return value documentation. /// - string ReturnDocumentation { get; } - - /// - /// Function definition is decorated with @overload. - /// - bool IsOverload { get; } + /// If function is in generic class it may need to know specific type + /// in order to be able to determine the return type. Passing null will yield either + /// static return type determined during analysis or type supplied by dynamic + /// return type provider. + string GetReturnDocumentation(IPythonType self = null); /// /// Return type as determined from evaluation or from the return type annotation. diff --git a/src/Analysis/Ast/Impl/Types/Definitions/IPythonTemplateType.cs b/src/Analysis/Ast/Impl/Types/Definitions/IPythonTemplateType.cs index 7e1452340..af36af8b7 100644 --- a/src/Analysis/Ast/Impl/Types/Definitions/IPythonTemplateType.cs +++ b/src/Analysis/Ast/Impl/Types/Definitions/IPythonTemplateType.cs @@ -13,10 +13,6 @@ // See the Apache Version 2.0 License for specific language governing // permissions and limitations under the License. -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - namespace Microsoft.Python.Analysis.Types { /// /// Represents type that can be a template for specific types. @@ -27,6 +23,6 @@ public interface IPythonTemplateType: IPythonType { /// Creates instance of a type information with the specific /// type arguments from a generic template. /// - Task CreateSpecificTypeAsync(IArgumentSet typeArguments, IPythonModule declaringModule, LocationInfo location, CancellationToken cancellationToken = default); + IPythonType CreateSpecificType(IArgumentSet typeArguments, IPythonModule declaringModule, LocationInfo location); } } diff --git a/src/Analysis/Ast/Impl/Types/PythonClassType.cs b/src/Analysis/Ast/Impl/Types/PythonClassType.cs index a36fb0971..f1f0f522f 100644 --- a/src/Analysis/Ast/Impl/Types/PythonClassType.cs +++ b/src/Analysis/Ast/Impl/Types/PythonClassType.cs @@ -18,7 +18,6 @@ using System.Diagnostics; using System.Linq; using System.Threading; -using System.Threading.Tasks; using Microsoft.Python.Analysis.Modules; using Microsoft.Python.Analysis.Specializations.Typing; using Microsoft.Python.Analysis.Types.Collections; @@ -34,6 +33,7 @@ internal class PythonClassType : PythonType, IPythonClassType, IPythonTemplateTy private readonly object _lock = new object(); private readonly AsyncLocal _processing = new AsyncLocal(); private IReadOnlyList _mro; + private Dictionary _genericParameters; // For tests internal PythonClassType(string name, IPythonModule declaringModule, LocationInfo location = null) @@ -125,6 +125,18 @@ public override IMember CreateInstance(string typeName, LocationInfo location, I } return new PythonInstance(this, location); } + + public override IMember Index(IPythonInstance instance, object index) { + var defaultReturn = base.Index(instance, index); + var fromBases = Bases + .MaybeEnumerate() + .Select(b => b.Index(instance, index)) + .Except(new[] { defaultReturn, UnknownType }) + .FirstOrDefault(); + + return fromBases ?? defaultReturn; + } + #endregion #region IPythonClass @@ -147,6 +159,9 @@ public IReadOnlyList Mro { } } } + + public IReadOnlyDictionary GenericParameters + => _genericParameters ?? EmptyDictionary.Instance; #endregion internal void SetBases(IEnumerable bases) { @@ -235,20 +250,25 @@ private bool Push(IPythonClassType cls) { public bool Equals(IPythonClassType other) => Name == other?.Name && DeclaringModule.Equals(other?.DeclaringModule); - public async Task CreateSpecificTypeAsync(IArgumentSet args, IPythonModule declaringModule, LocationInfo location, CancellationToken cancellationToken = default) { - cancellationToken.ThrowIfCancellationRequested(); - - var genericBases = Bases.Where(b => b is IGenericType).ToArray(); + public IPythonType CreateSpecificType(IArgumentSet args, IPythonModule declaringModule, LocationInfo location = null) { + location = location ?? LocationInfo.Empty; + var allGenericClassBases = Bases.OfType().ToArray(); // Generic[T1, T2, ...] + var genericClassBase = allGenericClassBases.FirstOrDefault(); + var genericClassParameters = genericClassBase?.TypeArgs ?? Array.Empty(); // TODO: handle optional generics as class A(Generic[_T1], Optional[Generic[_T2]]) - if (genericBases.Length != args.Arguments.Count) { + if (genericClassParameters.Count != args.Arguments.Count) { // TODO: report parameters mismatch. } - // Create concrete type - var bases = args.Arguments.Select(a => a.Value).OfType().ToArray(); - var specificName = CodeFormatter.FormatSequence(Name, '[', bases); + // Record match between class template parameters and actual type arguments. + var specificTypes = args.Arguments.Select(a => a.Value).OfType().ToArray(); + var specificName = CodeFormatter.FormatSequence(Name, '[', specificTypes); var classType = new PythonClassType(specificName, declaringModule); + // Methods returning generic types need to know how to match generic + // parameter name to the actual supplied type. + StoreGenericParameters(classType, genericClassParameters.ToArray(), specificTypes); + // Prevent reentrancy when resolving generic class where // method may be returning instance of type of the same class. if (!Push(classType)) { @@ -256,11 +276,32 @@ public async Task CreateSpecificTypeAsync(IArgumentSet args, IPytho } try { - // Optimistically use what is available even if there is an argument mismatch. - // TODO: report unresolved types? + // Create specific bases since we may have generic types there. + // Match generic parameter names to base type parameter names. + // Consider 'class A(Generic[T], B[T], C[E]): ...' + var genericTypeBases = Bases.Except(allGenericClassBases).OfType().ToArray(); + // Start with regular types, then add specific types for all generic types. + var bases = Bases.Except(genericTypeBases).Except(allGenericClassBases).ToList(); + + // Create specific types for generic type bases + // it for generic types but not Generic[T, ...] itself. + foreach (var gt in genericTypeBases) { + var st = gt.Parameters + .Select(p => classType.GenericParameters.TryGetValue(p.Name, out var t) ? t : null) + .Where(p => !p.IsUnknown()) + .ToArray(); + if (st.Length > 0) { + var type = gt.CreateSpecificType(new ArgumentSet(st), classType.DeclaringModule, location); + if (!type.IsUnknown()) { + bases.Add(type); + } + } + } classType.SetBases(bases); // Add members from the template class (this one). + // Members must be clones rather than references since + // we are going to set specific types on them. classType.AddMembers(this, true); // Resolve return types of methods, if any were annotated as generics @@ -268,36 +309,18 @@ public async Task CreateSpecificTypeAsync(IArgumentSet args, IPytho .Except(new[] { "__class__", "__bases__", "__base__" }) .ToDictionary(n => n, n => classType.GetMember(n)); + // Create specific types. + // Functions handle generics internally upon the call to Call. foreach (var m in members) { switch (m.Value) { - case IPythonFunctionType fn: { - foreach (var o in fn.Overloads.OfType()) { - var returnType = o.StaticReturnValue.GetPythonType(); - if (returnType is PythonClassType cls && cls.IsGeneric()) { - // -> A[_E] - if (!cls.Equals(classType)) { - // Prevent reentrancy - var specificReturnValue = await cls.CreateSpecificTypeAsync(args, declaringModule, location, cancellationToken); - o.SetReturnValue(new PythonInstance(specificReturnValue, location), true); - } - } else if (returnType is IGenericTypeParameter) { - // -> _T - var b = bases.FirstOrDefault(); - if (b != null) { - o.SetReturnValue(new PythonInstance(b, location), true); - } - } - } - break; - } case IPythonTemplateType tt: { - var specificType = await tt.CreateSpecificTypeAsync(args, declaringModule, location, cancellationToken); + var specificType = tt.CreateSpecificType(args, declaringModule, location); classType.AddMember(m.Key, specificType, true); break; } case IPythonInstance inst: { if (inst.GetPythonType() is IPythonTemplateType tt && tt.IsGeneric()) { - var specificType = await tt.CreateSpecificTypeAsync(args, declaringModule, location, cancellationToken); + var specificType = tt.CreateSpecificType(args, declaringModule, location); classType.AddMember(m.Key, new PythonInstance(specificType, location), true); } break; @@ -309,5 +332,13 @@ public async Task CreateSpecificTypeAsync(IArgumentSet args, IPytho } return classType; } + + private void StoreGenericParameters(PythonClassType classType, IGenericTypeParameter[] genericParameters, IPythonType[] specificTypes) { + classType._genericParameters = new Dictionary(); + for (var i = 0; i < genericParameters.Length; i++) { + var gb = genericParameters[i]; + classType._genericParameters[gb.Name] = i < specificTypes.Length ? specificTypes[i] : UnknownType.GetPythonType(); + } + } } } diff --git a/src/Analysis/Ast/Impl/Types/PythonFunctionOverload.cs b/src/Analysis/Ast/Impl/Types/PythonFunctionOverload.cs index 000bf85d0..f38763c99 100644 --- a/src/Analysis/Ast/Impl/Types/PythonFunctionOverload.cs +++ b/src/Analysis/Ast/Impl/Types/PythonFunctionOverload.cs @@ -41,6 +41,7 @@ public delegate IMember ReturnValueProvider( internal sealed class PythonFunctionOverload : IPythonFunctionOverload, ILocatedMember { private readonly Func _locationProvider; private readonly IPythonModule _declaringModule; + private readonly string _returnDocumentation; // Allow dynamic function specialization, such as defining return types for builtin // functions that are impossible to scrape and that are missing from stubs. @@ -52,17 +53,12 @@ internal sealed class PythonFunctionOverload : IPythonFunctionOverload, ILocated private Func _documentationProvider; private bool _fromAnnotation; - public PythonFunctionOverload(string name, IPythonModule declaringModule, LocationInfo location) - : this(name, declaringModule, _ => location ?? LocationInfo.Empty) { - _declaringModule = declaringModule; - } - public PythonFunctionOverload(FunctionDefinition fd, IPythonClassMember classMember, IPythonModule declaringModule, LocationInfo location) : this(fd.Name, declaringModule, _ => location) { FunctionDefinition = fd; ClassMember = classMember; var ast = (declaringModule as IDocument)?.Analysis.Ast; - ReturnDocumentation = ast != null ? fd.ReturnAnnotation?.ToCodeString(ast) : null; + _returnDocumentation = ast != null ? fd.ReturnAnnotation?.ToCodeString(ast) : null; } public PythonFunctionOverload(string name, IPythonModule declaringModule, Func locationProvider) { @@ -73,7 +69,7 @@ public PythonFunctionOverload(string name, IPythonModule declaringModule, Func parameters) => Parameters = parameters; - internal void SetDocumentationProvider(Func documentationProvider) + internal void SetDocumentationProvider(Func documentationProvider) => _documentationProvider = _documentationProvider ?? documentationProvider; internal void AddReturnValue(IMember value) { @@ -117,14 +113,50 @@ public string Documentation { } } - public string ReturnDocumentation { get; } - public bool IsOverload { get; private set; } + public string GetReturnDocumentation(IPythonType self = null) { + if (self == null) { + return _returnDocumentation; + } + var returnType = StaticReturnValue.GetPythonType(); + switch (returnType) { + case PythonClassType cls when cls.IsGeneric(): { + // -> A[_T1, _T2, ...] + // Match arguments + var typeArgs = cls.GenericParameters.Keys + .Select(n => cls.GenericParameters.TryGetValue(n, out var t) ? t : null) + .ExcludeDefault() + .ToArray(); + var specificReturnValue = cls.CreateSpecificType(new ArgumentSet(typeArgs), _declaringModule); + return specificReturnValue.Name; + } + case IGenericTypeParameter gtp1 when self is IPythonClassType cls: { + // -> _T + if (cls.GenericParameters.TryGetValue(gtp1.Name, out var specificType)) { + return specificType.Name; + } + // Try returning the constraint + // TODO: improve this, the heuristic is pretty basic and tailored to simple func(_T) -> _T + var name = StaticReturnValue.GetPythonType()?.Name; + var typeDefVar = _declaringModule.Analysis.GlobalScope.Variables[name]; + if (typeDefVar?.Value is IGenericTypeParameter gtp2) { + var t = gtp2.Constraints.FirstOrDefault(); + if (t != null) { + return t.Name; + } + } + break; + } + } + return _returnDocumentation; + } + public IReadOnlyList Parameters { get; private set; } = Array.Empty(); public LocationInfo Location => _locationProvider?.Invoke(Name) ?? LocationInfo.Empty; public PythonMemberType MemberType => PythonMemberType.Function; public IMember StaticReturnValue { get; private set; } - public IMember GetReturnValue(LocationInfo callLocation, IArgumentSet args) { + public IMember Call(IArgumentSet args, IPythonType self, LocationInfo callLocation = null) { + callLocation = callLocation ?? LocationInfo.Empty; if (!_fromAnnotation) { // First try supplied specialization callback. var rt = _returnValueProvider?.Invoke(_declaringModule, this, callLocation, args); @@ -133,50 +165,41 @@ public IMember GetReturnValue(LocationInfo callLocation, IArgumentSet args) { } } - // If function returns generic, try to return the incoming argument - // TODO: improve this, the heuristic is pretty basic and tailored to simple func(_T) -> _T - IMember retValue = null; - if (StaticReturnValue.GetPythonType() is IGenericTypeParameter) { - if (args.Arguments.Count > 0) { - retValue = args.Arguments[0].Value as IMember; - } + // If function returns generic, determine actual type based on the passed in specific type (self). + if (!(self is IPythonClassType selfClassType)) { + return StaticReturnValue; + } - if (retValue == null) { - // Try returning the constraint - var name = StaticReturnValue.GetPythonType()?.Name; - var typeDefVar = _declaringModule.Analysis.GlobalScope.Variables[name]; - if (typeDefVar?.Value is IGenericTypeParameter gtp) { - retValue = gtp.Constraints.FirstOrDefault(); + var returnType = StaticReturnValue.GetPythonType(); + switch (returnType) { + case PythonClassType cls when cls.IsGeneric(): { + // -> A[_T1, _T2, ...] + // Match arguments + var typeArgs = selfClassType.GenericParameters.Keys + .Select(n => selfClassType.GenericParameters.TryGetValue(n, out var t) ? t : null) + .ExcludeDefault() + .ToArray(); + var specificReturnValue = cls.CreateSpecificType(new ArgumentSet(typeArgs), _declaringModule, callLocation); + return new PythonInstance(specificReturnValue, callLocation); } - } - } - return retValue ?? StaticReturnValue; - } - #endregion + case IGenericTypeParameter gtp1: { + // -> _T + if (selfClassType.GenericParameters.TryGetValue(gtp1.Name, out var specificType)) { + return new PythonInstance(specificType, callLocation); + } + // Try returning the constraint + // TODO: improve this, the heuristic is pretty basic and tailored to simple func(_T) -> _T + var name = StaticReturnValue.GetPythonType()?.Name; + var typeDefVar = _declaringModule.Analysis.GlobalScope.Variables[name]; + if (typeDefVar?.Value is IGenericTypeParameter gtp2) { + return gtp2.Constraints.FirstOrDefault(); + } - private void ProcessDecorators(FunctionDefinition fd) { - foreach (var dec in (fd.Decorators?.Decorators).MaybeEnumerate().OfType()) { - // TODO: warn about incompatible combinations. - switch (dec.Name) { - case @"overload": - IsOverload = true; break; - } + } } + return StaticReturnValue; } - - //private sealed class ReturnValueCache { - // private const int MaxResults = 10; - // private readonly Dictionary _results = new Dictionary(new ArgumentSetComparer()); - - // public bool TryGetResult(IReadOnlyList args, out IMember result) - // => _results.TryGetValue(new ArgumentSet(args), out result); - - // public void AddResult(IReadOnlyList args, out IMember result) { - // var key = new ArgumentSet(args); - // Debug.Assert(!_results.ContainsKey(key)); - // _results[key] = result; - // } - //} + #endregion } } diff --git a/src/Analysis/Ast/Impl/Types/PythonFunctionType.cs b/src/Analysis/Ast/Impl/Types/PythonFunctionType.cs index 889b0805f..05b3f012e 100644 --- a/src/Analysis/Ast/Impl/Types/PythonFunctionType.cs +++ b/src/Analysis/Ast/Impl/Types/PythonFunctionType.cs @@ -101,7 +101,7 @@ public override PythonMemberType MemberType public override IMember Call(IPythonInstance instance, string memberName, IArgumentSet args) { // Now we can go and find overload with matching arguments. var overload = Overloads[args.OverloadIndex]; - return overload?.GetReturnValue(instance?.Location ?? LocationInfo.Empty, args); + return overload?.Call(args, instance?.GetPythonType() ?? DeclaringType, instance?.Location); } internal override void SetDocumentationProvider(Func provider) { diff --git a/src/Analysis/Ast/Impl/Types/PythonPropertyType.cs b/src/Analysis/Ast/Impl/Types/PythonPropertyType.cs index bb8d11b3e..6d91a8d74 100644 --- a/src/Analysis/Ast/Impl/Types/PythonPropertyType.cs +++ b/src/Analysis/Ast/Impl/Types/PythonPropertyType.cs @@ -44,10 +44,10 @@ public PythonPropertyType(string name, IPythonModule declaringModule, IPythonTyp public string Description => Type == null ? Resources.PropertyOfUnknownType : Resources.PropertyOfType.FormatUI(Type.Name); public override IMember Call(IPythonInstance instance, string memberName, IArgumentSet args) - => _getter.GetReturnValue(instance?.Location ?? LocationInfo.Empty, args); + => _getter.Call(args, instance?.GetPythonType() ?? DeclaringType, instance?.Location); #endregion internal void AddOverload(IPythonFunctionOverload overload) => _getter = _getter ?? overload; - private IPythonType Type => _getter?.GetReturnValue(null, ArgumentSet.Empty)?.GetPythonType(); + private IPythonType Type => _getter?.Call(ArgumentSet.Empty, DeclaringType)?.GetPythonType(); } } diff --git a/src/Analysis/Ast/Impl/Values/PythonInstance.cs b/src/Analysis/Ast/Impl/Values/PythonInstance.cs index 07f2cf17d..259d30ccb 100644 --- a/src/Analysis/Ast/Impl/Values/PythonInstance.cs +++ b/src/Analysis/Ast/Impl/Values/PythonInstance.cs @@ -59,7 +59,7 @@ public virtual IMember Call(string memberName, IArgumentSet args) { public virtual IPythonIterator GetIterator() { var iteratorFunc = Type.GetMember(@"__iter__") as IPythonFunctionType; var o = iteratorFunc?.Overloads.FirstOrDefault(); - var instance = o?.GetReturnValue(LocationInfo.Empty, ArgumentSet.Empty); + var instance = o?.Call(ArgumentSet.Empty, Type); return instance != null ? new PythonInstanceIterator(instance, Type.DeclaringModule.Interpreter) : null; } diff --git a/src/Analysis/Ast/Test/FluentAssertions/PythonFunctionOverloadAssertions.cs b/src/Analysis/Ast/Test/FluentAssertions/PythonFunctionOverloadAssertions.cs index 4d2b9268f..2744f69b3 100644 --- a/src/Analysis/Ast/Test/FluentAssertions/PythonFunctionOverloadAssertions.cs +++ b/src/Analysis/Ast/Test/FluentAssertions/PythonFunctionOverloadAssertions.cs @@ -31,7 +31,7 @@ public PythonFunctionOverloadAssertions(IPythonFunctionOverload pythonFunctionOv protected override string Identifier => nameof(IPythonFunctionOverload); public AndWhichConstraint HaveReturnType(string because = "", params object[] reasonArgs) { - var returnType = Subject.GetReturnValue(LocationInfo.Empty, ArgumentSet.Empty); + var returnType = Subject.Call(ArgumentSet.Empty, null); Execute.Assertion.ForCondition(returnType != null) .BecauseOf(because, reasonArgs) .FailWith($"Expected {Subject.Name} overload to have a return type{{reason}}, but it has none."); @@ -40,7 +40,7 @@ public AndWhichConstraint HaveRet } public AndWhichConstraint HaveReturnType(BuiltinTypeId typeid, string because = "", params object[] reasonArgs) { - Subject.GetReturnValue(LocationInfo.Empty, ArgumentSet.Empty).GetPythonType().TypeId.Should().Be(typeid); + Subject.Call(ArgumentSet.Empty, null).GetPythonType().TypeId.Should().Be(typeid); return new AndWhichConstraint(this, Subject); } @@ -53,11 +53,12 @@ public AndWhichConstraint HaveDocument } public AndWhichConstraint HaveReturnDocumentation(string documentation, string because = "", params object[] reasonArgs) { - Execute.Assertion.ForCondition(Subject.ReturnDocumentation == documentation) + var returnDoc = Subject.GetReturnDocumentation(null); + Execute.Assertion.ForCondition(Subject.GetReturnDocumentation(null) == documentation) .BecauseOf(because, reasonArgs) - .FailWith($"Expected {Subject.Name} overload to have a return documentation '{documentation}', but it has '{Subject.ReturnDocumentation}'."); + .FailWith($"Expected {Subject.Name} overload to have a return documentation '{documentation}', but it has '{returnDoc}'."); - return new AndWhichConstraint(this, Subject.ReturnDocumentation); + return new AndWhichConstraint(this, returnDoc); } public AndWhichConstraint HaveName(string name, string because = "", params object[] reasonArgs) { @@ -105,7 +106,7 @@ public AndConstraint HaveNoParameters(string b => HaveParameters(Enumerable.Empty(), because, reasonArgs); public AndConstraint HaveReturnType(string type, string because = "", params object[] reasonArgs) { - var returnType = Subject.GetReturnValue(LocationInfo.Empty, ArgumentSet.Empty).GetPythonType(); + var returnType = Subject.Call(ArgumentSet.Empty, null).GetPythonType(); Execute.Assertion.ForCondition(string.Equals(returnType.Name, type, StringComparison.Ordinal)) .BecauseOf(because, reasonArgs) .FailWith($"Expected {Subject.Name} to have return type [{type}]{{reason}}, but it has [{returnType}]."); diff --git a/src/Analysis/Ast/Test/LibraryTests.cs b/src/Analysis/Ast/Test/LibraryTests.cs index 06c99e205..286815501 100644 --- a/src/Analysis/Ast/Test/LibraryTests.cs +++ b/src/Analysis/Ast/Test/LibraryTests.cs @@ -88,7 +88,7 @@ with open('foo.txt', 'wb') as file: file "; var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X); - analysis.Should().HaveVariable("file").OfType("BufferedIOBase"); + analysis.Should().HaveVariable("file").OfType("IOBase"); } [TestMethod, Priority(0)] diff --git a/src/Analysis/Ast/Test/TypeshedTests.cs b/src/Analysis/Ast/Test/TypeshedTests.cs index 9a91f965a..a6594b115 100644 --- a/src/Analysis/Ast/Test/TypeshedTests.cs +++ b/src/Analysis/Ast/Test/TypeshedTests.cs @@ -54,7 +54,7 @@ import sys .Which.Should().HaveMember("exc_info").Which; f.Overloads.Should().HaveCount(1); - f.Overloads[0].ReturnDocumentation + f.Overloads[0].GetReturnDocumentation(null) .Should().Be(@"Tuple[ (Optional[Type[BaseException]],Optional[BaseException],Optional[TracebackType])]"); } diff --git a/src/Analysis/Ast/Test/TypingTests.cs b/src/Analysis/Ast/Test/TypingTests.cs index b405cb330..219e8faca 100644 --- a/src/Analysis/Ast/Test/TypingTests.cs +++ b/src/Analysis/Ast/Test/TypingTests.cs @@ -652,67 +652,59 @@ from typing import List [TestMethod, Priority(0)] - public async Task GenericClassBase1() { + public async Task GenericClassBaseForwardRef() { const string code = @" -from typing import TypeVar, Generic - -_E = TypeVar('_E', bound=Exception) +from typing import TypeVar, Generic, List -class A(Generic[_E]): ... +_E = TypeVar('_E') class B(Generic[_E]): a: A[_E] def func(self) -> A[_E]: ... -b = B[TypeError]() +class A(Generic[_E], List[_E]): ... + +b = B[str]() x = b.func() y = b.a "; var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X); analysis.Should().HaveVariable("b") - .Which.Should().HaveMembers("args", @"with_traceback"); + .Which.Should().HaveType("B[str]"); analysis.Should().HaveVariable("x") - .Which.Should().HaveType("A[TypeError]") - .Which.Should().HaveMembers("args", @"with_traceback"); + .Which.Should().HaveType("A[str]") + .Which.Should().HaveMembers("append", "index"); analysis.Should().HaveVariable("y") - .Which.Should().HaveType("A[TypeError]") - .Which.Should().HaveMembers("args", @"with_traceback"); + .Which.Should().HaveType("A[str]") + .Which.Should().HaveMembers("append", "index"); } [TestMethod, Priority(0)] - public async Task GenericClassBaseForwardRef() { + public async Task GenericClassInstantiation() { const string code = @" from typing import TypeVar, Generic -_E = TypeVar('_E', bound=Exception) +_T = TypeVar('_T') -class B(Generic[_E]): - a: A[_E] - def func(self) -> A[_E]: ... +class Box(Generic[_T]): + def __init__(self, v: _T): + self.v = v -class A(Generic[_E]): ... + def get(self) -> _T: + return self.v -b = B[TypeError]() -x = b.func() -y = b.a +boxed = Box[int]() +x = boxed.get() "; var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X); - analysis.Should().HaveVariable("b") - .Which.Should().HaveMembers("args", @"with_traceback"); - analysis.Should().HaveVariable("x") - .Which.Should().HaveType("A[TypeError]") - .Which.Should().HaveMembers("args", @"with_traceback"); - - analysis.Should().HaveVariable("y") - .Which.Should().HaveType("A[TypeError]") - .Which.Should().HaveMembers("args", @"with_traceback"); + .Which.Should().HaveType(BuiltinTypeId.Int); } [TestMethod, Priority(0)] - public async Task GenericClassBase2() { + public async Task GenericClassInstantiationByValue() { const string code = @" from typing import TypeVar, Generic @@ -725,7 +717,7 @@ def __init__(self, v: _T): def get(self) -> _T: return self.v -boxed = Box[int]() +boxed = Box(1234) x = boxed.get() "; var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X); @@ -734,13 +726,36 @@ def get(self) -> _T: } [TestMethod, Priority(0)] - public async Task GenericClassBase3() { + public async Task GenericClassRegularBase() { const string code = @" from typing import TypeVar, Generic _T = TypeVar('_T') -class Box(Generic[_T]): +class Box(Generic[_T], list): + def __init__(self, v: _T): + self.v = v + + def get(self) -> _T: + return self.v + +boxed = Box(1234) +x = boxed.get() +"; + var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X); + var boxed = analysis.Should().HaveVariable("boxed").Which; + boxed.Should().HaveMembers("append", "index"); + boxed.Should().NotHaveMember("bit_length"); + } + + [TestMethod, Priority(0)] + public async Task GenericClassGenericListBase() { + const string code = @" +from typing import TypeVar, Generic, List + +_T = TypeVar('_T') + +class Box(Generic[_T], List[_T]): def __init__(self, v: _T): self.v = v @@ -749,10 +764,72 @@ def get(self) -> _T: boxed = Box(1234) x = boxed.get() +y = boxed[0] +"; + var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X); + analysis.Should().HaveVariable("x").Which.Should().HaveType(BuiltinTypeId.Int); + + var boxed = analysis.Should().HaveVariable("boxed").Which; + boxed.Should().HaveMembers("append", "index"); + boxed.Should().NotHaveMember("bit_length"); + + analysis.Should().HaveVariable("y").Which.Should().HaveType(BuiltinTypeId.Int); + } + + [TestMethod, Priority(0)] + public async Task GenericClassMultipleArgumentsListBase() { + const string code = @" +from typing import TypeVar, Generic, List + +_T = TypeVar('_T') +_E = TypeVar('_E') + +class Box(Generic[_T, _E], List[_E]): + def __init__(self, v: _T): + self.v = v + + def get(self) -> _T: + return self.v + +boxed = Box(1234, 'abc') +x = boxed.get() +y = boxed[0] +"; + var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X); + analysis.Should().HaveVariable("x").Which.Should().HaveType(BuiltinTypeId.Int); + + var boxed = analysis.Should().HaveVariable("boxed").Which; + boxed.Should().HaveMembers("append", "index"); + boxed.Should().NotHaveMember("bit_length"); + + analysis.Should().HaveVariable("y").Which.Should().HaveType(BuiltinTypeId.Str); + } + + [TestMethod, Priority(0)] + public async Task GenericClassToDifferentTypes() { + const string code = @" +from typing import TypeVar, Generic + +_T = TypeVar('_T') + +class Box(Generic[_T]): + def __init__(self, v: _T): + self.v = v + + def get(self) -> _T: + return self.v + +boxedint = Box(1234) +x = boxedint.get() + +boxedstr = Box('str') +y = boxedstr.get() "; var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X); analysis.Should().HaveVariable("x") .Which.Should().HaveType(BuiltinTypeId.Int); + analysis.Should().HaveVariable("y") + .Which.Should().HaveType(BuiltinTypeId.Str); } [TestMethod, Priority(0)] diff --git a/src/LanguageServer/Impl/Completion/CompletionItemSource.cs b/src/LanguageServer/Impl/Completion/CompletionItemSource.cs index f21874b95..390a27260 100644 --- a/src/LanguageServer/Impl/Completion/CompletionItemSource.cs +++ b/src/LanguageServer/Impl/Completion/CompletionItemSource.cs @@ -35,10 +35,10 @@ public CompletionItemSource(IDocumentationSource docSource, ServerSettings.Pytho _options = options; } - public CompletionItem CreateCompletionItem(string text, IMember member, string label = null) - => CreateCompletionItem(text, ToCompletionItemKind(member?.MemberType ?? PythonMemberType.Class), member, label); + public CompletionItem CreateCompletionItem(string text, IMember member, IPythonType self = null, string label = null) + => CreateCompletionItem(text, ToCompletionItemKind(member?.MemberType ?? PythonMemberType.Class), member, self, label); - public CompletionItem CreateCompletionItem(string text, CompletionItemKind kind, IMember member, string label = null) { + public CompletionItem CreateCompletionItem(string text, CompletionItemKind kind, IMember member, IPythonType self = null, string label = null) { var t = member?.GetPythonType(); var docFormat = _docSource.DocumentationFormat; @@ -54,7 +54,7 @@ public CompletionItem CreateCompletionItem(string text, CompletionItemKind kind, // Place regular items first, advanced entries last sortText = char.IsLetter(text, 0) ? "1" : "2", kind = kind, - documentation = !t.IsUnknown() ? _docSource.GetHover(label ?? text, member) : null + documentation = !t.IsUnknown() ? _docSource.GetHover(label ?? text, member, self) : null }; } diff --git a/src/LanguageServer/Impl/Completion/ExpressionCompletion.cs b/src/LanguageServer/Impl/Completion/ExpressionCompletion.cs index 27ed98e52..ad8d88e58 100644 --- a/src/LanguageServer/Impl/Completion/ExpressionCompletion.cs +++ b/src/LanguageServer/Impl/Completion/ExpressionCompletion.cs @@ -18,7 +18,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Python.Analysis; +using Microsoft.Python.Analysis.Types; using Microsoft.Python.Analysis.Values; +using Microsoft.Python.Core.Text; using Microsoft.Python.LanguageServer.Protocol; using Microsoft.Python.Parsing.Ast; @@ -47,7 +49,7 @@ private static async Task> GetItemsFromExpressionAsy if(m is IVariable v && v.Source != VariableSource.Declaration) { continue; } - items.Add(context.ItemSource.CreateCompletionItem(t, m)); + items.Add(context.ItemSource.CreateCompletionItem(t, m, type)); } return items; } diff --git a/src/LanguageServer/Impl/Definitions/IDocumentationSource.cs b/src/LanguageServer/Impl/Definitions/IDocumentationSource.cs index 1dce4db25..5552827ee 100644 --- a/src/LanguageServer/Impl/Definitions/IDocumentationSource.cs +++ b/src/LanguageServer/Impl/Definitions/IDocumentationSource.cs @@ -19,8 +19,8 @@ namespace Microsoft.Python.LanguageServer { public interface IDocumentationSource { InsertTextFormat DocumentationFormat { get; } - MarkupContent GetHover(string name, IMember member); - string GetSignatureString(IPythonFunctionType ft, int overloadIndex = 0); + MarkupContent GetHover(string name, IMember member, IPythonType self = null); + string GetSignatureString(IPythonFunctionType ft, IPythonType self, int overloadIndex = 0); MarkupContent FormatParameterDocumentation(IParameterInfo parameter); MarkupContent FormatDocumentation(string documentation); } diff --git a/src/LanguageServer/Impl/Sources/DefinitionSource.cs b/src/LanguageServer/Impl/Sources/DefinitionSource.cs index 24815fec9..697b67ead 100644 --- a/src/LanguageServer/Impl/Sources/DefinitionSource.cs +++ b/src/LanguageServer/Impl/Sources/DefinitionSource.cs @@ -13,7 +13,6 @@ // See the Apache Version 2.0 License for specific language governing // permissions and limitations under the License. -using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; using Microsoft.Python.Analysis; @@ -23,7 +22,6 @@ using Microsoft.Python.Analysis.Modules; using Microsoft.Python.Analysis.Types; using Microsoft.Python.Analysis.Values; -using Microsoft.Python.Core; using Microsoft.Python.Core.Text; using Microsoft.Python.LanguageServer.Completion; using Microsoft.Python.LanguageServer.Protocol; diff --git a/src/LanguageServer/Impl/Sources/HoverSource.cs b/src/LanguageServer/Impl/Sources/HoverSource.cs index 57c3603ce..89ef8e57f 100644 --- a/src/LanguageServer/Impl/Sources/HoverSource.cs +++ b/src/LanguageServer/Impl/Sources/HoverSource.cs @@ -48,7 +48,7 @@ public async Task GetHoverAsync(IDocumentAnalysis analysis, SourceLocatio var range = new Range { start = expr.GetStart(analysis.Ast), - end = expr.GetEnd(analysis.Ast), + end = expr.GetEnd(analysis.Ast) }; var eval = analysis.ExpressionEvaluator; @@ -100,8 +100,24 @@ public async Task GetHoverAsync(IDocumentAnalysis analysis, SourceLocatio } } + IPythonType self = null; + string name = null; + // If expression is A.B, trim applicable span to 'B'. + if (expr is MemberExpression mex) { + name = mex.Name; + range = new Range { + start = mex.Target.GetEnd(analysis.Ast), + end = range.end + }; + + // In case of a member expression get the target since if we end up with method + // of a generic class, the function will need specific type to determine its return + // value correctly. I.e. in x.func() we need to determine type of x (self for func). + var v = await analysis.ExpressionEvaluator.GetValueFromExpressionAsync(mex.Target, cancellationToken); + self = v?.GetPythonType(); + } + // Figure out name, if any - var name = (expr as MemberExpression)?.Name; name = name ?? (node as NameExpression)?.Name; // Special case hovering over self or cls @@ -116,7 +132,7 @@ public async Task GetHoverAsync(IDocumentAnalysis analysis, SourceLocatio name = name == null && statement is FunctionDefinition fd ? fd.Name : name; return new Hover { - contents = _docSource.GetHover(name, value), + contents = _docSource.GetHover(name, value, self), range = range }; } diff --git a/src/LanguageServer/Impl/Sources/MarkdownDocumentationSource.cs b/src/LanguageServer/Impl/Sources/MarkdownDocumentationSource.cs index 05b9c6d3c..77fc3ee2a 100644 --- a/src/LanguageServer/Impl/Sources/MarkdownDocumentationSource.cs +++ b/src/LanguageServer/Impl/Sources/MarkdownDocumentationSource.cs @@ -27,7 +27,7 @@ namespace Microsoft.Python.LanguageServer.Sources { internal sealed class MarkdownDocumentationSource : IDocumentationSource { public InsertTextFormat DocumentationFormat => InsertTextFormat.PlainText; - public MarkupContent GetHover(string name, IMember member) { + public MarkupContent GetHover(string name, IMember member, IPythonType self) { // We need to tell between instance and type. var type = member.GetPythonType(); if (type.IsUnknown()) { @@ -49,7 +49,7 @@ public MarkupContent GetHover(string name, IMember member) { break; case IPythonFunctionType ft: - text = GetFunctionHoverString(ft); + text = GetFunctionHoverString(ft, self); break; case IPythonClassType cls: @@ -75,12 +75,13 @@ public MarkupContent FormatDocumentation(string documentation) { return new MarkupContent { kind = MarkupKind.Markdown, value = DocstringConverter.ToMarkdown(documentation) }; } - public string GetSignatureString(IPythonFunctionType ft, int overloadIndex = 0) { + public string GetSignatureString(IPythonFunctionType ft, IPythonType self, int overloadIndex = 0) { var o = ft.Overloads[overloadIndex]; var parms = GetFunctionParameters(ft); var parmString = string.Join(", ", parms); - var annString = string.IsNullOrEmpty(o.ReturnDocumentation) ? string.Empty : $" -> {o.ReturnDocumentation}"; + var returnDoc = o.GetReturnDocumentation(self); + var annString = string.IsNullOrEmpty(returnDoc) ? string.Empty : $" -> {returnDoc}"; return $"{ft.Name}({parmString}){annString}"; } @@ -100,8 +101,8 @@ private string GetPropertyHoverString(IPythonPropertyType prop, int overloadInde return $"```\n{decTypeString}\n```{propDoc}"; } - private string GetFunctionHoverString(IPythonFunctionType ft, int overloadIndex = 0) { - var sigString = GetSignatureString(ft, overloadIndex); + private string GetFunctionHoverString(IPythonFunctionType ft, IPythonType self, int overloadIndex = 0) { + var sigString = GetSignatureString(ft, self, overloadIndex); var decTypeString = ft.DeclaringType != null ? $"{ft.DeclaringType.Name}." : string.Empty; var funcDoc = !string.IsNullOrEmpty(ft.Documentation) ? $"\n---\n{ft.MarkdownDoc()}" : string.Empty; return $"```\n{decTypeString}{sigString}\n```{funcDoc}"; diff --git a/src/LanguageServer/Impl/Sources/PlainTextDocumentationSource.cs b/src/LanguageServer/Impl/Sources/PlainTextDocumentationSource.cs index 273afad7f..46213d055 100644 --- a/src/LanguageServer/Impl/Sources/PlainTextDocumentationSource.cs +++ b/src/LanguageServer/Impl/Sources/PlainTextDocumentationSource.cs @@ -25,7 +25,7 @@ namespace Microsoft.Python.LanguageServer.Sources { internal sealed class PlainTextDocumentationSource : IDocumentationSource { public InsertTextFormat DocumentationFormat => InsertTextFormat.PlainText; - public MarkupContent GetHover(string name, IMember member) { + public MarkupContent GetHover(string name, IMember member, IPythonType self) { // We need to tell between instance and type. var type = member.GetPythonType(); if (type.IsUnknown()) { @@ -47,7 +47,7 @@ public MarkupContent GetHover(string name, IMember member) { break; case IPythonFunctionType ft: - text = GetFunctionHoverString(ft); + text = GetFunctionHoverString(ft, self); break; case IPythonClassType cls: @@ -73,12 +73,13 @@ public MarkupContent FormatDocumentation(string documentation) { return new MarkupContent { kind = MarkupKind.PlainText, value = documentation }; } - public string GetSignatureString(IPythonFunctionType ft, int overloadIndex = 0) { + public string GetSignatureString(IPythonFunctionType ft, IPythonType self, int overloadIndex = 0) { var o = ft.Overloads[overloadIndex]; var parms = GetFunctionParameters(ft); var parmString = string.Join(", ", parms); - var annString = string.IsNullOrEmpty(o.ReturnDocumentation) ? string.Empty : $" -> {o.ReturnDocumentation}"; + var returnDoc = o.GetReturnDocumentation(self); + var annString = string.IsNullOrEmpty(returnDoc) ? string.Empty : $" -> {returnDoc}"; return $"{ft.Name}({parmString}){annString}"; } @@ -98,8 +99,8 @@ private string GetPropertyHoverString(IPythonPropertyType prop, int overloadInde return $"{decTypeString}{propDoc}"; } - private string GetFunctionHoverString(IPythonFunctionType ft, int overloadIndex = 0) { - var sigString = GetSignatureString(ft, overloadIndex); + private string GetFunctionHoverString(IPythonFunctionType ft, IPythonType self, int overloadIndex = 0) { + var sigString = GetSignatureString(ft, self, overloadIndex); var decTypeString = ft.DeclaringType != null ? $"{ft.DeclaringType.Name}." : string.Empty; var funcDoc = !string.IsNullOrEmpty(ft.Documentation) ? $"\n\n{ft.PlaintextDoc()}" : string.Empty; return $"{decTypeString}{sigString}{funcDoc}"; diff --git a/src/LanguageServer/Impl/Sources/SignatureSource.cs b/src/LanguageServer/Impl/Sources/SignatureSource.cs index c08d9613b..b3f8afb73 100644 --- a/src/LanguageServer/Impl/Sources/SignatureSource.cs +++ b/src/LanguageServer/Impl/Sources/SignatureSource.cs @@ -43,9 +43,14 @@ public async Task GetSignatureAsync(IDocumentAnalysis analysis, S FindExpressionOptions.Hover, out var node, out var statement, out var scope); IMember value = null; + IPythonType selfType = null; var call = node as CallExpression; if (call != null) { using (analysis.ExpressionEvaluator.OpenScope(analysis.Document, scope)) { + if (call.Target is MemberExpression mex) { + var v = await analysis.ExpressionEvaluator.GetValueFromExpressionAsync(mex.Target, cancellationToken); + selfType = v?.GetPythonType(); + } value = await analysis.ExpressionEvaluator.GetValueFromExpressionAsync(call.Target, cancellationToken); } } @@ -67,7 +72,7 @@ public async Task GetSignatureAsync(IDocumentAnalysis analysis, S }).ToArray(); signatures[i] = new SignatureInformation { - label = _docSource.GetSignatureString(ft, i), + label = _docSource.GetSignatureString(ft, selfType, i), documentation = _docSource.FormatDocumentation(ft.Documentation), parameters = parameters }; diff --git a/src/LanguageServer/Test/CompletionTests.cs b/src/LanguageServer/Test/CompletionTests.cs index be36b1274..bb587cfb7 100644 --- a/src/LanguageServer/Test/CompletionTests.cs +++ b/src/LanguageServer/Test/CompletionTests.cs @@ -56,7 +56,7 @@ def method(self): var analysis = await GetAnalysisAsync(code); var cs = new CompletionSource(new PlainTextDocumentationSource(), ServerSettings.completion); var comps = await cs.GetCompletionsAsync(analysis, new SourceLocation(8, 1)); - comps.Should().HaveLabels("C", "x", "y", "while", "for", "yield"); + comps.Should().HaveLabels("C", "x", "y", "while", "for"); } [TestMethod, Priority(0)] @@ -429,6 +429,70 @@ def func(a: Dict[int, str]): result.Should().HaveLabels("capitalize"); } + [TestMethod, Priority(0)] + public async Task GenericClassMethod() { + const string code = @" +from typing import TypeVar, Generic + +_T = TypeVar('_T') + +class Box(Generic[_T]): + def __init__(self, v: _T): + self.v = v + + def get(self) -> _T: + return self.v + +boxedint = Box(1234) +x = boxedint. + +boxedstr = Box('str') +y = boxedstr. +"; + var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X); + var cs = new CompletionSource(new PlainTextDocumentationSource(), ServerSettings.completion); + + var result = await cs.GetCompletionsAsync(analysis, new SourceLocation(14, 14)); + result.Should().HaveItem("get").Which.Should().HaveDocumentation("Box.get() -> int"); + result.Should().NotContainLabels("bit_length"); + + result = await cs.GetCompletionsAsync(analysis, new SourceLocation(17, 14)); + result.Should().HaveItem("get").Which.Should().HaveDocumentation("Box.get() -> str"); + result.Should().NotContainLabels("capitalize"); + } + + [TestMethod, Priority(0)] + public async Task GenericAndRegularBases() { + const string code = @" +from typing import TypeVar, Generic + +_T = TypeVar('_T') + +class Box(Generic[_T], list): + def __init__(self, v: _T): + self.v = v + + def get(self) -> _T: + return self.v + +boxedint = Box(1234) +x = boxedint. + +boxedstr = Box('str') +y = boxedstr. +"; + var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X); + var cs = new CompletionSource(new PlainTextDocumentationSource(), ServerSettings.completion); + + var result = await cs.GetCompletionsAsync(analysis, new SourceLocation(14, 14)); + result.Should().HaveLabels("append", "index"); + result.Should().NotContainLabels("bit_length"); + + result = await cs.GetCompletionsAsync(analysis, new SourceLocation(17, 14)); + result.Should().HaveLabels("append", "index"); + result.Should().NotContainLabels("capitalize"); + } + [TestMethod, Priority(0)] public async Task ForwardRef() { const string code = @" diff --git a/src/LanguageServer/Test/HoverTests.cs b/src/LanguageServer/Test/HoverTests.cs index caf48b76f..ee3798793 100644 --- a/src/LanguageServer/Test/HoverTests.cs +++ b/src/LanguageServer/Test/HoverTests.cs @@ -92,8 +92,8 @@ import datetime var hs = new HoverSource(new PlainTextDocumentationSource()); await AssertHover(hs, analysis, new SourceLocation(3, 2), "module datetime*", new SourceSpan(3, 1, 3, 9)); - await AssertHover(hs, analysis, new SourceLocation(3, 11), "class datetime*", new SourceSpan(3, 1, 3, 18)); - await AssertHover(hs, analysis, new SourceLocation(3, 20), "datetime.now(tz: Optional[tzinfo]) -> datetime*", new SourceSpan(3, 1, 3, 22)); + await AssertHover(hs, analysis, new SourceLocation(3, 11), "class datetime*", new SourceSpan(3, 9, 3, 18)); + await AssertHover(hs, analysis, new SourceLocation(3, 20), "datetime.now(tz: Optional[tzinfo]) -> datetime*", new SourceSpan(3, 18, 3, 22)); } [TestMethod, Priority(0)] @@ -139,6 +139,32 @@ def fob_derived(self): await AssertHover(hs, analysis, new SourceLocation(8, 8), "class Derived*", new SourceSpan(8, 8, 8, 12)); } + [TestMethod, Priority(0)] + public async Task HoverGenericClass() { + const string code = @" +from typing import TypeVar, Generic + +_T = TypeVar('_T') + +class Box(Generic[_T]): + def __init__(self, v: _T): + self.v = v + + def get(self) -> _T: + return self.v + +boxedint = Box(1234) +x = boxedint.get() + +boxedstr = Box('str') +y = boxedstr.get() +"; + var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X); + var hs = new HoverSource(new PlainTextDocumentationSource()); + await AssertHover(hs, analysis, new SourceLocation(14, 15), "Box.get() -> int", new SourceSpan(14, 13, 14, 17)); + await AssertHover(hs, analysis, new SourceLocation(17, 15), "Box.get() -> str", new SourceSpan(17, 13, 17, 17)); + } + private static async Task AssertHover(HoverSource hs, IDocumentAnalysis analysis, SourceLocation position, string hoverText, SourceSpan? span = null) { var hover = await hs.GetHoverAsync(analysis, position); diff --git a/src/LanguageServer/Test/SignatureTests.cs b/src/LanguageServer/Test/SignatureTests.cs new file mode 100644 index 000000000..c3db48ece --- /dev/null +++ b/src/LanguageServer/Test/SignatureTests.cs @@ -0,0 +1,88 @@ +// 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.Threading.Tasks; +using FluentAssertions; +using Microsoft.Python.Analysis; +using Microsoft.Python.Core.Text; +using Microsoft.Python.LanguageServer.Sources; +using Microsoft.Python.Parsing.Tests; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestUtilities; + +namespace Microsoft.Python.LanguageServer.Tests { + [TestClass] + public class SignatureTests : LanguageServerTestBase { + 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 MethodSignature() { + const string code = @" +class C: + def method(self, a:int, b) -> float: + return 1.0 + +C().method() +"; + var analysis = await GetAnalysisAsync(code); + var src = new SignatureSource(new PlainTextDocumentationSource()); + + var sig = await src.GetSignatureAsync(analysis, new SourceLocation(6, 12)); + sig.activeSignature.Should().Be(0); + sig.activeParameter.Should().Be(0); + sig.signatures.Length.Should().Be(1); + sig.signatures[0].label.Should().Be("method(a: int, b) -> float"); + } + + [TestMethod, Priority(0)] + public async Task GenericClassMethod() { + const string code = @" +from typing import TypeVar, Generic + +_T = TypeVar('_T') + +class Box(Generic[_T]): + def get(self) -> _T: + return self.v + +boxedint = Box(1234) +x = boxedint.get() + +boxedstr = Box('str') +y = boxedstr.get() +"; + var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X); + var src = new SignatureSource(new PlainTextDocumentationSource()); + + var sig = await src.GetSignatureAsync(analysis, new SourceLocation(11, 18)); + sig.signatures.Should().NotBeNull(); + sig.signatures.Length.Should().Be(1); + sig.signatures[0].label.Should().Be("get() -> int"); + + sig = await src.GetSignatureAsync(analysis, new SourceLocation(14, 18)); + sig.signatures.Should().NotBeNull(); + sig.signatures.Length.Should().Be(1); + sig.signatures[0].label.Should().Be("get() -> str"); + + } + } +}