Moved more logic from type analyzer to expression evaluator.

Added specialization support for function types.
This commit is contained in:
Eric Traut 2019-03-17 00:06:21 -07:00
parent 18ab06dbe3
commit 42a19a82c5
3 changed files with 531 additions and 417 deletions

View File

@ -7,6 +7,8 @@
* within particular contexts and reports type errors.
*/
import * as assert from 'assert';
import { TextRangeDiagnosticSink } from '../common/diagnosticSink';
import StringMap from '../common/stringMap';
import { TextRange } from '../common/textRange';
@ -25,14 +27,15 @@ import { Scope, ScopeType } from './scope';
import { Symbol, SymbolCategory } from './symbol';
import { TypeConstraint } from './typeConstraint';
import { AnyType, ClassType, ClassTypeFlags, FunctionParameter, FunctionType, FunctionTypeFlags,
ModuleType, NoneType, ObjectType, OverloadedFunctionType, PropertyType, TupleType, Type,
TypeVarType, UnionType, UnknownType } from './types';
ModuleType, NoneType, ObjectType, OverloadedFunctionType, PropertyType, SpecializedFunctionTypes, TupleType,
Type, TypeVarType, UnionType, UnknownType } from './types';
import { TypeUtils } from './typeUtils';
interface TypeResult {
type: Type;
typeList?: TypeResult[];
isClassOrObjectMember?: boolean;
isClassMember?: boolean;
isObjectMember?: boolean;
node: ExpressionNode;
}
@ -43,7 +46,16 @@ export enum EvaluatorFlags {
// Interpret a class type as a instance of that class. This
// is the normal mode used for type annotations.
ConvertClassToObject = 1
ConvertClassToObject = 1,
// Should types like 'Callable' be converted to their internal
// representations even if they have no explicit specialization?
ConvertSpecialTypes = 2
}
interface ParamAssignmentInfo {
argsNeeded: number;
argsReceived: number;
}
export type ReadTypeFromNodeCacheCallback = (node: ExpressionNode) => Type | undefined;
@ -67,7 +79,7 @@ export class ExpressionEvaluator {
}
getType(node: ExpressionNode, flags: EvaluatorFlags): Type {
let typeResult = this._getTypeFromExpression(node, flags);
let typeResult = this._getTypeFromExpression(node, flags | EvaluatorFlags.ConvertSpecialTypes);
return typeResult.type;
}
@ -210,6 +222,11 @@ export class ExpressionEvaluator {
typeResult = this._getTypeFromName(node, flags);
} else if (node instanceof MemberAccessExpressionNode) {
typeResult = this._getTypeFromMemberAccessExpression(node, flags);
// Cache the type information in the member name node as well.
if (this._writeTypeToCache) {
this._writeTypeToCache(node.memberName, typeResult.type);
}
} else if (node instanceof IndexExpressionNode) {
typeResult = this._getTypeFromIndexExpression(node, flags);
} else if (node instanceof CallExpressionNode) {
@ -336,6 +353,14 @@ export class ExpressionEvaluator {
type = UnknownType.create();
}
// If we're not converting to an object, convert classes like
// "Callable" into their internal
if ((flags & EvaluatorFlags.ConvertSpecialTypes) !== 0) {
if (type instanceof ClassType && type.isSpecialBuiltIn()) {
type = this._createSpecializeClassType(type, [], node, flags);
}
}
type = this._convertClassToObject(type, flags);
return { type, node };
@ -355,19 +380,21 @@ export class ExpressionEvaluator {
const memberName = node.memberName.nameToken.value;
let type: Type | undefined;
let isClassOrObjectMember = false;
let isClassMember = false;
let isObjectMember = false;
if (baseType.isAny()) {
type = baseType;
// Assume that the base type is a class or object.
isClassOrObjectMember = true;
isClassMember = true;
isObjectMember = true;
} else if (baseType instanceof ClassType) {
type = this._getTypeFromClassMemberAccess(node.memberName, baseType, false);
isClassOrObjectMember = true;
isClassMember = true;
} else if (baseType instanceof ObjectType) {
type = this._getTypeFromClassMemberAccess(node.memberName, baseType.getClassType(), true);
isClassOrObjectMember = true;
isObjectMember = true;
} else if (baseType instanceof ModuleType) {
let memberInfo = baseType.getFields().get(memberName);
if (memberInfo) {
@ -383,9 +410,21 @@ export class ExpressionEvaluator {
// TODO - ignore None for now.
} else {
let typeResult = this._getTypeFromMemberAccessExpressionWithBaseType(node,
{ type: typeEntry, isClassOrObjectMember: baseTypeResult.isClassOrObjectMember, node },
{
type: typeEntry,
isClassMember: baseTypeResult.isClassMember,
isObjectMember: baseTypeResult.isObjectMember,
node
},
EvaluatorFlags.None);
if (typeResult) {
if (typeResult.isClassMember) {
isClassMember = true;
}
if (typeResult.isObjectMember) {
isObjectMember = true;
}
returnTypes.push(typeResult.type);
}
}
@ -413,7 +452,7 @@ export class ExpressionEvaluator {
type = this._convertClassToObject(type, flags);
return { type, node, isClassOrObjectMember };
return { type, node, isClassMember, isObjectMember };
}
private _getTypeFromClassMemberAccess(memberNameNode: NameNode,
@ -432,7 +471,7 @@ export class ExpressionEvaluator {
// TODO - figure out a better approach here.
if (!classType.hasDecorators()) {
this._addError(
`'${ memberName }' is not a known member of type '${ classType.asString() }'`,
`'${ memberName }' is not a known member of type '${ classType.getObjectName() }'`,
memberNameNode);
}
@ -450,7 +489,9 @@ export class ExpressionEvaluator {
if (baseType.isAny()) {
type = baseType;
} else if (baseType instanceof ClassType) {
type = this._createSpecializeClassType(baseType, node.indexExpression, flags);
let typeArgs = this._getTypeArgs(node.indexExpression);
type = this._createSpecializeClassType(baseType, typeArgs,
node.indexExpression, flags);
} else if (baseType instanceof UnionType) {
// TODO - need to implement
type = UnknownType.create();
@ -499,12 +540,14 @@ export class ExpressionEvaluator {
typeResult = {
type: UnknownType.create(),
typeList: node.entries.map(entry => {
return this._getTypeFromExpression(entry, EvaluatorFlags.ConvertClassToObject);
return this._getTypeFromExpression(entry,
EvaluatorFlags.ConvertClassToObject | EvaluatorFlags.ConvertSpecialTypes);
}),
node
};
} else {
typeResult = this._getTypeFromExpression(node, EvaluatorFlags.ConvertClassToObject);
typeResult = this._getTypeFromExpression(node,
EvaluatorFlags.ConvertClassToObject | EvaluatorFlags.ConvertSpecialTypes);
}
return typeResult;
@ -524,17 +567,23 @@ export class ExpressionEvaluator {
};
}
private _getTypeFromCallExpression(node: CallExpressionNode, flags: EvaluatorFlags): TypeResult {
const baseTypeResult = this._getTypeFromExpression(node.leftExpression, EvaluatorFlags.None);
private _getTypeFromCallExpression(node: CallExpressionNode,
flags: EvaluatorFlags): TypeResult {
return this._getTypeFromCallExpressionWithBaseType(node, baseTypeResult, flags);
const baseTypeResult = this._getTypeFromExpression(
node.leftExpression, EvaluatorFlags.None);
return this._getTypeFromCallExpressionWithBaseType(
node, baseTypeResult, flags);
}
private _getTypeFromCallExpressionWithBaseType(node: CallExpressionNode, baseTypeResult: TypeResult,
flags: EvaluatorFlags): TypeResult {
private _getTypeFromCallExpressionWithBaseType(node: CallExpressionNode,
baseTypeResult: TypeResult, flags: EvaluatorFlags): TypeResult {
let type: Type | undefined;
const callType = baseTypeResult.type;
const isClassMemberAccess = !!baseTypeResult.isClassMember;
const isObjectMemberAccess = !!baseTypeResult.isObjectMember;
if (callType instanceof ClassType) {
if (callType.isBuiltIn()) {
@ -556,43 +605,58 @@ export class ExpressionEvaluator {
type = UnknownType.create();
}
} else if (className === 'TypeVar') {
type = this.createTypeVarType(node);
type = this._createTypeVarType(node);
} else if (className === 'NamedTuple') {
type = this.createNamedTupleType(node, true);
type = this._createNamedTupleType(node, true);
flags &= ~EvaluatorFlags.ConvertClassToObject;
}
}
// Assume this is a call to the constructor.
if (!type) {
type = new ObjectType(callType);
if (this._validateConstructorArguments(node, callType)) {
type = new ObjectType(callType);
}
}
} 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.getBuiltInName() === 'namedtuple') {
type = this.createNamedTupleType(node, false);
type = this._createNamedTupleType(node, false);
flags &= ~EvaluatorFlags.ConvertClassToObject;
} else {
type = callType.getEffectiveReturnType();
const isClassMethod = callType.isClassMethod();
const isInstanceMethod = callType.isInstanceMethod();
let skipFirstMethodParam = (isObjectMemberAccess && isInstanceMethod) ||
((isClassMemberAccess || isObjectMemberAccess) && isClassMethod);
if (this._validateCallArguments(node, callType, skipFirstMethodParam)) {
type = callType.getEffectiveReturnType();
}
}
} else if (callType instanceof OverloadedFunctionType) {
// Determine which of the overloads (if any) match.
// let skipFirstMethodParam = !!baseTypeResult.isClassOrObjectMember;
let skipFirstMethodParam = isClassMemberAccess || isObjectMemberAccess;
// Determine which of the overloads (if any) match.
let functionType = this._findOverloadedFunctionType(
callType, node, skipFirstMethodParam);
if (functionType) {
type = functionType.getEffectiveReturnType();
} else {
const exprString = ParseTreeUtils.printExpression(node.leftExpression);
this._addError(
`No overloads for '${ exprString }' match parameters`,
node.leftExpression);
}
// TODO - need to figure out what to do here.
// let functionType = this._findOverloadedFunctionType(
// callType, node, skipFirstMethodParam);
// if (functionType) {
// type = functionType.getEffectiveReturnType();
// }
type = UnknownType.create();
} else if (callType instanceof ObjectType) {
const callMember = TypeUtils.lookUpObjectMember(callType, '__call__');
if (callMember) {
const callMethodType = TypeUtils.getEffectiveTypeOfMember(callMember);
if (callMethodType instanceof FunctionType) {
type = callMethodType.getEffectiveReturnType();
let memberType = this.getTypeFromClassMemberAccess(
'__call__', callType.getClassType(), false);
if (memberType && memberType instanceof FunctionType) {
if (this._validateCallArguments(node, memberType, true)) {
type = memberType.getEffectiveReturnType();
}
}
} else if (callType instanceof UnionType) {
@ -602,7 +666,12 @@ export class ExpressionEvaluator {
// TODO - ignore None for now.
} else {
let typeResult = this._getTypeFromCallExpressionWithBaseType(node,
{ type: typeEntry, isClassOrObjectMember: baseTypeResult.isClassOrObjectMember, node },
{
type: typeEntry,
isClassMember: baseTypeResult.isClassMember,
isObjectMember: baseTypeResult.isObjectMember,
node
},
EvaluatorFlags.None);
if (typeResult) {
returnTypes.push(typeResult.type);
@ -618,7 +687,10 @@ export class ExpressionEvaluator {
}
if (type === undefined) {
this._addError(`'Unsupported type expression: call`, node);
this._addError(
`'${ ParseTreeUtils.printExpression(node.leftExpression) }' has type ` +
`'${ callType.asString() }' and is not callable`,
node.leftExpression);
type = UnknownType.create();
}
@ -627,7 +699,278 @@ export class ExpressionEvaluator {
return { type, node };
}
createTypeVarType(node: CallExpressionNode): Type | undefined {
private _findOverloadedFunctionType(callType: OverloadedFunctionType,
node: CallExpressionNode, skipFirstMethodParam: boolean): FunctionType | undefined {
let validOverload: FunctionType | undefined;
// Create a new evaluator that will not emit any diagnostics.
let altEvaluator = new ExpressionEvaluator(this._scope, this._expressionTypeConstraints);
for (let overload of callType.getOverloads()) {
if (altEvaluator._validateCallArguments(node, overload.type, skipFirstMethodParam)) {
validOverload = overload.type;
}
}
return validOverload;
}
// Tries to match the arguments of a call to the constructor for a class.
private _validateConstructorArguments(node: CallExpressionNode, type: ClassType): boolean {
let isValid = false;
let validatedTypes = false;
let initMethodType = this.getTypeFromClassMemberAccess('__init__', type, false);
if (initMethodType) {
isValid = this._validateCallArguments(node, initMethodType, true);
validatedTypes = true;
}
if (!validatedTypes) {
// If there's no init method, check for a constructor.
let constructorMethodType = this.getTypeFromClassMemberAccess('__new__', type, false);
if (constructorMethodType) {
isValid = this._validateCallArguments(node, constructorMethodType, true);
validatedTypes = true;
}
}
if (!validatedTypes && node.arguments.length > 0) {
this._addError(
`Expected no arguments to '${ type.getClassName() }' constructor`, node);
}
return isValid;
}
private _validateCallArguments(node: CallExpressionNode, callType: Type,
skipFirstMethodParam: boolean): boolean {
let isCallable = true;
if (callType.isAny()) {
// Nothing to do in this case.
} else if (callType instanceof FunctionType) {
this._validateFunctionArguments(node, callType, skipFirstMethodParam);
} else if (callType instanceof OverloadedFunctionType) {
if (!this._findOverloadedFunctionType(callType, node, skipFirstMethodParam)) {
const exprString = ParseTreeUtils.printExpression(node.leftExpression);
this._addError(
`No overloads for '${ exprString }' match parameters`,
node.leftExpression);
}
} else if (callType instanceof ClassType) {
if (!callType.isSpecialBuiltIn()) {
this._validateConstructorArguments(node, callType);
}
} else if (callType instanceof ObjectType) {
isCallable = false;
let memberType = this.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()) {
if (type instanceof NoneType) {
// TODO - for now, assume that optional
// types (unions with None) are valid. Tighten
// this later.
} else if (!this._validateCallArguments(node, type, skipFirstMethodParam)) {
isCallable = false;
break;
}
}
} else {
isCallable = false;
}
return isCallable;
}
// Tries to assign the call arguments to the function parameter
// list and reports any mismatches in types or counts.
// If skipFirstMethodParam is true and the callee is a method,
// the logic assumes that it can skip the validation of the first
// parameter because it's a "self" or "cls" parameter.
// This logic is based on PEP 3102: https://www.python.org/dev/peps/pep-3102/
private _validateFunctionArguments(node: CallExpressionNode, type: FunctionType,
skipFirstMethodParam: boolean) {
let argIndex = 0;
const typeParams = type.getParameters();
// If it's a raw function (versus a method call), no need to skip the first parameter.
const skipFirstParam = skipFirstMethodParam &&
(type.isInstanceMethod() || type.isClassMethod());
// Evaluate all of the argument values and generate errors if appropriate.
// The expression type will be cached in the node so we don't re-evaluate
// it below.
node.arguments.forEach(arg => {
this.getType(arg.valueExpression, EvaluatorFlags.None);
});
// If the function has decorators, we need to back off because the decorator
// parameter lists may differ from those of the function.
// TODO - improve this
if (type.hasCustomDecorators()) {
return;
}
// The last parameter might be a var arg dictionary. If so, strip it off.
let hasVarArgDictParam = typeParams.find(
param => param.category === ParameterCategory.VarArgDictionary) !== undefined;
let reportedArgError = false;
// Build a map of parameters by name.
let paramMap = new StringMap<ParamAssignmentInfo>();
typeParams.forEach((param, index) => {
// Skip the first named param if appropriate.
if (param.name && (index > 0 || !skipFirstParam)) {
paramMap.set(param.name, {
argsNeeded: param.category === ParameterCategory.Simple && !param.hasDefault ? 1 : 0,
argsReceived: 0
});
}
});
// Is there a bare (nameless) "*" parameter? If so, it signifies the end
// of the positional parameter list.
let positionalParamCount = typeParams.findIndex(
param => param.category === ParameterCategory.VarArgList && !param.name);
// Is there a var-arg (named "*") parameter? If so, it is the last of
// the positional parameters.
if (positionalParamCount < 0) {
positionalParamCount = typeParams.findIndex(
param => param.category === ParameterCategory.VarArgList);
if (positionalParamCount >= 0) {
positionalParamCount++;
}
}
// Is there a keyword var-arg ("**") parameter? If so, it's not included
// in the list of positional parameters.
if (positionalParamCount < 0) {
positionalParamCount = typeParams.findIndex(
param => param.category === ParameterCategory.VarArgDictionary);
}
// If we didn't see any special cases, then all parameters are positional.
if (positionalParamCount < 0) {
positionalParamCount = typeParams.length;
}
// Determine how many positional args are being passed before
// we see a named arg.
let positionalArgCount = node.arguments.findIndex(
arg => arg.argumentCategory === ArgumentCategory.Dictionary || arg.name !== undefined);
if (positionalArgCount < 0) {
positionalArgCount = node.arguments.length;
}
// Map the positional args to parameters.
let paramIndex = skipFirstParam ? 1 : 0;
while (argIndex < positionalArgCount) {
if (paramIndex >= positionalParamCount) {
let adjustedCount = positionalParamCount;
if (skipFirstParam) {
adjustedCount--;
}
this._addError(
`Expected ${ adjustedCount } positional argument${ adjustedCount === 1 ? '' : 's' }`,
node.arguments[argIndex]);
reportedArgError = true;
break;
}
if (typeParams[paramIndex].category === ParameterCategory.VarArgList) {
// Consume the remaining positional args.
argIndex = positionalArgCount;
} else {
let paramType = type.getEffectiveParameterType(paramIndex);
this._validateArgType(paramType, node.arguments[argIndex].valueExpression);
// Note that the parameter has received an argument.
const paramName = typeParams[paramIndex].name;
if (paramName) {
paramMap.get(paramName)!.argsReceived++;
}
argIndex++;
}
paramIndex++;
}
if (!reportedArgError) {
let foundDictionaryArg = false;
let foundListArg = node.arguments.find(arg => arg.argumentCategory === ArgumentCategory.List) !== undefined;
// Now consume any named parameters.
while (argIndex < node.arguments.length) {
if (node.arguments[argIndex].argumentCategory === ArgumentCategory.Dictionary) {
foundDictionaryArg = true;
} else {
// Protect against the case where a non-named argument appears after
// a named argument. This will have already been reported as a parse
// error, but we need to protect against it here.
const paramName = node.arguments[argIndex].name;
if (paramName) {
const paramNameValue = paramName.nameToken.value;
const paramEntry = paramMap.get(paramNameValue);
if (paramEntry) {
if (paramEntry.argsReceived > 0) {
this._addError(
`Parameter '${ paramNameValue }' is already assigned`, paramName);
} else {
paramMap.get(paramName.nameToken.value)!.argsReceived++;
let paramInfoIndex = typeParams.findIndex(param => param.name === paramNameValue);
assert(paramInfoIndex >= 0);
const paramType = type.getEffectiveParameterType(paramInfoIndex);
this._validateArgType(paramType, node.arguments[argIndex].valueExpression);
}
} else if (!hasVarArgDictParam) {
this._addError(
`No parameter named '${ paramName.nameToken.value }'`, paramName);
}
}
}
argIndex++;
}
// Determine whether there are any parameters that require arguments
// but have not yet received them. If we received a dictionary argument
// (i.e. an arg starting with a "**") or a list argument (i.e. an arg
// starting with a "*"), we will assume that all parameters are matched.
if (!foundDictionaryArg && !foundListArg) {
let unassignedParams = paramMap.getKeys().filter(name => {
const entry = paramMap.get(name)!;
return entry.argsReceived < entry.argsNeeded;
});
if (unassignedParams.length > 0) {
this._addError(
`Argument missing for parameter${ unassignedParams.length === 1 ? '' : 's' } ` +
unassignedParams.map(p => `${ p }`).join(', '), node);
}
}
}
}
private _validateArgType(paramType: Type, argExpression: ExpressionNode) {
let argType = this.getType(argExpression, EvaluatorFlags.None);
if (!TypeUtils.canAssignType(paramType, argType)) {
this._addError(
`Argument of type '${ argType.asString() }'` +
` cannot be assigned to parameter of type '${ paramType.asString() }'`,
argExpression);
}
}
private _createTypeVarType(node: CallExpressionNode): Type | undefined {
let typeVarName = '';
if (node.arguments.length === 0) {
this._addError('Expected name of type var', node);
@ -713,7 +1056,7 @@ export class ExpressionEvaluator {
// Creates a new custom tuple factory class with named values.
// Supports both typed and untyped variants.
createNamedTupleType(node: CallExpressionNode, includesTypes: boolean): ClassType {
private _createNamedTupleType(node: CallExpressionNode, includesTypes: boolean): ClassType {
let className = 'namedtuple';
if (node.arguments.length === 0) {
this._addError('Expected named tuple class name as firat parameter',
@ -857,6 +1200,11 @@ export class ExpressionEvaluator {
}
}
if (addGenericGetAttribute) {
TypeUtils.addDefaultFunctionParameters(constructorType);
TypeUtils.addDefaultFunctionParameters(initType);
}
classFields.set('__new__', new Symbol(constructorType, DefaultTypeSourceId));
classFields.set('__init__', new Symbol(initType, DefaultTypeSourceId));
@ -1146,10 +1494,8 @@ export class ExpressionEvaluator {
// the specified type arguments, reporting errors as appropriate.
// Returns the specialized type and a boolean indicating whether
// the type indiciates a class type (true) or an object type (false).
private _createSpecializeClassType(classType: ClassType,
typeArgNode: ExpressionNode, flags: EvaluatorFlags): Type {
let typeArgs = this._getTypeArgs(typeArgNode);
private _createSpecializeClassType(classType: ClassType, typeArgs: TypeResult[],
errorNode: ExpressionNode, flags: EvaluatorFlags): Type {
// Handle the special-case classes that are not defined
// in the type stubs.
@ -1162,11 +1508,11 @@ export class ExpressionEvaluator {
}
case 'Optional': {
return this._createOptional(typeArgNode, typeArgs);
return this._createOptional(errorNode, typeArgs);
}
case 'Type': {
return this._createTypeType(typeArgNode, typeArgs);
return this._createTypeType(errorNode, typeArgs);
}
case 'ClassVar':
@ -1194,16 +1540,16 @@ export class ExpressionEvaluator {
case 'Generic':
if (flags & EvaluatorFlags.ConvertClassToObject) {
this._addError(`Generic allowed only as base class`, typeArgNode);
this._addError(`Generic allowed only as base class`, errorNode);
}
return this._createGenericType(typeArgNode, classType, typeArgs);
return this._createGenericType(errorNode, classType, typeArgs);
}
}
if (classType === ScopeUtils.getBuiltInType(this._scope, 'type')) {
// The built-in 'type' class isn't defined as a generic class.
// It needs to be special-cased here.
return this._createTypeType(typeArgNode, typeArgs);
return this._createTypeType(errorNode, typeArgs);
}
let specializedType = this._createSpecializedClassType(classType, typeArgs);
@ -1232,7 +1578,12 @@ export class ExpressionEvaluator {
// Specializes a (potentially generic) type by substituting
// type variables with specified types.
private _specializeType(type: Type, typeVarMap: TypeVarMap): Type {
private _specializeType(type: Type, typeVarMap: TypeVarMap, recursionLevel = 0): Type {
// Prevent infinite recursion in case a type refers to itself.
if (recursionLevel > 100) {
return AnyType.create();
}
if (type.isAny()) {
return type;
}
@ -1253,19 +1604,25 @@ export class ExpressionEvaluator {
if (type instanceof UnionType) {
let subtypes: Type[] = [];
type.getTypes().forEach(typeEntry => {
subtypes.push(this._specializeType(typeEntry, typeVarMap));
subtypes.push(this._specializeType(typeEntry, typeVarMap, recursionLevel + 1));
});
return TypeUtils.combineTypesArray(subtypes);
}
if (type instanceof ObjectType) {
const classType = this._specializeClassType(type.getClassType(), typeVarMap);
const classType = this._specializeClassType(type.getClassType(),
typeVarMap, recursionLevel + 1);
// Don't allocate a new ObjectType class if the class
// didn't need to be specialized.
if (classType === type.getClassType()) {
return type;
}
return new ObjectType(classType);
}
if (type instanceof ClassType) {
return this._specializeClassType(type, typeVarMap);
return this._specializeClassType(type, typeVarMap, recursionLevel + 1);
}
if (type instanceof TupleType) {
@ -1274,15 +1631,16 @@ export class ExpressionEvaluator {
}
if (type instanceof FunctionType) {
// TODO - need to implement
return type;
return this._specializeFunctionType(type, typeVarMap, recursionLevel + 1);
}
// TODO - need to implement
return type;
}
private _specializeClassType(classType: ClassType, typeVarMap: TypeVarMap): ClassType {
private _specializeClassType(classType: ClassType, typeVarMap: TypeVarMap,
recursionLevel: number): ClassType {
// Handle the common case where the class has no type parameters.
if (classType.getTypeParameters().length === 0) {
return classType;
@ -1290,6 +1648,7 @@ export class ExpressionEvaluator {
const oldTypeArgs = classType.getTypeArguments();
let newTypeArgs: Type[] = [];
let specializationNeeded = false;
classType.getTypeParameters().forEach((typeParam, index) => {
let typeArgType: Type;
@ -1299,19 +1658,63 @@ export class ExpressionEvaluator {
if (oldTypeArgs) {
if (index >= oldTypeArgs.length) {
typeArgType = AnyType.create();
specializationNeeded = true;
} else {
typeArgType = this._specializeType(oldTypeArgs[index] as Type, typeVarMap);
typeArgType = this._specializeType(oldTypeArgs[index] as Type,
typeVarMap, recursionLevel + 1);
if (typeArgType !== oldTypeArgs[index] as Type) {
specializationNeeded = true;
}
}
} else {
typeArgType = this._specializeTypeVarType(typeParam);
if (typeArgType !== typeParam) {
specializationNeeded = true;
}
}
newTypeArgs.push(typeArgType);
});
// If specialization wasn't needed, don't allocate a new class.
if (!specializationNeeded) {
return classType;
}
return classType.cloneForSpecialization(newTypeArgs);
}
private _specializeFunctionType(functionType: FunctionType,
typeVarMap: TypeVarMap, recursionLevel: number): FunctionType {
const returnType = functionType.getEffectiveReturnType();
const specializedReturnType = this._specializeType(returnType,
typeVarMap, recursionLevel + 1);
let typesRequiredSpecialization = returnType !== specializedReturnType;
let specializedParameters: SpecializedFunctionTypes = {
parameterTypes: [],
returnType: specializedReturnType
};
for (let i = 0; i < functionType.getParameterCount(); i++) {
const paramType = functionType.getEffectiveParameterType(i);
const specializedType = this._specializeType(paramType,
typeVarMap, recursionLevel + 1);
specializedParameters.parameterTypes.push(specializedType);
if (paramType !== specializedType) {
typesRequiredSpecialization = true;
}
}
if (!typesRequiredSpecialization) {
return functionType;
}
return functionType.cloneForSpecialization(specializedParameters);
}
private _convertClassToObject(type: Type, flags: EvaluatorFlags): Type {
if (flags & EvaluatorFlags.ConvertClassToObject) {
if (type instanceof ClassType) {

View File

@ -66,9 +66,6 @@ export class TypeAnalyzer extends ParseTreeWalker {
// from a previous pass.
private _analysisVersion = 0;
// Temporarily suppress the output of diagnostics?
private _isDiagnosticsSuppressed = false;
// List of type constraints that are currently in effect
// when walking a multi-part AND expression (e.g. A and B
// and C).
@ -185,7 +182,9 @@ export class TypeAnalyzer extends ParseTreeWalker {
}
}
functionParams[index].type = annotatedType;
if (functionType.setParameterType(index, annotatedType)) {
this._setAnalysisChanged();
}
if (param.defaultValue) {
// Verify that the default value matches the type annotation.
@ -205,9 +204,13 @@ export class TypeAnalyzer extends ParseTreeWalker {
let inferredClassType = AnalyzerNodeInfo.getExpressionType(classNode) as ClassType;
if (inferredClassType) {
if (param.name.nameToken.value === 'self') {
functionParams[index].type = new ObjectType(inferredClassType);
if (functionType.setParameterType(index, new ObjectType(inferredClassType))) {
this._setAnalysisChanged();
}
} else if (param.name.nameToken.value === 'cls') {
functionParams[index].type = inferredClassType;
if (functionType.setParameterType(index, inferredClassType)) {
this._setAnalysisChanged();
}
}
}
}
@ -359,22 +362,9 @@ export class TypeAnalyzer extends ParseTreeWalker {
}
visitCall(node: CallExpressionNode): boolean {
let callType = this._getTypeOfExpression(node.leftExpression);
if (callType instanceof ClassType && callType.isGeneric()) {
// TODO - need to infer types. For now, just assume "any" type.
let specializedType = callType.cloneForSpecialization([]);
callType = specializedType;
}
// TODO - need to handle union type
if (!this._validateCallArguments(node, callType, this._isCallOnObjectOrClass(node))) {
this._addError(
`'${ ParseTreeUtils.printExpression(node.leftExpression) }' has type ` +
`'${ callType.asString() }' and is not callable`,
node.leftExpression);
}
// Calculate and cache the expression and report
// any validation errors.
this._getTypeOfExpression(node);
return true;
}
@ -775,14 +765,13 @@ export class TypeAnalyzer extends ParseTreeWalker {
}
visitMemberAccess(node: MemberAccessExpressionNode) {
let leftType = this._getTypeOfExpression(node.leftExpression);
this._validateMemberAccess(leftType, node.memberName);
this._getTypeOfExpression(node);
this._setDefinitionForMemberName(
this._getTypeOfExpression(node.leftExpression), node.memberName);
// Walk the leftExpression but not the memberName.
this.walk(node.leftExpression);
// Set the member type for the hover provider.
this._updateExpressionTypeForNode(node.memberName, this._getTypeOfExpression(node));
return false;
}
@ -1013,90 +1002,6 @@ export class TypeAnalyzer extends ParseTreeWalker {
return false;
}
// Determine if this is a call through an object or class, in
// which case a "self" or "cls" argument needs to be synthesized.
private _isCallOnObjectOrClass(node: CallExpressionNode): boolean {
let skipFirstMethodParam = false;
if (node.leftExpression instanceof MemberAccessExpressionNode) {
let leftType = this._getTypeOfExpression(
node.leftExpression.leftExpression);
// TODO - what should we do about UnionType here?
if (leftType instanceof ObjectType || leftType instanceof ClassType) {
skipFirstMethodParam = true;
}
}
return skipFirstMethodParam;
}
private _validateCallArguments(node: CallExpressionNode, callType: Type,
skipFirstMethodParam: boolean): boolean {
let isCallable = true;
if (callType instanceof TypeVarType) {
// TODO - need to remove once we resolve type vars
return true;
}
if (!callType.isAny()) {
if (callType instanceof FunctionType) {
this._validateFunctionArguments(node, callType, skipFirstMethodParam);
} else if (callType instanceof OverloadedFunctionType) {
if (!this._findOverloadedFunctionType(callType, node, skipFirstMethodParam)) {
const exprString = ParseTreeUtils.printExpression(node.leftExpression);
this._addError(
`No overloads for '${ exprString }' match parameters`,
node.leftExpression);
}
} else if (callType instanceof ClassType) {
if (!callType.isSpecialBuiltIn()) {
this._validateConstructorArguments(node, callType);
}
} else if (callType instanceof ObjectType) {
isCallable = false;
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()) {
if (type instanceof NoneType) {
// TODO - for now, assume that optional
// types (unions with None) are valid. Tighten
// this later.
} else if (!this._validateCallArguments(node, type, skipFirstMethodParam)) {
isCallable = false;
break;
}
}
} else {
isCallable = false;
}
}
return isCallable;
}
private _findOverloadedFunctionType(callType: OverloadedFunctionType,
node: CallExpressionNode, skipFirstMethodParam: boolean): FunctionType | undefined {
let validOverload: FunctionType | undefined;
// Temporarily suppress diagnostics.
this._suppressDiagnostics(() => {
for (let overload of callType.getOverloads()) {
if (this._validateCallArguments(node, overload.type, skipFirstMethodParam)) {
validOverload = overload.type;
}
}
});
return validOverload;
}
private _validateExceptionType(exceptionType: Type, errorNode: ParseNode) {
if (exceptionType.isAny()) {
return exceptionType;
@ -1260,6 +1165,7 @@ export class TypeAnalyzer extends ParseTreeWalker {
if (!oldType || !oldType.isSame(exprType)) {
AnalyzerNodeInfo.setExpressionType(node, exprType);
this._setAnalysisChanged();
}
}
@ -1500,220 +1406,7 @@ export class TypeAnalyzer extends ParseTreeWalker {
return undefined;
}
// Tries to assign the call arguments to the function parameter
// list and reports any mismatches in types or counts.
// If skipFirstMethodParam is true and the callee is a method,
// the logic assumes that it can skip the validation of the first
// parameter because it's a "self" or "cls" parameter.
// This logic is based on PEP 3102: https://www.python.org/dev/peps/pep-3102/
private _validateFunctionArguments(node: CallExpressionNode, type: FunctionType,
skipFirstMethodParam: boolean) {
let argIndex = 0;
const typeParams = type.getParameters();
// If it's a raw function (versus a method call), no need to skip the first parameter.
const skipFirstParam = skipFirstMethodParam && type.isInstanceMethod();
// Evaluate all of the argument values and generate errors if appropriate.
// The expression type will be cached in the node so we don't re-evaluate
// it below.
node.arguments.forEach(arg => {
this._getTypeOfExpression(arg.valueExpression);
});
// If the function has decorators, we need to back off because the decorator
// parameter lists may differ from those of the function.
// TODO - improve this
if (type.hasCustomDecorators()) {
return;
}
// The last parameter might be a var arg dictionary. If so, strip it off.
let hasVarArgDictParam = typeParams.find(
param => param.category === ParameterCategory.VarArgDictionary) !== undefined;
let reportedArgError = false;
// Build a map of parameters by name.
let paramMap = new StringMap<ParamAssignmentInfo>();
typeParams.forEach((param, index) => {
// Skip the first named param if appropriate.
if (param.name && (index > 0 || !skipFirstParam)) {
paramMap.set(param.name, {
argsNeeded: param.category === ParameterCategory.Simple && !param.hasDefault ? 1 : 0,
argsReceived: 0
});
}
});
// Is there a bare (nameless) "*" parameter? If so, it signifies the end
// of the positional parameter list.
let positionalParamCount = typeParams.findIndex(
param => param.category === ParameterCategory.VarArgList && !param.name);
// Is there a var-arg (named "*") parameter? If so, it is the last of
// the positional parameters.
if (positionalParamCount < 0) {
positionalParamCount = typeParams.findIndex(
param => param.category === ParameterCategory.VarArgList);
if (positionalParamCount >= 0) {
positionalParamCount++;
}
}
// Is there a keyword var-arg ("**") parameter? If so, it's not included
// in the list of positional parameters.
if (positionalParamCount < 0) {
positionalParamCount = typeParams.findIndex(
param => param.category === ParameterCategory.VarArgDictionary);
}
// If we didn't see any special cases, then all parameters are positional.
if (positionalParamCount < 0) {
positionalParamCount = typeParams.length;
}
// Determine how many positional args are being passed before
// we see a named arg.
let positionalArgCount = node.arguments.findIndex(
arg => arg.argumentCategory === ArgumentCategory.Dictionary || arg.name !== undefined);
if (positionalArgCount < 0) {
positionalArgCount = node.arguments.length;
}
// Map the positional args to parameters.
let paramIndex = skipFirstParam ? 1 : 0;
while (argIndex < positionalArgCount) {
if (paramIndex >= positionalParamCount) {
this._addError(
`Expected ${ positionalParamCount } positional argument${ positionalParamCount === 1 ? '' : 's' }`,
node.arguments[argIndex]);
reportedArgError = true;
break;
}
if (typeParams[paramIndex].category === ParameterCategory.VarArgList) {
// Consume the remaining positional args.
argIndex = positionalArgCount;
} else {
let paramType = typeParams[paramIndex].type;
this._validateArgType(paramType, node.arguments[argIndex].valueExpression);
// Note that the parameter has received an argument.
const paramName = typeParams[paramIndex].name;
if (paramName) {
paramMap.get(paramName)!.argsReceived++;
}
argIndex++;
}
paramIndex++;
}
if (!reportedArgError) {
let foundDictionaryArg = false;
let foundListArg = node.arguments.find(arg => arg.argumentCategory === ArgumentCategory.List) !== undefined;
// Now consume any named parameters.
while (argIndex < node.arguments.length) {
if (node.arguments[argIndex].argumentCategory === ArgumentCategory.Dictionary) {
foundDictionaryArg = true;
} else {
// Protect against the case where a non-named argument appears after
// a named argument. This will have already been reported as a parse
// error, but we need to protect against it here.
const paramName = node.arguments[argIndex].name;
if (paramName) {
const paramNameValue = paramName.nameToken.value;
const paramEntry = paramMap.get(paramNameValue);
if (paramEntry) {
if (paramEntry.argsReceived > 0) {
this._addError(
`Parameter '${ paramNameValue }' is already assigned`, paramName);
} else {
paramMap.get(paramName.nameToken.value)!.argsReceived++;
let paramInfo = typeParams.find(param => param.name === paramNameValue);
assert(paramInfo !== undefined);
this._validateArgType(paramInfo!.type, node.arguments[argIndex].valueExpression);
this._updateExpressionTypeForNode(paramName, paramInfo!.type);
}
} else if (!hasVarArgDictParam) {
this._addError(
`No parameter named '${ paramName.nameToken.value }'`, paramName);
}
}
}
argIndex++;
}
// Determine whether there are any parameters that require arguments
// but have not yet received them. If we received a dictionary argument
// (i.e. an arg starting with a "**") or a list argument (i.e. an arg
// starting with a "*"), we will assume that all parameters are matched.
if (!foundDictionaryArg && !foundListArg) {
let unassignedParams = paramMap.getKeys().filter(name => {
const entry = paramMap.get(name)!;
return entry.argsReceived < entry.argsNeeded;
});
if (unassignedParams.length > 0) {
this._addError(
`Argument missing for parameter${ unassignedParams.length === 1 ? '' : 's' } ` +
unassignedParams.map(p => `${ p }`).join(', '), node);
}
}
}
}
private _validateArgType(paramType: Type, argExpression: ExpressionNode) {
let argType = this._getTypeOfExpression(argExpression);
if (!TypeUtils.canAssignType(paramType, argType)) {
this._addError(
`Argument of type '${ argType.asString() }'` +
` cannot be assigned to parameter of type '${ paramType.asString() }'`,
argExpression);
}
}
// Tries to match the arguments of a call to the constructor for a class.
private _validateConstructorArguments(node: CallExpressionNode, type: ClassType) {
let validatedTypes = false;
const initMethodMember = TypeUtils.lookUpClassMember(type, '__init__', false);
if (initMethodMember) {
if (initMethodMember.symbol) {
const initMethodType = TypeUtils.getEffectiveTypeOfMember(initMethodMember);
this._validateCallArguments(node, initMethodType, true);
} else {
// If we received a defined result with no symbol, that
// means one of the base classes was an "any" type, so
// we don't know if it has a valid intializer.
}
validatedTypes = true;
}
if (!validatedTypes) {
// If there's no init method, check for a constructor.
const constructorMember = TypeUtils.lookUpClassMember(type, '__new__', false);
if (constructorMember && constructorMember.symbol) {
const constructorMethodType = TypeUtils.getEffectiveTypeOfMember(constructorMember);
this._validateCallArguments(node, constructorMethodType, true);
validatedTypes = true;
}
}
if (!validatedTypes && node.arguments.length > 0) {
this._addError(
`Expected no arguments to '${ type.getClassName() }' constructor`, node);
}
}
private _validateMemberAccess(baseType: Type, memberName: NameNode): void {
// TODO - most of this logic is now redudnant with the expression evaluation
// logic. The only part that remains is the calls to setDeclaration. Clean
// this up at some point.
private _setDefinitionForMemberName(baseType: Type, memberName: NameNode): void {
const memberNameValue = memberName.nameToken.value;
if (baseType instanceof ObjectType) {
@ -1741,15 +1434,8 @@ export class TypeAnalyzer extends ParseTreeWalker {
}
if (baseType instanceof UnionType) {
// TODO - need to add extra logic to determine whether it's safe
// to simplfy the type at this point in the program.
let simplifiedType = baseType.removeOptional();
if (simplifiedType instanceof UnionType) {
for (let t of simplifiedType.getTypes()) {
this._validateMemberAccess(t, memberName);
}
} else {
this._validateMemberAccess(simplifiedType, memberName);
for (let t of baseType.getTypes()) {
this._setDefinitionForMemberName(t, memberName);
}
}
}
@ -1822,19 +1508,9 @@ export class TypeAnalyzer extends ParseTreeWalker {
return newScope!;
}
private _suppressDiagnostics(callback: () => void) {
// Temporarily suppress diagnostics.
let prevSuppressDiagnostics = this._isDiagnosticsSuppressed;
this._isDiagnosticsSuppressed = true;
callback();
this._isDiagnosticsSuppressed = prevSuppressDiagnostics;
}
private _addError(message: string, textRange: TextRange) {
// Don't emit error if the scope is guaranteed not to be executed.
if (!this._currentScope.isNotExecuted() && !this._isDiagnosticsSuppressed) {
if (!this._currentScope.isNotExecuted()) {
this._fileInfo.diagnosticSink.addErrorWithTextRange(message, textRange);
}
}
@ -1855,7 +1531,7 @@ export class TypeAnalyzer extends ParseTreeWalker {
// If the current scope isn't executed, create a dummy sink
// for any errors that are reported.
if (this._currentScope.isNotExecuted() && !this._isDiagnosticsSuppressed) {
if (this._currentScope.isNotExecuted()) {
diagSink = undefined;
}

View File

@ -7,6 +7,7 @@
*/
import * as assert from 'assert';
import { ParameterCategory } from '../parser/parseNodes';
import { InferredType, TypeSourceId } from './inferredType';
import { SymbolTable } from './symbol';
@ -176,7 +177,7 @@ export enum ClassTypeFlags {
// Class is defined in the "builtins" or "typings" file.
BuiltInClass = 0x02,
// CLass requires special-case handling because it
// Class requires special-case handling because it
// exhibits non-standard behavior or is not defined
// formally as a class. Examples include 'Optional'
// and 'Union'.
@ -512,16 +513,19 @@ interface FunctionDetails {
builtInName?: string;
}
export interface SpecializedFunctionTypes {
parameterTypes: Type[];
returnType: Type;
}
export class FunctionType extends Type {
category = TypeCategory.Function;
private _functionDetails: FunctionDetails;
// 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
// parameters are undefined.
// private _typeArguments?: (Type | undefined)[];
// A function type can be specialized (i.e. generic type
// variables replaced by a concrete type).
private _specializedTypes?: SpecializedFunctionTypes;
constructor(flags: FunctionTypeFlags) {
super();
@ -532,6 +536,16 @@ export class FunctionType extends Type {
};
}
cloneForSpecialization(specializedTypes: SpecializedFunctionTypes): FunctionType {
let newFunction = new FunctionType(this._functionDetails.flags);
newFunction._functionDetails = this._functionDetails;
assert(specializedTypes.parameterTypes.length === this._functionDetails.parameters.length);
newFunction._specializedTypes = specializedTypes;
return newFunction;
}
isInstanceMethod(): boolean {
return (this._functionDetails.flags & FunctionTypeFlags.InstanceMethod) !== 0;
}
@ -552,8 +566,24 @@ export class FunctionType extends Type {
return this._functionDetails.parameters;
}
setParameters(params: FunctionParameter[]) {
this._functionDetails.parameters = params;
getParameterCount() {
return this._functionDetails.parameters.length;
}
setParameterType(index: number, type: Type): boolean {
assert(index < this._functionDetails.parameters.length);
const typeChanged = !type.isSame(this._functionDetails.parameters[index].type);
this._functionDetails.parameters[index].type = type;
return typeChanged;
}
getEffectiveParameterType(index: number): Type {
assert(index < this._functionDetails.parameters.length);
if (this._specializedTypes) {
return this._specializedTypes.parameterTypes[index];
}
return this._functionDetails.parameters[index].type;
}
addParameter(param: FunctionParameter) {
@ -577,6 +607,10 @@ export class FunctionType extends Type {
}
getEffectiveReturnType() {
if (this._specializedTypes) {
return this._specializedTypes.returnType;
}
if (this._functionDetails.declaredReturnType) {
return this._functionDetails.declaredReturnType;
}
@ -585,7 +619,7 @@ export class FunctionType extends Type {
}
hasCustomDecorators(): boolean {
return (this._functionDetails.flags & FunctionTypeFlags.HasCustomDecorators) !== undefined;
return (this._functionDetails.flags & FunctionTypeFlags.HasCustomDecorators) !== 0;
}
clearHasCustomDecoratorsFlag() {
@ -593,7 +627,7 @@ export class FunctionType extends Type {
}
asStringInternal(recursionCount = 0): string {
let paramTypeString = this._functionDetails.parameters.map(param => {
let paramTypeString = this._functionDetails.parameters.map((param, index) => {
let paramString = '';
if (param.category === ParameterCategory.VarArgList) {
paramString += '*';
@ -606,8 +640,9 @@ export class FunctionType extends Type {
}
if (param.category === ParameterCategory.Simple) {
const paramType = this.getEffectiveParameterType(index);
const paramTypeString = recursionCount < AsStringMaxRecursionCount ?
param.type.asStringInternal(recursionCount + 1) : '';
paramType.asStringInternal(recursionCount + 1) : '';
paramString += ': ' + paramTypeString;
}
return paramString;