Skip to content
This repository was archived by the owner on Nov 4, 2024. It is now read-only.

Commit 8fde7d4

Browse files
author
Mikhail Arkhipov
authored
Improve handling of circular and imported class bases (microsoft#1497)
* Add test for GPy (fails) * Disambiguate bases * Handle base loops better * GPy test (on ignore) * PT feedback + test * PR feedback * Pass template class * Add declaration position extension * Pass template class * Change eval to scope * Add test for scoped import * Adjust comparison
1 parent 6bab760 commit 8fde7d4

16 files changed

+328
-86
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,23 @@ public void DeclareVariable(string name, IMember value, VariableSource source, N
4747
=> DeclareVariable(name, value, source, GetLocationOfName(location), overwrite);
4848

4949
public void DeclareVariable(string name, IMember value, VariableSource source, Location location, bool overwrite = true) {
50+
if (source == VariableSource.Import) {
51+
// Duplicate declaration so if variable gets overwritten it can still be retrieved. Consider:
52+
// from X import A
53+
// class A(A): ...
54+
CurrentScope.DeclareImported(name, value, location);
55+
}
56+
5057
var member = GetInScope(name);
5158
if (member != null && !overwrite) {
5259
return;
5360
}
61+
5462
if (source == VariableSource.Import && value is IVariable v) {
5563
CurrentScope.LinkVariable(name, v, location);
5664
return;
5765
}
66+
5867
if (member != null) {
5968
if (!value.IsUnknown()) {
6069
CurrentScope.DeclareVariable(name, value, source, location);

src/Analysis/Ast/Impl/Analyzer/Handlers/AssignmentHandler.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,7 @@ public void HandleAssignment(AssignmentStatement node) {
8080
TryHandleClassVariable(node, value);
8181
}
8282

83-
private bool IsValidAssignment(string name, Location loc) {
84-
if (Eval.GetInScope(name) is ILocatedMember m) {
85-
// Class and function definition are processed first, so only override
86-
// if assignment happens after declaration
87-
if (loc.IndexSpan.Start < m.Location.IndexSpan.Start) {
88-
return false;
89-
}
90-
}
91-
return true;
92-
}
83+
private bool IsValidAssignment(string name, Location loc) => !Eval.GetInScope(name).IsDeclaredAfter(loc);
9384

9485
public void HandleAnnotatedExpression(ExpressionWithAnnotation expr, IMember value) {
9586
if (expr?.Annotation == null) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,9 +361,9 @@ private void AnalyzeEntry(IDependencyChainNode<PythonAnalyzerEntry> node, Python
361361

362362
private void LogCompleted(IDependencyChainNode<PythonAnalyzerEntry> node, IPythonModule module, Stopwatch stopWatch, TimeSpan startTime) {
363363
if (_log != null) {
364-
var completed = node != null && module.Analysis is LibraryAnalysis ? "completed for library" : "completed ";
364+
var completed = node != null && module.Analysis is LibraryAnalysis ? "completed for library" : "completed";
365365
var message = node != null
366-
? $"Analysis of {module.Name}({module.ModuleType}) on depth {node.VertexDepth} {completed} in {(stopWatch.Elapsed - startTime).TotalMilliseconds} ms."
366+
? $"Analysis of {module.Name} ({module.ModuleType}) on depth {node.VertexDepth} {completed} in {(stopWatch.Elapsed - startTime).TotalMilliseconds} ms."
367367
: $"Out of order analysis of {module.Name}({module.ModuleType}) completed in {(stopWatch.Elapsed - startTime).TotalMilliseconds} ms.";
368368
_log.Log(TraceEventType.Verbose, message);
369369
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public void EvaluateClass() {
5353
_class = classInfo;
5454

5555
var bases = ProcessBases();
56-
_class.SetBases(bases);
56+
_class.SetBases(bases, Eval.CurrentScope);
5757
_class.DecideGeneric();
5858
// Declare __class__ variable in the scope.
5959
Eval.DeclareVariable("__class__", _class, VariableSource.Declaration);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public static IMember LookupNameInScopes(this IExpressionEvaluator eval, string
2424
=> eval.LookupNameInScopes(name, out scope, out _, options);
2525
public static IMember LookupNameInScopes(this IExpressionEvaluator eval, string name, LookupOptions options = LookupOptions.Normal)
2626
=> eval.LookupNameInScopes(name, out _, out _, options);
27+
2728
public static IDisposable OpenScope(this IExpressionEvaluator eval, IPythonClassType cls)
2829
=> eval.OpenScope(cls.DeclaringModule, cls.ClassDefinition);
2930
public static IDisposable OpenScope(this IExpressionEvaluator eval, IPythonFunctionType ft)

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

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

1616
using System.Diagnostics;
17-
using Microsoft.Python.Analysis.Specializations.Typing;
1817
using Microsoft.Python.Analysis.Types;
1918
using Microsoft.Python.Analysis.Values;
2019

@@ -106,5 +105,16 @@ public static ILocatedMember GetRootDefinition(this ILocatedMember lm) {
106105
return parent;
107106
}
108107

108+
public static bool IsDeclaredAfter(this IMember m, Location loc)
109+
=> m is ILocatedMember lm && lm.IsDeclaredAfter(loc);
110+
111+
public static bool IsDeclaredAfter(this ILocatedMember lm, ILocatedMember other)
112+
=> lm.IsDeclaredAfter(other.Location);
113+
public static bool IsDeclaredAfter(this ILocatedMember lm, Location loc)
114+
=> lm.Location.IndexSpan.Start > loc.IndexSpan.Start;
115+
public static bool IsDeclaredAfterOrAt(this ILocatedMember lm, ILocatedMember other)
116+
=> lm.IsDeclaredAfterOrAt(other.Location);
117+
public static bool IsDeclaredAfterOrAt(this ILocatedMember lm, Location loc)
118+
=> lm.Location.IndexSpan.Start >= loc.IndexSpan.Start;
109119
}
110120
}

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,35 @@
1515

1616
using System.Linq;
1717
using Microsoft.Python.Analysis.Documents;
18+
using Microsoft.Python.Analysis.Types;
1819
using Microsoft.Python.Analysis.Values;
1920
using Microsoft.Python.Core.Text;
2021
using Microsoft.Python.Parsing.Ast;
2122

2223
namespace Microsoft.Python.Analysis.Analyzer {
2324
public static class ScopeExtensions {
25+
public static IMember LookupNameInScopes(this IScope currentScope, string name, out IScope scope) {
26+
scope = null;
27+
foreach (var s in currentScope.EnumerateTowardsGlobal) {
28+
if (s.Variables.TryGetVariable(name, out var v) && v != null) {
29+
scope = s;
30+
return v.Value;
31+
}
32+
}
33+
return null;
34+
}
35+
36+
public static IMember LookupImportedNameInScopes(this IScope currentScope, string name, out IScope scope) {
37+
scope = null;
38+
foreach (var s in currentScope.EnumerateTowardsGlobal) {
39+
if (s.Imported.TryGetVariable(name, out var v) && v != null) {
40+
scope = s;
41+
return v.Value;
42+
}
43+
}
44+
return null;
45+
}
46+
2447
public static int GetBodyStartIndex(this IScope scope) {
2548
switch (scope.Node) {
2649
case ClassDefinition cd:
@@ -34,7 +57,7 @@ public static int GetBodyStartIndex(this IScope scope) {
3457
}
3558
}
3659

37-
public static bool IsNestedInScope(this IScope s, IScope outer)
60+
public static bool IsNestedInScope(this IScope s, IScope outer)
3861
=> s.OuterScope != null && s.OuterScope.EnumerateTowardsGlobal.Any(x => x == outer);
3962

4063
public static IScope FindScope(this IScope parent, IDocument document, SourceLocation location) {

src/Analysis/Ast/Impl/Types/PythonClassType.Generics.cs

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@
2323

2424
namespace Microsoft.Python.Analysis.Types {
2525
internal partial class PythonClassType {
26+
private readonly ReentrancyGuard<IPythonClassType> _genericSpecializationGuard = new ReentrancyGuard<IPythonClassType>();
27+
private readonly ReentrancyGuard<IPythonClassType> _genericResolutionGuard = new ReentrancyGuard<IPythonClassType>();
28+
private readonly object _genericParameterLock = new object();
29+
2630
private bool _isGeneric;
27-
private object _genericParameterLock = new object();
2831
private Dictionary<string, PythonClassType> _specificTypeCache;
2932
private Dictionary<IGenericTypeParameter, IPythonType> _genericParameters;
3033
private IReadOnlyList<IGenericTypeParameter> _parameters = new List<IGenericTypeParameter>();
31-
private ReentrancyGuard<IPythonClassType> _genericSpecializationGuard = new ReentrancyGuard<IPythonClassType>();
32-
private ReentrancyGuard<IPythonClassType> _genericResolutionGuard = new ReentrancyGuard<IPythonClassType>();
3334

3435
#region IGenericType
3536
/// <summary>
@@ -60,23 +61,19 @@ internal partial class PythonClassType {
6061
/// </summary>
6162
public IPythonType CreateSpecificType(IArgumentSet args) {
6263
lock (_genericParameterLock) {
63-
var genericTypeParameters = GetTypeParameters();
64+
var newGenericTypeParameters = GetTypeParameters();
6465
var newBases = new List<IPythonType>();
6566

6667
// Get map of generic type parameter to specific type - fill in what type goes to what
6768
// type parameter T -> int, U -> str, etc.
68-
var genericTypeToSpecificType = GetSpecificTypes(args, genericTypeParameters, newBases);
69+
var genericTypeToSpecificType = GetSpecificTypes(args, newGenericTypeParameters, newBases);
6970

70-
PythonClassType classType = new PythonClassType(BaseName, new Location(DeclaringModule));
71+
var classType = new PythonClassType(BaseName, new Location(DeclaringModule));
7172
// Storing generic parameters allows methods returning generic types
7273
// to know what type parameter returns what specific type
73-
StoreGenericParameters(classType, genericTypeParameters, genericTypeToSpecificType);
74+
classType.StoreGenericParameters(this, newGenericTypeParameters, genericTypeToSpecificType);
7475

7576
// Set generic name
76-
if (!classType._genericParameters.IsNullOrEmpty()) {
77-
classType._genericName = CodeFormatter.FormatSequence(BaseName, '[', classType._genericParameters.Values);
78-
}
79-
8077
// Locking so threads can only access class after it's been initialized
8178
// Store generic parameters first so name updates correctly, then check if class type has been cached
8279
_specificTypeCache = _specificTypeCache ?? new Dictionary<string, PythonClassType>();
@@ -105,8 +102,8 @@ public IPythonType CreateSpecificType(IArgumentSet args) {
105102
// Get list of bases that are generic but not generic class parameters, e.g A[T], B[T] but not Generic[T1, T2]
106103
var genericTypeBases = bases.Except(genericClassParameters).OfType<IGenericType>().Where(g => g.IsGeneric).ToArray();
107104

108-
// Removing all generic bases, and will only specialize genericTypeBases, remove generic class paramters entirely
109-
// We remove generic class paramters entirely because the type information is now stored in GenericParameters field
105+
// Removing all generic bases, and will only specialize genericTypeBases, remove generic class parameters entirely
106+
// We remove generic class parameters entirely because the type information is now stored in GenericParameters field
110107
// We still need generic bases so we can specialize them
111108
var specificBases = bases.Except(genericTypeBases).Except(genericClassParameters).ToList();
112109

@@ -127,12 +124,12 @@ public IPythonType CreateSpecificType(IArgumentSet args) {
127124
}
128125

129126
// Set specific class bases
130-
classType.SetBases(specificBases.Concat(newBases));
127+
classType.SetBases(specificBases.Concat(newBases), args.Eval.CurrentScope);
131128
// Now that parameters are set, check if class is generic
132129
classType._parameters = classType._genericParameters.Values.Distinct().OfType<IGenericTypeParameter>().ToList();
133130
classType.DecideGeneric();
134131
// Transfer members from generic to specific type.
135-
SetClassMembers(classType, args);
132+
classType.SetClassMembers(this, args);
136133
}
137134
return classType;
138135
}
@@ -154,7 +151,7 @@ private IGenericTypeParameter[] GetTypeParameters() {
154151
var genericClassParameter = bases.OfType<IGenericClassParameter>().FirstOrDefault();
155152

156153
// If Generic[...] is present, ordering of type variables is determined from that
157-
if (genericClassParameter != null && genericClassParameter.TypeParameters != null) {
154+
if (genericClassParameter?.TypeParameters != null) {
158155
fromBases.UnionWith(genericClassParameter.TypeParameters);
159156
} else {
160157
// otherwise look at the generic class bases
@@ -214,7 +211,7 @@ private IReadOnlyDictionary<IGenericTypeParameter, IPythonType> GetSpecificTypes
214211
// for the copy constructor. Consider 'class A(Generic[K, V], Mapping[K, V])'
215212
// constructed as 'd = {1:'a', 2:'b'}; A(d)'. Here we look through bases
216213
// and see if any matches the builtin type id. For example, Mapping or Dict
217-
// will have BultinTypeId.Dict and we can figure out specific types from
214+
// will have BuiltinTypeId.Dict and we can figure out specific types from
218215
// the content of the collection.
219216
var b = _bases.OfType<IGenericType>().Where(g => g.IsGeneric).FirstOrDefault(x => x.TypeId == type.TypeId);
220217
if (b != null && !b.Parameters.IsNullOrEmpty()) {
@@ -259,33 +256,40 @@ private IReadOnlyDictionary<IGenericTypeParameter, IPythonType> GetSpecificTypes
259256
/// Points the generic type parameter in class type to their corresponding specific type (or a generic
260257
/// type parameter if no specific type was provided)
261258
/// </summary>
262-
private void StoreGenericParameters(PythonClassType classType, IGenericTypeParameter[] genericParameters, IReadOnlyDictionary<IGenericTypeParameter, IPythonType> genericToSpecificTypes) {
259+
private void StoreGenericParameters(
260+
IPythonClassType templateClass,
261+
IEnumerable<IGenericTypeParameter> newGenericParameters,
262+
IReadOnlyDictionary<IGenericTypeParameter, IPythonType> genericToSpecificTypes) {
263+
263264
// copy original generic parameters over and try to fill them in
264-
classType._genericParameters = new Dictionary<IGenericTypeParameter, IPythonType>(GenericParameters.ToDictionary(k => k.Key, k => k.Value));
265+
_genericParameters = templateClass.GenericParameters.ToDictionary(k => k.Key, k => k.Value);
265266

266267
// Case when creating a new specific class type
267-
if (Parameters.Count == 0) {
268+
if (templateClass.Parameters.Count == 0) {
268269
// Assign class type generic type parameters to specific types
269-
for (var i = 0; i < genericParameters.Length; i++) {
270-
var gb = genericParameters[i];
271-
classType._genericParameters[gb] = genericToSpecificTypes.TryGetValue(gb, out var v) ? v : null;
270+
foreach (var gb in newGenericParameters) {
271+
_genericParameters[gb] = genericToSpecificTypes.TryGetValue(gb, out var v) ? v : null;
272272
}
273273
} else {
274274
// When Parameters field is not empty then need to update generic parameters field
275-
foreach (var gp in GenericParameters.Keys) {
276-
if (GenericParameters[gp] is IGenericTypeParameter specificType) {
275+
foreach (var gp in templateClass.GenericParameters.Keys) {
276+
if (templateClass.GenericParameters[gp] is IGenericTypeParameter specificType) {
277277
// Get unfilled type parameter or type parameter that was filled with another type parameter
278278
// and try to fill it in
279279
// e.g
280280
// class A(Generic[T]):
281281
// class B(A[U])
282282
// A has T => U
283-
classType._genericParameters[gp] = genericToSpecificTypes.TryGetValue(specificType, out var v) ? v : null;
283+
_genericParameters[gp] = genericToSpecificTypes.TryGetValue(specificType, out var v) ? v : null;
284284
}
285285
}
286286
}
287+
288+
if (!_genericParameters.IsNullOrEmpty()) {
289+
_genericName = CodeFormatter.FormatSequence(BaseName, '[', _genericParameters.Values);
290+
}
287291
}
288-
292+
289293
/// <summary>
290294
/// Given generic type such as Generic[T1, T2, ...] attempts to extract specific types
291295
/// for its parameters from an argument value. Handles common cases such as dictionary,
@@ -331,24 +335,24 @@ private void GetSpecificTypeFromArgumentValue(IGenericType gt, object argumentVa
331335
/// Transfers members from generic class to the specific class type
332336
/// while instantiating specific types for the members.
333337
/// </summary>
334-
private void SetClassMembers(PythonClassType classType, IArgumentSet args) {
335-
// Add members from the template class (this one).
338+
private void SetClassMembers(IPythonClassType templateClass, IArgumentSet args) {
339+
// Add members from the template class.
336340
// Members must be clones rather than references since
337341
// we are going to set specific types on them.
338-
classType.AddMembers(this, true);
342+
AddMembers(templateClass, true);
339343

340344
// Resolve return types of methods, if any were annotated as generics
341-
var members = classType.GetMemberNames()
345+
var members = GetMemberNames()
342346
.Except(new[] { "__class__", "__bases__", "__base__" })
343-
.ToDictionary(n => n, classType.GetMember);
347+
.ToDictionary(n => n, GetMember);
344348

345349
// Create specific types.
346350
// Functions handle generics internally upon the call to Call.
347351
foreach (var m in members) {
348352
switch (m.Value) {
349353
case IPythonTemplateType tt when tt.IsGeneric(): {
350354
var specificType = tt.CreateSpecificType(args);
351-
classType.AddMember(m.Key, specificType, true);
355+
AddMember(m.Key, specificType, true);
352356
break;
353357
}
354358
case IPythonInstance inst: {
@@ -359,12 +363,12 @@ private void SetClassMembers(PythonClassType classType, IArgumentSet args) {
359363
specificType = tt.CreateSpecificType(args);
360364
break;
361365
case IGenericTypeParameter gtd:
362-
classType.GenericParameters.TryGetValue(gtd, out specificType);
366+
GenericParameters.TryGetValue(gtd, out specificType);
363367
break;
364368
}
365369

366370
if (specificType != null) {
367-
classType.AddMember(m.Key, new PythonInstance(specificType), true);
371+
AddMember(m.Key, new PythonInstance(specificType), true);
368372
}
369373
break;
370374
}

0 commit comments

Comments
 (0)