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

Improve handling of circular and imported class bases #1497

Merged
merged 13 commits into from
Sep 3, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,23 @@ public void DeclareVariable(string name, IMember value, VariableSource source, N
=> DeclareVariable(name, value, source, GetLocationOfName(location), overwrite);

public void DeclareVariable(string name, IMember value, VariableSource source, Location location, bool overwrite = true) {
if (source == VariableSource.Import) {
// Duplicate declaration so if variable gets overwritten it can still be retrieved. Consider:
// from X import A
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe update the comment now that it loses its old context:
Store declaration source so in case if variable gets overwritten...

// class A(A): ...
CurrentScope.DeclareImported(name, value, location);
}

var member = GetInScope(name);
if (member != null && !overwrite) {
return;
}

if (source == VariableSource.Import && value is IVariable v) {
CurrentScope.LinkVariable(name, v, location);
return;
}

if (member != null) {
if (!value.IsUnknown()) {
CurrentScope.DeclareVariable(name, value, source, location);
Expand Down
4 changes: 2 additions & 2 deletions src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -361,9 +361,9 @@ private void AnalyzeEntry(IDependencyChainNode<PythonAnalyzerEntry> node, Python

private void LogCompleted(IDependencyChainNode<PythonAnalyzerEntry> node, IPythonModule module, Stopwatch stopWatch, TimeSpan startTime) {
if (_log != null) {
var completed = node != null && module.Analysis is LibraryAnalysis ? "completed for library" : "completed ";
var completed = node != null && module.Analysis is LibraryAnalysis ? "completed for library" : "completed";
var message = node != null
? $"Analysis of {module.Name}({module.ModuleType}) on depth {node.VertexDepth} {completed} in {(stopWatch.Elapsed - startTime).TotalMilliseconds} ms."
? $"Analysis of {module.Name} ({module.ModuleType}) on depth {node.VertexDepth} {completed} in {(stopWatch.Elapsed - startTime).TotalMilliseconds} ms."
: $"Out of order analysis of {module.Name}({module.ModuleType}) completed in {(stopWatch.Elapsed - startTime).TotalMilliseconds} ms.";
_log.Log(TraceEventType.Verbose, message);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Analysis/Ast/Impl/Analyzer/Symbols/ClassEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public void EvaluateClass() {
_class = classInfo;

var bases = ProcessBases();
_class.SetBases(bases);
_class.SetBases(bases, Eval);
_class.DecideGeneric();
// Declare __class__ variable in the scope.
Eval.DeclareVariable("__class__", _class, VariableSource.Declaration);
Expand Down
13 changes: 13 additions & 0 deletions src/Analysis/Ast/Impl/Extensions/EvaluatorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ public static IMember LookupNameInScopes(this IExpressionEvaluator eval, string
=> eval.LookupNameInScopes(name, out scope, out _, options);
public static IMember LookupNameInScopes(this IExpressionEvaluator eval, string name, LookupOptions options = LookupOptions.Normal)
=> eval.LookupNameInScopes(name, out _, out _, options);

public static IMember LookupImportedNameInScopes(this IExpressionEvaluator eval, string name, out IScope scope) {
scope = null;
foreach(var s in eval.CurrentScope.EnumerateTowardsGlobal) {
var v = s.Imported[name];
if(v != null) {
scope = s;
return v.Value;
}
}
return null;
}

public static IDisposable OpenScope(this IExpressionEvaluator eval, IPythonClassType cls)
=> eval.OpenScope(cls.DeclaringModule, cls.ClassDefinition);
public static IDisposable OpenScope(this IExpressionEvaluator eval, IPythonFunctionType ft)
Expand Down
44 changes: 22 additions & 22 deletions src/Analysis/Ast/Impl/Types/PythonClassType.Generics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@

namespace Microsoft.Python.Analysis.Types {
internal partial class PythonClassType {
private readonly ReentrancyGuard<IPythonClassType> _genericSpecializationGuard = new ReentrancyGuard<IPythonClassType>();
private readonly ReentrancyGuard<IPythonClassType> _genericResolutionGuard = new ReentrancyGuard<IPythonClassType>();
private readonly object _genericParameterLock = new object();

private bool _isGeneric;
private object _genericParameterLock = new object();
private Dictionary<string, PythonClassType> _specificTypeCache;
private Dictionary<IGenericTypeParameter, IPythonType> _genericParameters;
private IReadOnlyList<IGenericTypeParameter> _parameters = new List<IGenericTypeParameter>();
private ReentrancyGuard<IPythonClassType> _genericSpecializationGuard = new ReentrancyGuard<IPythonClassType>();
private ReentrancyGuard<IPythonClassType> _genericResolutionGuard = new ReentrancyGuard<IPythonClassType>();

#region IGenericType
/// <summary>
Expand Down Expand Up @@ -67,10 +68,10 @@ public IPythonType CreateSpecificType(IArgumentSet args) {
// type parameter T -> int, U -> str, etc.
var genericTypeToSpecificType = GetSpecificTypes(args, genericTypeParameters, newBases);

PythonClassType classType = new PythonClassType(BaseName, new Location(DeclaringModule));
var classType = new PythonClassType(BaseName, new Location(DeclaringModule));
// Storing generic parameters allows methods returning generic types
// to know what type parameter returns what specific type
StoreGenericParameters(classType, genericTypeParameters, genericTypeToSpecificType);
classType.StoreGenericParameters(genericTypeParameters, genericTypeToSpecificType);

// Set generic name
if (!classType._genericParameters.IsNullOrEmpty()) {
Expand Down Expand Up @@ -127,12 +128,12 @@ public IPythonType CreateSpecificType(IArgumentSet args) {
}

// Set specific class bases
classType.SetBases(specificBases.Concat(newBases));
classType.SetBases(specificBases.Concat(newBases), args.Eval);
// Now that parameters are set, check if class is generic
classType._parameters = classType._genericParameters.Values.Distinct().OfType<IGenericTypeParameter>().ToList();
classType.DecideGeneric();
// Transfer members from generic to specific type.
SetClassMembers(classType, args);
classType.SetClassMembers(args);
}
return classType;
}
Expand All @@ -154,7 +155,7 @@ private IGenericTypeParameter[] GetTypeParameters() {
var genericClassParameter = bases.OfType<IGenericClassParameter>().FirstOrDefault();

// If Generic[...] is present, ordering of type variables is determined from that
if (genericClassParameter != null && genericClassParameter.TypeParameters != null) {
if (genericClassParameter?.TypeParameters != null) {
fromBases.UnionWith(genericClassParameter.TypeParameters);
} else {
// otherwise look at the generic class bases
Expand Down Expand Up @@ -259,16 +260,15 @@ private IReadOnlyDictionary<IGenericTypeParameter, IPythonType> GetSpecificTypes
/// Points the generic type parameter in class type to their corresponding specific type (or a generic
/// type parameter if no specific type was provided)
/// </summary>
private void StoreGenericParameters(PythonClassType classType, IGenericTypeParameter[] genericParameters, IReadOnlyDictionary<IGenericTypeParameter, IPythonType> genericToSpecificTypes) {
private void StoreGenericParameters(IGenericTypeParameter[] genericParameters, IReadOnlyDictionary<IGenericTypeParameter, IPythonType> genericToSpecificTypes) {
// copy original generic parameters over and try to fill them in
classType._genericParameters = new Dictionary<IGenericTypeParameter, IPythonType>(GenericParameters.ToDictionary(k => k.Key, k => k.Value));
_genericParameters = new Dictionary<IGenericTypeParameter, IPythonType>(GenericParameters.ToDictionary(k => k.Key, k => k.Value));

// Case when creating a new specific class type
if (Parameters.Count == 0) {
// Assign class type generic type parameters to specific types
for (var i = 0; i < genericParameters.Length; i++) {
var gb = genericParameters[i];
classType._genericParameters[gb] = genericToSpecificTypes.TryGetValue(gb, out var v) ? v : null;
foreach (var gb in genericParameters) {
_genericParameters[gb] = genericToSpecificTypes.TryGetValue(gb, out var v) ? v : null;
}
} else {
// When Parameters field is not empty then need to update generic parameters field
Expand All @@ -280,12 +280,12 @@ private void StoreGenericParameters(PythonClassType classType, IGenericTypeParam
// class A(Generic[T]):
// class B(A[U])
// A has T => U
classType._genericParameters[gp] = genericToSpecificTypes.TryGetValue(specificType, out var v) ? v : null;
_genericParameters[gp] = genericToSpecificTypes.TryGetValue(specificType, out var v) ? v : null;
}
}
}
}

/// <summary>
/// Given generic type such as Generic[T1, T2, ...] attempts to extract specific types
/// for its parameters from an argument value. Handles common cases such as dictionary,
Expand Down Expand Up @@ -331,24 +331,24 @@ private void GetSpecificTypeFromArgumentValue(IGenericType gt, object argumentVa
/// Transfers members from generic class to the specific class type
/// while instantiating specific types for the members.
/// </summary>
private void SetClassMembers(PythonClassType classType, IArgumentSet args) {
private void SetClassMembers(IArgumentSet args) {
// 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);
AddMembers(this, true);

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

// Create specific types.
// Functions handle generics internally upon the call to Call.
foreach (var m in members) {
switch (m.Value) {
case IPythonTemplateType tt when tt.IsGeneric(): {
var specificType = tt.CreateSpecificType(args);
classType.AddMember(m.Key, specificType, true);
AddMember(m.Key, specificType, true);
break;
}
case IPythonInstance inst: {
Expand All @@ -359,12 +359,12 @@ private void SetClassMembers(PythonClassType classType, IArgumentSet args) {
specificType = tt.CreateSpecificType(args);
break;
case IGenericTypeParameter gtd:
classType.GenericParameters.TryGetValue(gtd, out specificType);
GenericParameters.TryGetValue(gtd, out specificType);
break;
}

if (specificType != null) {
classType.AddMember(m.Key, new PythonInstance(specificType), true);
AddMember(m.Key, new PythonInstance(specificType), true);
}
break;
}
Expand Down
69 changes: 61 additions & 8 deletions src/Analysis/Ast/Impl/Types/PythonClassType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Python.Analysis.Analyzer;
using Microsoft.Python.Analysis.Modules;
using Microsoft.Python.Analysis.Specializations.Typing;
using Microsoft.Python.Analysis.Types.Collections;
Expand All @@ -30,7 +31,7 @@

namespace Microsoft.Python.Analysis.Types {
[DebuggerDisplay("Class {Name}")]
internal partial class PythonClassType : PythonType, IPythonClassType, IGenericType, IEquatable<IPythonClassType> {
internal partial class PythonClassType : PythonType, IPythonClassType, IEquatable<IPythonClassType> {
private static readonly string[] _classMethods = { "mro", "__dict__", @"__weakref__" };

private readonly ReentrancyGuard<IPythonClassType> _memberGuard = new ReentrancyGuard<IPythonClassType>();
Expand All @@ -44,11 +45,9 @@ internal PythonClassType(string name, Location location)
: base(name, location, string.Empty, BuiltinTypeId.Type) {
}

public PythonClassType(
ClassDefinition classDefinition,
Location location,
BuiltinTypeId builtinTypeId = BuiltinTypeId.Type
) : base(classDefinition.Name, location, classDefinition.GetDocumentation(), builtinTypeId) {
public PythonClassType(ClassDefinition classDefinition, Location location, BuiltinTypeId builtinTypeId = BuiltinTypeId.Type)
: base(classDefinition.Name, location, classDefinition.GetDocumentation(), builtinTypeId) {
Check.ArgumentNotNull(nameof(location), location.Module);
location.Module.AddAstNode(this, classDefinition);
}

Expand Down Expand Up @@ -192,12 +191,15 @@ public IReadOnlyList<IPythonType> Mro {

#endregion

internal void SetBases(IEnumerable<IPythonType> bases) {
internal void SetBases(IEnumerable<IPythonType> bases, IExpressionEvaluator eval = null) {
if (_bases != null) {
return; // Already set
}

bases = bases != null ? bases.Where(b => !b.GetPythonType().IsUnknown()).ToArray() : Array.Empty<IPythonType>();
// Consider
// from X import A
// class A(A): ...
bases = DisambiguateBases(bases, eval).ToArray();

// For Python 3+ attach object as a base class by default except for the object class itself.
if (DeclaringModule.Interpreter.LanguageVersion.Is3x() && DeclaringModule.ModuleType != ModuleType.Builtins) {
Expand Down Expand Up @@ -231,6 +233,7 @@ internal static IReadOnlyList<IPythonType> CalculateMro(IPythonType type, HashSe
if (type == null) {
return Array.Empty<IPythonType>();
}

recursionProtection = recursionProtection ?? new HashSet<IPythonType>();
if (!recursionProtection.Add(type)) {
return Array.Empty<IPythonType>();
Expand Down Expand Up @@ -281,5 +284,55 @@ internal static IReadOnlyList<IPythonType> CalculateMro(IPythonType type, HashSe
public bool Equals(IPythonClassType other)
=> Name == other?.Name && DeclaringModule.Equals(other?.DeclaringModule);

private IEnumerable<IPythonType> DisambiguateBases(IEnumerable<IPythonType> bases, IExpressionEvaluator eval) {
if (bases == null) {
return Array.Empty<IPythonType>();
}

if (eval == null) {
return FilterCircularBases(bases).Where(b => !b.IsUnknown());
}

var newBases = new List<IPythonType>();
foreach (var b in bases) {
var imported = eval.LookupImportedNameInScopes(b.Name, out _);
if (imported is IPythonType importedType) {
// Variable with same name as the base was imported.
// If there is also a local declaration, we need to figure out which one wins.
var declared = eval.LookupNameInScopes(b.Name, out var scope);
if (declared != null && scope != null) {
var v = scope.Variables[b.Name];
if (v.Source != VariableSource.Import && v.Value is IPythonClassType cls && cls.Location.IndexSpan.Start >= Location.IndexSpan.Start) {
// There is a declaration with the same name, but it appears later in the module. Use the import.
if (!importedType.IsUnknown()) {
newBases.Add(importedType);
}
continue;
}
}
}

if (!b.IsUnknown()) {
newBases.Add(b);
}
}
return FilterCircularBases(newBases);
}

private IEnumerable<IPythonType> FilterCircularBases(IEnumerable<IPythonType> bases) {
// Inspect each base chain and exclude bases that chains to this class.
foreach(var b in bases.Where(x => !Equals(x))) {
if (b is IPythonClassType cls) {
var chain = cls.Bases
.MaybeEnumerate()
.OfType<IPythonClassType>()
.SelectMany(x => x.TraverseDepthFirst(c => c.Bases.MaybeEnumerate().OfType<IPythonClassType>()));
if (chain.Any(Equals)) {
continue;
}
}
yield return b;
}
}
}
}
5 changes: 5 additions & 0 deletions src/Analysis/Ast/Impl/Values/Definitions/IScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ public interface IScope {
/// </summary>
IVariableCollection Globals { get; }

/// <summary>
/// Collection of variables imported from other modules.
/// </summary>
IVariableCollection Imported { get; }

/// <summary>
/// Module the scope belongs to.
/// </summary>
Expand Down
45 changes: 45 additions & 0 deletions src/Analysis/Ast/Impl/Values/EmptyGlobalScope.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Parsing.Ast;

namespace Microsoft.Python.Analysis.Values {
internal class EmptyGlobalScope : IGlobalScope {
public EmptyGlobalScope(IPythonModule module) {
GlobalScope = this;
Module = module;
}

public IPythonModule Module { get; }
public string Name => string.Empty;
public ScopeStatement Node => Module.Analysis.Ast;
public IScope OuterScope => null;
public IGlobalScope GlobalScope { get; }
public IReadOnlyList<IScope> Children => Array.Empty<IScope>();
public IEnumerable<IScope> EnumerateTowardsGlobal => Enumerable.Repeat(this, 1);
public IEnumerable<IScope> EnumerateFromGlobal => Enumerable.Repeat(this, 1);
public IVariableCollection Variables => VariableCollection.Empty;
public IVariableCollection NonLocals => VariableCollection.Empty;
public IVariableCollection Globals => VariableCollection.Empty;
public IVariableCollection Imported => VariableCollection.Empty;

public void DeclareVariable(string name, IMember value, VariableSource source, Location location) { }
public void LinkVariable(string name, IVariable v, Location location) { }
}
}
Loading