Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -13,6 +13,7 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

using Microsoft.Python.Analysis.Specializations.Typing;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Parsing.Ast;

Expand All @@ -26,6 +27,14 @@ public IPythonType GetTypeFromAnnotation(Expression expr, out bool isGeneric, Lo
switch (expr) {
case null:
return null;
case NameExpression nameExpr:
Comment thread
CTrando marked this conversation as resolved.
// x: T
var name = GetValueFromExpression(nameExpr);
if(name is IGenericTypeParameter gtp) {
isGeneric = true;
return gtp;
}
break;
case CallExpression callExpr:
// x: NamedTuple(...)
return GetValueFromCallable(callExpr)?.GetPythonType() ?? UnknownType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,14 +325,16 @@ public IReadOnlyList<IParameterInfo> CreateFunctionParameters(IPythonClassType s
if (self != null && function.HasClassFirstArgument()) {
var p0 = fd.Parameters.FirstOrDefault();
if (p0 != null && !string.IsNullOrEmpty(p0.Name)) {
var annType = GetTypeFromAnnotation(p0.Annotation, out var isGeneric);
// Actual parameter type will be determined when method is invoked.
// The reason is that if method might be called on a derived class.
// Declare self or cls in this scope.
if (declareVariables) {
DeclareVariable(p0.Name, new PythonInstance(self), VariableSource.Declaration, p0.NameExpression);
}
// Set parameter info.
var pi = new ParameterInfo(Ast, p0, self, null, false);
// Set parameter info, declare type as annotation type for generic self
// e.g def test(self: T)
var pi = new ParameterInfo(Ast, p0, isGeneric ? annType : self, null, false);
parameters.Add(pi);
skip++;
}
Expand Down
4 changes: 4 additions & 0 deletions src/Analysis/Ast/Impl/Extensions/MemberExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public static bool IsUnknown(this IMember m) {
public static bool IsOfType(this IMember m, BuiltinTypeId typeId)
=> m?.GetPythonType().TypeId == typeId;

public static string GetString(this IMember m) {
return (m as IPythonConstant)?.GetString();
}

public static IPythonType GetPythonType(this IMember m) {
switch (m) {
case IPythonType pt:
Expand Down
13 changes: 13 additions & 0 deletions src/Analysis/Ast/Impl/Extensions/PythonClassExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// permissions and limitations under the License.

using Microsoft.Python.Analysis.Analyzer;
using Microsoft.Python.Analysis.Specializations.Typing;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Core;

Expand Down Expand Up @@ -56,5 +57,17 @@ public static bool IsPrivateMember(this IPythonClassType cls, string memberName)
var unmangledName = cls.UnmangleMemberName(memberName);
return unmangledName.StartsWithOrdinal("__") && memberName.EqualsOrdinal($"_{cls.Name}{unmangledName}");
}

/// <summary>
/// Gets specific type for the given generic type parameter, resolving bounds as well
/// </summary>
public static bool GetSpecificType(this IPythonClassType cls, IGenericTypeParameter param, out IPythonType specificType) {
cls.GenericParameters.TryGetValue(param, out specificType);
// If type has not been found, check if the type parameter has an upper bound and use that
if (specificType is IGenericTypeParameter gtp && gtp.Bound != null) {
specificType = gtp.Bound;
}
return specificType != null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,16 @@ namespace Microsoft.Python.Analysis.Specializations.Typing {
/// <summary>
/// Represents generic type definition. Typically value returned by TypeVar.
/// </summary>
public interface IGenericTypeParameter: IPythonType, IEquatable<IGenericTypeParameter> {
/// <remarks>See 'https://docs.python.org/3/library/typing.html#typing.TypeVar'</remarks>
public interface IGenericTypeParameter : IPythonType, IEquatable<IGenericTypeParameter> {
/// <summary>
/// List of constraints for the type.
/// </summary>
/// <remarks>See 'https://docs.python.org/3/library/typing.html#typing.TypeVar'</remarks>
IReadOnlyList<IPythonType> Constraints { get; }

/// <summary>
/// Bound type, i.e upper bound this type parameter can represent
/// </summary>
IPythonType Bound { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,17 @@

namespace Microsoft.Python.Analysis.Specializations.Typing.Types {
internal sealed class GenericTypeParameter : PythonType, IGenericTypeParameter {
public GenericTypeParameter(string name, IPythonModule declaringModule, IReadOnlyList<IPythonType> constraints, string documentation, IndexSpan location)
public GenericTypeParameter(string name, IPythonModule declaringModule, IReadOnlyList<IPythonType> constraints,
IPythonType bound, string documentation, IndexSpan location)
: base(name, new Location(declaringModule), documentation) {
Constraints = constraints ?? Array.Empty<IPythonType>();
Bound = bound;
}

#region IGenericTypeParameter
public IReadOnlyList<IPythonType> Constraints { get; }
public IPythonType Bound { get; }
#endregion

public override BuiltinTypeId TypeId => BuiltinTypeId.Type;
public override PythonMemberType MemberType => PythonMemberType.Generic;
Expand Down Expand Up @@ -77,6 +83,22 @@ private static bool TypeVarArgumentsValid(IArgumentSet argSet) {
return true;
}

/// <summary>
/// Given arguments to TypeVar, finds the bound type
/// </summary>
private static IPythonType GetBoundType(IArgumentSet argSet) {
var eval = argSet.Eval;
var rawBound = argSet.GetArgumentValue<IMember>("bound");
switch (rawBound) {
case IPythonType t:
return t;
case IPythonConstant c when c.GetString() != null:
return eval.GetTypeFromString(c.GetString());
default:
return rawBound.GetPythonType();
}
}

public static IPythonType FromTypeVar(IArgumentSet argSet, IPythonModule declaringModule, IndexSpan location = default) {
if (!TypeVarArgumentsValid(argSet)) {
return declaringModule.Interpreter.UnknownType;
Expand All @@ -85,15 +107,16 @@ public static IPythonType FromTypeVar(IArgumentSet argSet, IPythonModule declari
var args = argSet.Arguments;
var constraintArgs = argSet.ListArgument?.Values ?? Array.Empty<IMember>();

var name = (args[0].Value as IPythonConstant)?.GetString();
var name = argSet.GetArgumentValue<IPythonConstant>("name")?.GetString();
var constraints = constraintArgs.Select(a => {
// Type constraints may be specified as type name strings.
var typeString = (a as IPythonConstant)?.GetString();
var typeString = a.GetString();
return !string.IsNullOrEmpty(typeString) ? argSet.Eval.GetTypeFromString(typeString) : a.GetPythonType();
}).ToArray() ?? Array.Empty<IPythonType>();

var bound = GetBoundType(argSet);
var documentation = GetDocumentation(args, constraints);
return new GenericTypeParameter(name, declaringModule, constraints, documentation, location);

return new GenericTypeParameter(name, declaringModule, constraints, bound, documentation, location);
}

private static string GetDocumentation(IReadOnlyList<IArgument> args, IReadOnlyList<IPythonType> constraints) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ private IPythonType CreateAnyStr() {
var docArgs = new[] { $"'{name}'" }.Concat(constraints.Select(c => c.Name));
var documentation = CodeFormatter.FormatSequence("TypeVar", '(', docArgs);

return new GenericTypeParameter(name, this, constraints, documentation, default);
return new GenericTypeParameter(name, this, constraints, default, documentation, default);
}

private IPythonType CreateGenericClassParameter(IReadOnlyList<IPythonType> typeArgs) {
Expand Down
3 changes: 2 additions & 1 deletion src/Analysis/Ast/Impl/Types/PythonClassType.Generics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ private IReadOnlyDictionary<IGenericTypeParameter, IPythonType> GetSpecificTypes
}
}
}

return genericTypeToSpecificType;
}

Expand Down Expand Up @@ -284,7 +285,7 @@ private void StoreGenericParameters(PythonClassType classType, IGenericTypeParam
}
}
}

/// <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
19 changes: 15 additions & 4 deletions src/Analysis/Ast/Impl/Types/PythonFunctionOverload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ private IMember GetSpecificReturnType(IPythonClassType selfClassType, IArgumentS
break;

case IGenericTypeParameter gtd1 when selfClassType != null:
return CreateSpecificReturnFromTypeVar(selfClassType, gtd1); // -> _T
return CreateSpecificReturnFromTypeVar(selfClassType, args, gtd1); // -> _T

case IGenericTypeParameter gtd2 when args != null: // -> T on standalone function.
return args.Arguments.FirstOrDefault(a => gtd2.Equals(a.Type))?.Value as IMember;
Expand Down Expand Up @@ -172,8 +172,8 @@ private IMember CreateSpecificReturnFromClassType(IPythonClassType selfClassType
return null;
}

private IMember CreateSpecificReturnFromTypeVar(IPythonClassType selfClassType, IGenericTypeParameter returnType) {
if (selfClassType.GenericParameters.TryGetValue(returnType, out var specificType)) {
private IMember CreateSpecificReturnFromTypeVar(IPythonClassType selfClassType, IArgumentSet args, IGenericTypeParameter returnType) {
if (selfClassType.GetSpecificType(returnType, out var specificType)) {
return new PythonInstance(specificType);
}

Expand All @@ -184,10 +184,21 @@ private IMember CreateSpecificReturnFromTypeVar(IPythonClassType selfClassType,
.FirstOrDefault(b => b.GetMember(ClassMember.Name) != null && b.GenericParameters.ContainsKey(returnType));

// Try and infer return value from base class
if (baseType != null && baseType.GenericParameters.TryGetValue(returnType, out specificType)) {
if (baseType != null && baseType.GetSpecificType(returnType, out specificType)) {
return new PythonInstance(specificType);
}

// Try getting type from passed in arguments
var typeFromArgs = args?.Arguments.FirstOrDefault(a => returnType.Equals(a.Type))?.Value as IMember;
if (typeFromArgs != null) {
return typeFromArgs;
}

// Try getting the type from the type parameter bound
if (returnType.Bound != null) {
return new PythonInstance(returnType.Bound);
}

// Try returning the constraint
// TODO: improve this, the heuristic is pretty basic and tailored to simple func(_T) -> _T
var name = StaticReturnValue.GetPythonType()?.Name;
Expand Down
54 changes: 54 additions & 0 deletions src/Analysis/Ast/Test/GenericsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,25 @@ def set_width(self, w: float) -> 'Square':
analysis.Should().HaveVariable("square").Which.Should().HaveType("Square");
}

[TestMethod, Priority(0)]
public async Task GenericSelf() {
const string code = @"
from typing import TypeVar

T = TypeVar('T')

class C:
def test(self: T) -> T:
pass

class D(C): ...

x = D().test()
";
var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X);
analysis.Should().HaveVariable("x").Which.Should().HaveType("D");
}

[TestMethod, Priority(0)]
public async Task GenericClassToDifferentTypes() {
const string code = @"
Expand Down Expand Up @@ -1122,5 +1141,40 @@ def log(self, message: str) -> None:
var analysis = await GetAnalysisAsync(code);
analysis.Should().HaveVariable("x").OfType(BuiltinTypeId.Int);
}

[TestMethod, Priority(0)]
public async Task GenericBound() {
const string code = @"
from typing import TypeVar, Generic
from logging import Logger, getLogger

T = TypeVar('T', bound='A')

class A: ...

class Test(Generic[T]):
def get(self) -> T: ...

x = Test().get()
";
var analysis = await GetAnalysisAsync(code);
analysis.Should().HaveVariable("x").OfType("A");
}

[TestMethod, Priority(0)]
public async Task GenericPath() {
const string code = @"
import pathlib

h = pathlib._PurePathBase
root = pathlib.Path('/some/directory')
subdir = root / 'subdir'
child = subdir / 'file.txt'
";
var analysis = await GetAnalysisAsync(code, PythonVersions.Python37);
analysis.Should().HaveVariable("root").OfType("Path");
analysis.Should().HaveVariable("subdir").OfType("PurePath");
analysis.Should().HaveVariable("child").OfType("PurePath");
}
}
}