Changed ClassType.cloneForSpecialization to take a list of type arguments as a parameter.

Moved __getattribute__ processing into expression evaluator.
Implemented more specialization logic.
Removed hacky node reference in FunctionParameter interface.
This commit is contained in:
Eric Traut 2019-03-16 16:14:47 -07:00
parent 6a246da971
commit 18ab06dbe3
5 changed files with 242 additions and 108 deletions

View File

@ -36,6 +36,8 @@ interface TypeResult {
node: ExpressionNode;
}
export class TypeVarMap extends StringMap<Type> {}
export enum EvaluatorFlags {
None = 0,
@ -129,6 +131,71 @@ export class ExpressionEvaluator {
return [decoratedType, warnIfDuplicate];
}
getTypeFromClassMemberAccess(memberName: string, classType: ClassType,
includeInstanceMembers: boolean): Type | undefined {
// Build a map of type parameters and the type arguments associated with them.
let typeArgMap = new TypeVarMap();
let typeArgs = classType.getTypeArguments();
classType.getTypeParameters().forEach((typeParam, index) => {
const typeVarName = typeParam.getName();
let typeArgType: Type;
if (typeArgs) {
if (index >= typeArgs.length) {
typeArgType = AnyType.create();
} else {
typeArgType = typeArgs[index] as Type;
}
} else {
typeArgType = this._specializeTypeVarType(typeParam);
}
typeArgMap.set(typeVarName, typeArgType);
});
let memberInfo = TypeUtils.lookUpClassMember(classType, memberName, includeInstanceMembers);
if (memberInfo) {
let type = TypeUtils.getEffectiveTypeOfMember(memberInfo);
if (type instanceof PropertyType) {
type = type.getEffectiveReturnType();
}
return this._specializeType(type, typeArgMap);
}
// See if the class has a "__getattribute__" or "__getattr__" method.
// If so, aribrary members are supported.
let getAttribMember = TypeUtils.lookUpClassMember(classType, '__getattribute__');
if (getAttribMember && getAttribMember.class) {
const isObjectClass = getAttribMember.class.isBuiltIn() &&
getAttribMember.class.getClassName() === 'object';
// The built-in 'object' class, from which every class derives,
// implements the default __getattribute__ method. We want to ignore
// this one. If this method is overridden, we need to assume that
// all members can be accessed.
if (!isObjectClass) {
const getAttribType = TypeUtils.getEffectiveTypeOfMember(getAttribMember);
if (getAttribType instanceof FunctionType) {
return this._specializeType(
getAttribType.getEffectiveReturnType(), typeArgMap);
}
}
}
let getAttrMember = TypeUtils.lookUpClassMember(classType, '__getattr__');
if (getAttrMember) {
const getAttrType = TypeUtils.getEffectiveTypeOfMember(getAttrMember);
if (getAttrType instanceof FunctionType) {
return this._specializeType(
getAttrType.getEffectiveReturnType(), typeArgMap);
}
}
return undefined;
}
private _getTypeFromExpression(node: ExpressionNode, flags: EvaluatorFlags): TypeResult {
if (this._readTypeFromCache) {
let cachedType = this._readTypeFromCache(node);
@ -286,55 +353,6 @@ export class ExpressionEvaluator {
const baseType = baseTypeResult.type;
const memberName = node.memberName.nameToken.value;
const getTypeFromClass = (classType: ClassType, includeInstanceMembers: boolean) => {
let type: Type | undefined;
let memberInfo = TypeUtils.lookUpClassMember(classType, memberName, includeInstanceMembers);
if (memberInfo) {
type = TypeUtils.getEffectiveTypeOfMember(memberInfo);
if (type instanceof PropertyType) {
type = type.getEffectiveReturnType();
}
return type;
}
// See if the class has a "__getattribute__" or "__getattr__" method.
// If so, aribrary members are supported.
let getAttribMember = TypeUtils.lookUpClassMember(classType, '__getattribute__');
if (getAttribMember && getAttribMember.class) {
const isObjectClass = getAttribMember.class.isBuiltIn() &&
getAttribMember.class.getClassName() === 'object';
// The built-in 'object' class, from which every class derives,
// implements the default __getattribute__ method. We want to ignore
// this one. If this method is overridden, we need to assume that
// all members can be accessed.
if (!isObjectClass) {
const getAttribType = TypeUtils.getEffectiveTypeOfMember(getAttribMember);
if (getAttribType instanceof FunctionType) {
return getAttribType.getEffectiveReturnType();
}
}
}
let getAttrMember = TypeUtils.lookUpClassMember(classType, '__getattr__');
if (getAttrMember) {
const getAttrType = TypeUtils.getEffectiveTypeOfMember(getAttrMember);
if (getAttrType instanceof FunctionType) {
return getAttrType.getEffectiveReturnType();
}
}
// If the class has decorators, there may be additional fields
// added that we don't know about.
// TODO - figure out a better approach here.
if (!classType.hasDecorators()) {
this._addError(
`'${ memberName }' is not a known member of type '${ baseType.asString() }'`,
node.memberName);
}
return UnknownType.create();
};
let type: Type | undefined;
let isClassOrObjectMember = false;
@ -345,10 +363,10 @@ export class ExpressionEvaluator {
// Assume that the base type is a class or object.
isClassOrObjectMember = true;
} else if (baseType instanceof ClassType) {
type = getTypeFromClass(baseType, false);
type = this._getTypeFromClassMemberAccess(node.memberName, baseType, false);
isClassOrObjectMember = true;
} else if (baseType instanceof ObjectType) {
type = getTypeFromClass(baseType.getClassType(), true);
type = this._getTypeFromClassMemberAccess(node.memberName, baseType.getClassType(), true);
isClassOrObjectMember = true;
} else if (baseType instanceof ModuleType) {
let memberInfo = baseType.getFields().get(memberName);
@ -398,6 +416,29 @@ export class ExpressionEvaluator {
return { type, node, isClassOrObjectMember };
}
private _getTypeFromClassMemberAccess(memberNameNode: NameNode,
classType: ClassType, includeInstanceMembers: boolean) {
const memberName = memberNameNode.nameToken.value;
let type = this.getTypeFromClassMemberAccess(memberName,
classType, includeInstanceMembers);
if (type) {
return type;
}
// If the class has decorators, there may be additional fields
// added that we don't know about.
// TODO - figure out a better approach here.
if (!classType.hasDecorators()) {
this._addError(
`'${ memberName }' is not a known member of type '${ classType.asString() }'`,
memberNameNode);
}
return UnknownType.create();
}
private _getTypeFromIndexExpression(node: IndexExpressionNode, flags: EvaluatorFlags): TypeResult {
let type: Type | undefined;
const baseTypeResult = this._getTypeFromExpression(node.baseExpression, EvaluatorFlags.None);
@ -529,7 +570,7 @@ export class ExpressionEvaluator {
} else if (callType instanceof FunctionType) {
// The stdlib collections/__init__.pyi stub file defines namedtuple
// as a function rather than a class, so we need to check for it here.
if (callType.getSpecialBuiltInName() === 'namedtuple') {
if (callType.getBuiltInName() === 'namedtuple') {
type = this.createNamedTupleType(node, false);
flags &= ~EvaluatorFlags.ConvertClassToObject;
} else {
@ -884,10 +925,8 @@ export class ExpressionEvaluator {
});
let type = ScopeUtils.getBuiltInType(this._scope, 'list') as ClassType;
type = type.cloneForSpecialization();
// TODO - infer list type from listTypes
type.setTypeArguments([]);
type = type.cloneForSpecialization([]);
// List literals are always objects, not classes.
let convertedType = this._convertClassToObject(type, EvaluatorFlags.ConvertClassToObject);
@ -908,10 +947,8 @@ export class ExpressionEvaluator {
}
let type = ScopeUtils.getBuiltInType(this._scope, 'set') as ClassType;
type = type.cloneForSpecialization();
// TODO - infer set type
type.setTypeArguments([]);
type = type.cloneForSpecialization([]);
let convertedType = this._convertClassToObject(type, flags);
@ -995,8 +1032,7 @@ export class ExpressionEvaluator {
typeArgCount = paramLimit;
}
let specializedType = classType.cloneForSpecialization();
specializedType.setTypeArguments(typeArgs.map(t => t.type));
let specializedType = classType.cloneForSpecialization(typeArgs.map(t => t.type));
return this._convertClassToObject(specializedType, flags);
}
@ -1062,10 +1098,8 @@ export class ExpressionEvaluator {
typeArgCount = typeParameters.length;
}
let specializedClass = classType.cloneForSpecialization();
// TODO - need to verify constraints of arguments
specializedClass.setTypeArguments(typeArgs.map(t => t.type));
let specializedClass = classType.cloneForSpecialization(typeArgs.map(t => t.type));
return specializedClass;
}
@ -1176,6 +1210,108 @@ export class ExpressionEvaluator {
return this._convertClassToObject(specializedType, flags);
}
// Converts a type var type into the most specific type
// that fits the specified constraints.
private _specializeTypeVarType(type: TypeVarType): Type {
let subtypes: Type[] = [];
type.getConstraints().forEach(constraint => {
subtypes.push(constraint);
});
const boundType = type.getBoundType();
if (boundType) {
subtypes.push(boundType);
}
if (subtypes.length === 0) {
return AnyType.create();
}
return TypeUtils.combineTypesArray(subtypes);
}
// Specializes a (potentially generic) type by substituting
// type variables with specified types.
private _specializeType(type: Type, typeVarMap: TypeVarMap): Type {
if (type.isAny()) {
return type;
}
if (type instanceof NoneType) {
return type;
}
if (type instanceof TypeVarType) {
const replacementType = typeVarMap.get(type.getName());
if (replacementType) {
return replacementType;
}
return type;
}
if (type instanceof UnionType) {
let subtypes: Type[] = [];
type.getTypes().forEach(typeEntry => {
subtypes.push(this._specializeType(typeEntry, typeVarMap));
});
return TypeUtils.combineTypesArray(subtypes);
}
if (type instanceof ObjectType) {
const classType = this._specializeClassType(type.getClassType(), typeVarMap);
return new ObjectType(classType);
}
if (type instanceof ClassType) {
return this._specializeClassType(type, typeVarMap);
}
if (type instanceof TupleType) {
// TODO - need to implement
return type;
}
if (type instanceof FunctionType) {
// TODO - need to implement
return type;
}
// TODO - need to implement
return type;
}
private _specializeClassType(classType: ClassType, typeVarMap: TypeVarMap): ClassType {
// Handle the common case where the class has no type parameters.
if (classType.getTypeParameters().length === 0) {
return classType;
}
const oldTypeArgs = classType.getTypeArguments();
let newTypeArgs: Type[] = [];
classType.getTypeParameters().forEach((typeParam, index) => {
let typeArgType: Type;
// If type args were previously provided, specialize them.
// Otherwise use the specialized type parameter.
if (oldTypeArgs) {
if (index >= oldTypeArgs.length) {
typeArgType = AnyType.create();
} else {
typeArgType = this._specializeType(oldTypeArgs[index] as Type, typeVarMap);
}
} else {
typeArgType = this._specializeTypeVarType(typeParam);
}
newTypeArgs.push(typeArgType);
});
return classType.cloneForSpecialization(newTypeArgs);
}
private _convertClassToObject(type: Type, flags: EvaluatorFlags): Type {
if (flags & EvaluatorFlags.ConvertClassToObject) {
if (type instanceof ClassType) {

View File

@ -231,8 +231,7 @@ export abstract class SemanticAnalyzer extends ParseTreeWalker {
category: param.category,
name: param.name ? param.name.nameToken.value : undefined,
hasDefault: !!param.defaultValue,
type: UnknownType.create(),
node: param
type: UnknownType.create()
};
functionType.addParameter(typeParam);

View File

@ -166,7 +166,7 @@ export class TypeAnalyzer extends ParseTreeWalker {
if (this._fileInfo.isCollectionsStubFile) {
// Stash away the name of the function since we need to handle
// 'namedtuple' specially.
functionType.setSpecialBuiltInName(node.name.nameToken.value);
functionType.setBuiltInName(node.name.nameToken.value);
}
const functionParams = functionType.getParameters();
@ -229,21 +229,23 @@ export class TypeAnalyzer extends ParseTreeWalker {
}
let functionScope = this._enterScope(node, () => {
const parameters = functionType.getParameters();
assert(parameters.length === node.parameters.length);
// Add the parameters to the scope and bind their types.
functionType.getParameters().forEach(param => {
parameters.forEach((param, index) => {
const paramNode = node.parameters[index];
if (param.name) {
if (param.category === ParameterCategory.Simple) {
let declaration: Declaration | undefined;
if (param.node) {
declaration = {
category: SymbolCategory.Parameter,
node: param.node,
path: this._fileInfo.filePath,
range: convertOffsetsToRange(param.node.start, param.node.end, this._fileInfo.lines)
};
}
let typeSourceId = param.node ?
AnalyzerNodeInfo.getTypeSourceId(param.node) :
declaration = {
category: SymbolCategory.Parameter,
node: paramNode,
path: this._fileInfo.filePath,
range: convertOffsetsToRange(paramNode.start, paramNode.end, this._fileInfo.lines)
};
let typeSourceId = paramNode ?
AnalyzerNodeInfo.getTypeSourceId(paramNode) :
DefaultTypeSourceId;
this._bindNameToType(param.name, param.type, typeSourceId, declaration);
}
@ -273,14 +275,12 @@ export class TypeAnalyzer extends ParseTreeWalker {
superType.addParameter({
category: ParameterCategory.VarArgList,
name: 'args',
type: UnknownType.create(),
node: classNode
type: UnknownType.create()
});
superType.addParameter({
category: ParameterCategory.VarArgDictionary,
name: 'kwargs',
type: UnknownType.create(),
node: classNode
type: UnknownType.create()
});
if (classType.getBaseClasses().length > 0) {
let baseClass = classType.getBaseClasses()[0];
@ -363,8 +363,7 @@ export class TypeAnalyzer extends ParseTreeWalker {
if (callType instanceof ClassType && callType.isGeneric()) {
// TODO - need to infer types. For now, just assume "any" type.
let specializedType = callType.cloneForSpecialization();
specializedType.setTypeArguments([]);
let specializedType = callType.cloneForSpecialization([]);
callType = specializedType;
}
@ -533,18 +532,21 @@ export class TypeAnalyzer extends ParseTreeWalker {
if (item.target) {
let exprType = this._getTypeOfExpression(item.expression);
// If the type has an "__enter__" method, it can return
// a type other than its own type.
const enterMethodName = node.isAsync ? '__aenter__' : '__enter__';
let enterTypeMember = TypeUtils.lookUpObjectMember(exprType, enterMethodName);
if (enterTypeMember) {
const memberType = TypeUtils.getEffectiveTypeOfMember(enterTypeMember);
if (memberType instanceof FunctionType) {
if (exprType instanceof ObjectType) {
// If the type has an "__enter__" method, it can return
// a type other than its own type.
const enterMethodName = node.isAsync ? '__aenter__' : '__enter__';
let evaluator = this._getEvaluator();
let memberType = evaluator.getTypeFromClassMemberAccess(
enterMethodName, exprType.getClassType(), false);
if (memberType && memberType instanceof FunctionType) {
exprType = memberType.getEffectiveReturnType();
}
}
this._assignTypeToPossibleTuple(item.target, exprType);
this.walk(item.target);
}
});
@ -1053,12 +1055,12 @@ export class TypeAnalyzer extends ParseTreeWalker {
}
} else if (callType instanceof ObjectType) {
isCallable = false;
const callMethod = TypeUtils.lookUpObjectMember(callType, '__call__');
if (callMethod) {
const callMethodType = TypeUtils.getEffectiveTypeOfMember(callMethod);
if (callMethodType instanceof FunctionType) {
isCallable = this._validateCallArguments(node, callMethodType, true);
}
let evaluator = this._getEvaluator();
let memberType = evaluator.getTypeFromClassMemberAccess(
'__call__', callType.getClassType(), false);
if (memberType && memberType instanceof FunctionType) {
isCallable = this._validateCallArguments(node, memberType, true);
}
} else if (callType instanceof UnionType) {
for (let type of callType.getTypes()) {

View File

@ -7,7 +7,7 @@
*/
import * as assert from 'assert';
import { ParameterCategory, ParseNode } from '../parser/parseNodes';
import { ParameterCategory } from '../parser/parseNodes';
import { InferredType, TypeSourceId } from './inferredType';
import { SymbolTable } from './symbol';
@ -224,10 +224,11 @@ export class ClassType extends Type {
};
}
cloneForSpecialization(): ClassType {
cloneForSpecialization(typeArguments: (Type | Type[])[]): ClassType {
let newClassType = new ClassType(this._classDetails.name,
this._classDetails.flags, this._classDetails.typeSourceId);
newClassType._classDetails = this._classDetails;
newClassType.setTypeArguments(typeArguments);
return newClassType;
}
@ -493,7 +494,6 @@ export interface FunctionParameter {
name?: string;
hasDefault?: boolean;
type: Type;
node?: ParseNode;
}
export enum FunctionTypeFlags {
@ -509,6 +509,7 @@ interface FunctionDetails {
parameters: FunctionParameter[];
declaredReturnType?: Type;
inferredReturnType: InferredType;
builtInName?: string;
}
export class FunctionType extends Type {
@ -516,9 +517,6 @@ export class FunctionType extends Type {
private _functionDetails: FunctionDetails;
// We need to handle certain built-in functions specially.
private _specialBuiltInName: string | undefined;
// A generic function that has been completely or partially
// specialized will have type arguments that correspond to
// some or all of the type parameters. Unspecified type
@ -542,12 +540,12 @@ export class FunctionType extends Type {
return (this._functionDetails.flags & FunctionTypeFlags.ClassMethod) !== 0;
}
getSpecialBuiltInName() {
return this._specialBuiltInName;
getBuiltInName() {
return this._functionDetails.builtInName;
}
setSpecialBuiltInName(name: string) {
this._specialBuiltInName = name;
setBuiltInName(name: string) {
this._functionDetails.builtInName = name;
}
getParameters() {

View File

@ -34,8 +34,7 @@ export class ScopeUtils {
if (nameType instanceof ClassType) {
let classType = nameType;
if (typeArguments) {
classType = classType.cloneForSpecialization();
classType.setTypeArguments(typeArguments);
classType = classType.cloneForSpecialization(typeArguments);
}
return new ObjectType(classType);