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

Commit 6cc5470

Browse files
author
Mikhail Arkhipov
authored
Handle generic standalone functions (#1164)
* Initial * Cleanup * Usings
1 parent 12d7f56 commit 6cc5470

File tree

11 files changed

+127
-98
lines changed

11 files changed

+127
-98
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ private static IReadOnlyList<IPythonType> GetSpecificTypeFromArgumentValue(objec
196196
specificTypes.Add(itemType);
197197
}
198198
break;
199+
case IPythonInstance inst:
200+
specificTypes.Add(inst.GetPythonType());
201+
break;
199202
case IMember m:
200203
if (!m.IsUnknown()) {
201204
specificTypes.Add(m.GetPythonType());

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
using Microsoft.Python.Analysis.Values;
2222
using Microsoft.Python.Core;
2323
using Microsoft.Python.Core.Disposables;
24-
using Microsoft.Python.Core.Text;
2524
using Microsoft.Python.Parsing.Ast;
2625

2726
namespace Microsoft.Python.Analysis.Analyzer.Evaluation {
@@ -117,11 +116,7 @@ public IMember LookupNameInScopes(string name, out IScope scope, out IVariable v
117116
return value;
118117
}
119118

120-
public IPythonType GetTypeFromAnnotation(Expression expr, LookupOptions options = LookupOptions.Global | LookupOptions.Builtins)
121-
=> GetTypeFromAnnotation(expr, out _, options);
122-
123-
public IPythonType GetTypeFromAnnotation(Expression expr, out bool isGeneric, LookupOptions options = LookupOptions.Global | LookupOptions.Builtins) {
124-
isGeneric = false;
119+
public IPythonType GetTypeFromAnnotation(Expression expr, LookupOptions options = LookupOptions.Global | LookupOptions.Builtins) {
125120
switch (expr) {
126121
case null:
127122
return null;
@@ -133,7 +128,6 @@ public IPythonType GetTypeFromAnnotation(Expression expr, out bool isGeneric, Lo
133128
var target = GetValueFromExpression(indexExpr.Target);
134129
var result = GetValueFromGeneric(target, indexExpr);
135130
if (result != null) {
136-
isGeneric = true;
137131
return result.GetPythonType();
138132
}
139133
break;

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ public override void Evaluate() {
5656
if (!annotationType.IsUnknown()) {
5757
// Annotations are typically types while actually functions return
5858
// instances unless specifically annotated to a type such as Type[T].
59-
var instance = annotationType.CreateInstance(annotationType.Name, ArgumentSet.Empty);
59+
var t = annotationType.CreateInstance(annotationType.Name, ArgumentSet.Empty);
60+
// If instance could not be create, such as when return type is List[T] and
61+
// type of T is not yet known, just use the type.
62+
var instance = t.IsUnknown() ? annotationType : t;
6063
_overload.SetReturnValue(instance, true);
6164
} else {
6265
// Check if function is a generator
@@ -152,24 +155,23 @@ private void DeclareParameters(bool declareVariables) {
152155
// Declare parameters in scope
153156
IMember defaultValue = null;
154157
for (var i = skip; i < FunctionDefinition.Parameters.Length; i++) {
155-
var isGeneric = false;
156158
var p = FunctionDefinition.Parameters[i];
157159
if (!string.IsNullOrEmpty(p.Name)) {
158160
IPythonType paramType = null;
159161
if (p.DefaultValue != null) {
160162
defaultValue = Eval.GetValueFromExpression(p.DefaultValue);
161163
// If parameter has default value, look for the annotation locally first
162164
// since outer type may be getting redefined. Consider 's = None; def f(s: s = 123): ...
163-
paramType = Eval.GetTypeFromAnnotation(p.Annotation, out isGeneric, LookupOptions.Local | LookupOptions.Builtins);
165+
paramType = Eval.GetTypeFromAnnotation(p.Annotation, LookupOptions.Local | LookupOptions.Builtins);
164166
// Default value of None does not mean the parameter is None, just says it can be missing.
165167
defaultValue = defaultValue.IsUnknown() || defaultValue.IsOfType(BuiltinTypeId.NoneType) ? null : defaultValue;
166168
if (paramType == null && defaultValue != null) {
167169
paramType = defaultValue.GetPythonType();
168170
}
169171
}
170172
// If all else fails, look up globally.
171-
paramType = paramType ?? Eval.GetTypeFromAnnotation(p.Annotation, out isGeneric) ?? Eval.UnknownType;
172-
var pi = new ParameterInfo(Ast, p, paramType, defaultValue, isGeneric);
173+
paramType = paramType ?? Eval.GetTypeFromAnnotation(p.Annotation) ?? Eval.UnknownType;
174+
var pi = new ParameterInfo(Ast, p, paramType, defaultValue, paramType.IsGeneric());
173175
if (declareVariables) {
174176
DeclareParameter(p, pi);
175177
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,10 @@ public interface IPythonPropertyType : IPythonClassMember {
3434
/// True if the property is read-only.
3535
/// </summary>
3636
bool IsReadOnly { get; }
37+
38+
/// <summary>
39+
/// Property return type.
40+
/// </summary>
41+
IPythonType Type { get; }
3742
}
3843
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
// permissions and limitations under the License.
1515

1616
using System;
17-
using Microsoft.Python.Core.Text;
1817
using Microsoft.Python.Parsing.Ast;
1918

2019
namespace Microsoft.Python.Analysis.Types {
@@ -26,7 +25,7 @@ public ParameterInfo(PythonAst ast, Parameter p, IPythonType type, IMember defau
2625
if (DefaultValueString == "...") {
2726
DefaultValueString = null;
2827
}
29-
IsGeneric = isGeneric;
28+
IsGeneric = isGeneric || type.IsGeneric();
3029
}
3130

3231
public ParameterInfo(string name, IPythonType type, ParameterKind? kind, IMember defaultValue) {
@@ -36,6 +35,7 @@ public ParameterInfo(string name, IPythonType type, ParameterKind? kind, IMember
3635
Type = type;
3736
IsParamArray = kind == ParameterKind.List;
3837
IsKeywordDict = kind == ParameterKind.Dictionary;
38+
IsGeneric = Type.IsGeneric();
3939
}
4040

4141
public string Name { get; }

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

Lines changed: 72 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ public PythonFunctionOverload(string name, Location location) : base(location) {
6363
}
6464

6565
#region ILocatedMember
66+
6667
public override PythonMemberType MemberType => PythonMemberType.Function;
68+
6769
#endregion
6870

6971
internal void SetParameters(IReadOnlyList<IParameterInfo> parameters) => Parameters = parameters;
@@ -75,10 +77,12 @@ internal void AddReturnValue(IMember value) {
7577
if (value.IsUnknown()) {
7678
return; // Don't add useless values.
7779
}
80+
7881
if (StaticReturnValue.IsUnknown()) {
7982
SetReturnValue(value, false);
8083
return;
8184
}
85+
8286
// If return value is set from annotation, it should not be changing.
8387
var currentType = StaticReturnValue.GetPythonType();
8488
var valueType = value.GetPythonType();
@@ -98,6 +102,7 @@ internal void SetReturnValueProvider(ReturnValueProvider provider)
98102
=> _returnValueProvider = provider;
99103

100104
#region IPythonFunctionOverload
105+
101106
public FunctionDefinition FunctionDefinition { get; }
102107
public IPythonClassMember ClassMember { get; }
103108
public string Name { get; }
@@ -108,44 +113,19 @@ public string Documentation {
108113
if (string.IsNullOrEmpty(s)) {
109114
s = FunctionDefinition.GetDocumentation();
110115
}
116+
111117
return s ?? string.Empty;
112118
}
113119
}
114120

115121
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-
}
122+
if (self != null) {
123+
var returnType = GetSpecificReturnType(self as IPythonClassType, null);
124+
if (!returnType.IsUnknown()) {
125+
return returnType.GetPythonType().Name;
126+
}
148127
}
128+
149129
return _returnDocumentation;
150130
}
151131

@@ -161,59 +141,71 @@ public IMember Call(IArgumentSet args, IPythonType self, Node callLocation = nul
161141
}
162142
}
163143

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-
}
144+
return GetSpecificReturnType(self as IPythonClassType, args);
145+
}
146+
147+
#endregion
172148

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ public string Description
4545
=> Type == null ? Resources.PropertyOfUnknownType : Resources.PropertyOfType.FormatUI(Type.Name);
4646
public override IMember Call(IPythonInstance instance, string memberName, IArgumentSet args)
4747
=> _getter.Call(args, instance?.GetPythonType() ?? DeclaringType);
48+
public IPythonType Type => _getter?.Call(ArgumentSet.Empty, DeclaringType)?.GetPythonType();
4849
#endregion
4950

5051
internal void AddOverload(IPythonFunctionOverload overload) => _getter = _getter ?? overload;
51-
private IPythonType Type => _getter?.Call(ArgumentSet.Empty, DeclaringType)?.GetPythonType();
5252
}
5353
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
using System;
1717
using System.Collections.Generic;
1818
using Microsoft.Python.Analysis.Values;
19-
using Microsoft.Python.Core.Text;
2019

2120
namespace Microsoft.Python.Analysis.Types {
2221
/// <summary>

src/Analysis/Ast/Impl/Values/Definitions/IPythonCollection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ namespace Microsoft.Python.Analysis.Values {
2020
/// <summary>
2121
/// Represents an instance of a sequence.
2222
/// </summary>
23-
public interface IPythonCollection : IPythonInstance {
23+
public interface IPythonCollection : IPythonInstance, IPythonIterable {
2424
/// <summary>
2525
/// Collection contents
2626
/// </summary>

src/Analysis/Ast/Impl/Values/Definitions/IPythonInstance.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace Microsoft.Python.Analysis.Values {
1919
/// <summary>
2020
/// Represents instance of a type.
2121
/// </summary>
22-
public interface IPythonInstance : IMember, IPythonIterable {
22+
public interface IPythonInstance : IMember {
2323
/// <summary>
2424
/// Type of the object the instance represents.
2525
/// </summary>

0 commit comments

Comments
 (0)