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

Commit c889543

Browse files
author
Mikhail Arkhipov
authored
Handle generic standalone functions (#1172)
* Handle generic standalone functions (#1164) * Initial * Cleanup * Usings * Fix return type * Restore lost code * Merge mess
1 parent f7a1f36 commit c889543

File tree

4 files changed

+82
-87
lines changed

4 files changed

+82
-87
lines changed

src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Generics.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ private static IReadOnlyList<IPythonType> GetSpecificTypeFromArgumentValue(objec
194194
var itemType = iter.GetIterator().Next.GetPythonType();
195195
if (!itemType.IsUnknown()) {
196196
specificTypes.Add(itemType);
197+
} else if(argumentValue is IPythonInstance inst) {
198+
specificTypes.Add(inst.GetPythonType());
197199
}
198200
break;
199201
case IMember m:

src/Analysis/Ast/Impl/Analyzer/ModuleWalker.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,12 @@ private void MergeStub() {
281281
}
282282
}
283283

284-
private static bool IsStubBetterType(IPythonType sourceType, IPythonType stubType)
284+
private static bool IsStubBetterType(IPythonType sourceType, IPythonType stubType) {
285285
// If stub says 'Any' but we have better type, keep the current type.
286-
=> sourceType.IsUnknown() || !(stubType.DeclaringModule is TypingModule) || stubType.Name != "Any";
286+
if (stubType.IsUnknown()) {
287+
return false;
288+
}
289+
return sourceType.IsUnknown() || !(stubType.DeclaringModule is TypingModule) || stubType.Name != "Any";
290+
}
287291
}
288292
}

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ private IPythonType TryDetermineReturnValue() {
117117
// If instance could not be created, such as when return type is List[T] and
118118
// type of T is not yet known, just use the type.
119119
var instance = t.IsUnknown() ? annotationType : t;
120-
_overload.SetReturnValue(instance, true); _overload.SetReturnValue(instance, true);
120+
_overload.SetReturnValue(instance, true);
121121
} else {
122122
// Check if function is a generator
123123
var suite = FunctionDefinition.Body as SuiteStatement;
@@ -158,10 +158,9 @@ private void DeclareParameters(bool declareVariables) {
158158
// Declare parameters in scope
159159
IMember defaultValue = null;
160160
for (var i = skip; i < FunctionDefinition.Parameters.Length; i++) {
161-
var isGeneric = false;
162161
var p = FunctionDefinition.Parameters[i];
163162
if (!string.IsNullOrEmpty(p.Name)) {
164-
var paramType = Eval.GetTypeFromAnnotation(p.Annotation);
163+
var paramType = Eval.GetTypeFromAnnotation(p.Annotation, out var isGeneric);
165164
if (paramType.IsUnknown() && p.DefaultValue != null) {
166165
defaultValue = Eval.GetValueFromExpression(p.DefaultValue);
167166
// If parameter has default value, look for the annotation locally first
@@ -175,7 +174,7 @@ private void DeclareParameters(bool declareVariables) {
175174
}
176175
// If all else fails, look up globally.
177176
paramType = paramType ?? Eval.GetTypeFromAnnotation(p.Annotation, out isGeneric) ?? Eval.UnknownType;
178-
var pi = new ParameterInfo(Ast, p, paramType, defaultValue, isGeneric);
177+
var pi = new ParameterInfo(Ast, p, paramType, defaultValue, isGeneric | paramType.IsGeneric());
179178
if (declareVariables) {
180179
DeclareParameter(p, pi);
181180
}

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

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

1616
using System;
1717
using System.Collections.Generic;
18+
using System.Diagnostics;
1819
using System.Linq;
1920
using Microsoft.Python.Analysis.Analyzer.Evaluation;
2021
using Microsoft.Python.Analysis.Documents;
@@ -75,10 +76,12 @@ internal void AddReturnValue(IMember value) {
7576
if (value.IsUnknown()) {
7677
return; // Don't add useless values.
7778
}
79+
7880
if (StaticReturnValue.IsUnknown()) {
7981
SetReturnValue(value, false);
8082
return;
8183
}
84+
8285
// If return value is set from annotation, it should not be changing.
8386
var currentType = StaticReturnValue.GetPythonType();
8487
var valueType = value.GetPythonType();
@@ -98,6 +101,7 @@ internal void SetReturnValueProvider(ReturnValueProvider provider)
98101
=> _returnValueProvider = provider;
99102

100103
#region IPythonFunctionOverload
104+
101105
public FunctionDefinition FunctionDefinition { get; }
102106
public IPythonClassMember ClassMember { get; }
103107
public string Name { get; }
@@ -113,38 +117,11 @@ public string Documentation {
113117
}
114118

115119
public string GetReturnDocumentation(IPythonType self = null) {
116-
if (self == null) {
117-
return _returnDocumentation;
118-
}
119-
var returnType = StaticReturnValue.GetPythonType();
120-
switch (returnType) {
121-
case PythonClassType cls when cls.IsGeneric(): {
122-
// -> A[_T1, _T2, ...]
123-
// Match arguments
124-
var typeArgs = cls.GenericParameters.Keys
125-
.Select(n => cls.GenericParameters.TryGetValue(n, out var t) ? t : null)
126-
.ExcludeDefault()
127-
.ToArray();
128-
var specificReturnValue = cls.CreateSpecificType(new ArgumentSet(typeArgs));
129-
return specificReturnValue.Name;
130-
}
131-
case IGenericTypeDefinition gtp1 when self is IPythonClassType cls: {
132-
// -> _T
133-
if (cls.GenericParameters.TryGetValue(gtp1.Name, out var specificType)) {
134-
return specificType.Name;
135-
}
136-
// Try returning the constraint
137-
// TODO: improve this, the heuristic is pretty basic and tailored to simple func(_T) -> _T
138-
var name = StaticReturnValue.GetPythonType()?.Name;
139-
var typeDefVar = DeclaringModule.Analysis.GlobalScope.Variables[name];
140-
if (typeDefVar?.Value is IGenericTypeDefinition gtp2) {
141-
var t = gtp2.Constraints.FirstOrDefault();
142-
if (t != null) {
143-
return t.Name;
144-
}
145-
}
146-
break;
147-
}
120+
if (self != null) {
121+
var returnType = GetSpecificReturnType(self as IPythonClassType, null);
122+
if (!returnType.IsUnknown()) {
123+
return returnType.GetPythonType().Name;
124+
}
148125
}
149126
return _returnDocumentation;
150127
}
@@ -161,59 +138,72 @@ public IMember Call(IArgumentSet args, IPythonType self, Node callLocation = nul
161138
}
162139
}
163140

164-
// If function returns generic, determine actual type based on the passed in specific type (self).
165-
// If there is no self and no declaring type, the function is standalone.
166-
if (self == null && StaticReturnValue.IsGeneric() && Parameters.Any(p => p.IsGeneric)) {
167-
return null; // Evaluate standalone generic with arguments instead.
168-
}
169-
if (!(self is IPythonClassType selfClassType)) {
170-
return StaticReturnValue;
171-
}
141+
return GetSpecificReturnType(self as IPythonClassType, args);
142+
}
143+
144+
#endregion
172145

173-
var returnType = StaticReturnValue.GetPythonType();
174-
switch (returnType) {
146+
private IMember GetSpecificReturnType(IPythonClassType selfClassType, IArgumentSet args) {
147+
var returnValueType = StaticReturnValue.GetPythonType();
148+
switch (returnValueType) {
175149
case PythonClassType cls when cls.IsGeneric():
176-
// -> A[_T1, _T2, ...]
177-
// Match arguments
178-
IReadOnlyList<IPythonType> typeArgs = null;
179-
var classGenericParameters = selfClassType.GenericParameters.Keys.ToArray();
180-
if (classGenericParameters.Length > 0) {
181-
// Declaring class is specific and provides definitions of generic parameters
182-
typeArgs = classGenericParameters
183-
.Select(n => selfClassType.GenericParameters.TryGetValue(n, out var t) ? t : null)
184-
.ExcludeDefault()
185-
.ToArray();
186-
} else {
187-
typeArgs = ExpressionEval.GetTypeArgumentsFromParameters(this, args);
188-
}
189-
190-
if (typeArgs != null) {
191-
var specificReturnValue = cls.CreateSpecificType(new ArgumentSet(typeArgs));
192-
return new PythonInstance(specificReturnValue);
193-
}
194-
break;
195-
196-
case IGenericTypeDefinition gtp1: {
197-
// -> _T
198-
if (selfClassType.GenericParameters.TryGetValue(gtp1.Name, out var specificType)) {
199-
return new PythonInstance(specificType);
200-
}
201-
// Try returning the constraint
202-
// TODO: improve this, the heuristic is pretty basic and tailored to simple func(_T) -> _T
203-
var name = StaticReturnValue.GetPythonType()?.Name;
204-
var typeDefVar = DeclaringModule.Analysis.GlobalScope.Variables[name];
205-
if (typeDefVar?.Value is IGenericTypeDefinition gtp2) {
206-
// See if the instance (self) type satisfies one of the constraints.
207-
return selfClassType.Mro.Any(b => gtp2.Constraints.Any(c => c.Equals(b)))
208-
? selfClassType
209-
: gtp2.Constraints.FirstOrDefault();
210-
}
211-
212-
break;
213-
}
150+
return CreateSpecificReturnFromClassType(selfClassType, cls, args); // -> A[_T1, _T2, ...]
151+
152+
case IGenericTypeDefinition gtd1 when selfClassType != null:
153+
return CreateSpecificReturnFromTypeVar(selfClassType, gtd1); // -> _T
154+
155+
case IGenericTypeDefinition gtd2 when args != null: // -> T on standalone function.
156+
return args.Arguments.FirstOrDefault(a => gtd2.Equals(a.Type))?.Value as IMember;
157+
158+
case IGenericType gt when args != null: // -> CLASS[T] on standalone function (i.e. -> List[T]).
159+
var typeArgs = ExpressionEval.GetTypeArgumentsFromParameters(this, args);
160+
Debug.Assert(typeArgs != null);
161+
return gt.CreateSpecificType(typeArgs);
214162
}
163+
215164
return StaticReturnValue;
216165
}
217-
#endregion
166+
167+
private IMember CreateSpecificReturnFromClassType(IPythonClassType selfClassType, PythonClassType returnClassType, IArgumentSet args) {
168+
// -> A[_T1, _T2, ...]
169+
// Match arguments
170+
IReadOnlyList<IPythonType> typeArgs = null;
171+
var classGenericParameters = selfClassType?.GenericParameters.Keys.ToArray() ?? Array.Empty<string>();
172+
if (classGenericParameters.Length > 0 && selfClassType != null) {
173+
// Declaring class is specific and provides definitions of generic parameters
174+
typeArgs = classGenericParameters
175+
.Select(n => selfClassType.GenericParameters.TryGetValue(n, out var t) ? t : null)
176+
.ExcludeDefault()
177+
.ToArray();
178+
} else if (args != null) {
179+
typeArgs = ExpressionEval.GetTypeArgumentsFromParameters(this, args);
180+
}
181+
182+
if (typeArgs != null) {
183+
var specificReturnValue = returnClassType.CreateSpecificType(new ArgumentSet(typeArgs));
184+
return new PythonInstance(specificReturnValue);
185+
}
186+
187+
return null;
188+
}
189+
190+
private IMember CreateSpecificReturnFromTypeVar(IPythonClassType selfClassType, IGenericTypeDefinition returnType) {
191+
if (selfClassType.GenericParameters.TryGetValue(returnType.Name, out var specificType)) {
192+
return new PythonInstance(specificType);
193+
}
194+
195+
// Try returning the constraint
196+
// TODO: improve this, the heuristic is pretty basic and tailored to simple func(_T) -> _T
197+
var name = StaticReturnValue.GetPythonType()?.Name;
198+
var typeDefVar = DeclaringModule.Analysis.GlobalScope.Variables[name];
199+
if (typeDefVar?.Value is IGenericTypeDefinition gtp2) {
200+
// See if the instance (self) type satisfies one of the constraints.
201+
return selfClassType.Mro.Any(b => gtp2.Constraints.Any(c => c.Equals(b)))
202+
? selfClassType
203+
: gtp2.Constraints.FirstOrDefault();
204+
}
205+
206+
return null;
207+
}
218208
}
219209
}

0 commit comments

Comments
 (0)