diff --git a/server/src/analyzer/hoverProvider.ts b/server/src/analyzer/hoverProvider.ts index d54c0e8d0..f426acff7 100644 --- a/server/src/analyzer/hoverProvider.ts +++ b/server/src/analyzer/hoverProvider.ts @@ -15,6 +15,8 @@ import { ParseResults } from '../parser/parser'; import { AnalyzerNodeInfo } from './analyzerNodeInfo'; import { ParseTreeUtils } from './parseTreeUtils'; import { SymbolCategory } from './symbol'; +import { ClassType } from './types'; +import { TypeUtils } from './typeUtils'; export class HoverProvider { static getHoverForPosition(parseResults: ParseResults, @@ -80,7 +82,8 @@ export class HoverProvider { case SymbolCategory.Class: { if (node instanceof NameNode) { - return '```\n(class) ' + node.nameToken.value + '\n```'; + return '```\n(class) ' + node.nameToken.value + + this._getTypeText(node) + '\n```'; } break; } diff --git a/server/src/analyzer/typeAnalyzer.ts b/server/src/analyzer/typeAnalyzer.ts index 52412a098..d903d4232 100644 --- a/server/src/analyzer/typeAnalyzer.ts +++ b/server/src/analyzer/typeAnalyzer.ts @@ -131,6 +131,16 @@ export class TypeAnalyzer extends ParseTreeWalker { } }); + // Update the type parameters for the class. + let typeParameters: TypeVarType[] = []; + classType.getBaseClasses().forEach(baseClass => { + TypeUtils.addTypeVarsToListIfUnique(typeParameters, + TypeUtils.getTypeVarArgumentsRecursive(baseClass.type)); + }); + if (classType.setTypeParameters(typeParameters)) { + this._setAnalysisChanged(); + } + this._enterScope(node, () => { this.walk(node.suite); }); @@ -1394,7 +1404,15 @@ export class TypeAnalyzer extends ParseTreeWalker { } else { let skipFirstMethodParam = this._isCallOnObjectOrClass(node); if (callType instanceof FunctionType) { - exprType = callType.getEffectiveReturnType(); + // The stdlib collections.pyi stub file defines namedtuple as a function + // rather than a class, so we need to check for it here. + if (node.leftExpression instanceof NameNode && + node.leftExpression.nameToken.value === 'namedtuple') { + exprType = TypeAnnotation.getNamedTupleType(node, false, + this._currentScope, this._fileInfo.diagnosticSink); + } else { + exprType = callType.getEffectiveReturnType(); + } } else if (callType instanceof OverloadedFunctionType) { // Determine which of the overloads (if any) match. let functionType = this._findOverloadedFunctionType( @@ -1923,14 +1941,27 @@ export class TypeAnalyzer extends ParseTreeWalker { // Tries to match the arguments of a call to the constructor for a class. private _validateConstructorArguments(node: CallExpressionNode, type: ClassType) { - const initMethodType = TypeUtils.lookUpClassMember(type, '__init__'); - if (!initMethodType) { - if (node.arguments.length > 0) { - this._addError( - `Expected 0 arguments to '${ type.getClassName() }' constructor`, node); + let validatedTypes = false; + const initMethodMember = TypeUtils.lookUpClassMember(type, '__init__', false); + if (initMethodMember && initMethodMember.symbol) { + const initMethodType = TypeUtils.getEffectiveTypeOfMember(initMethodMember); + this._validateCallArguments(node, initMethodType, true); + 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; } - } else if (initMethodType instanceof FunctionType) { - this._validateFunctionArguments(node, initMethodType, true); + } + + if (!validatedTypes && node.arguments.length > 0) { + this._addError( + `Expected 0 arguments to '${ type.getClassName() }' constructor`, node); } } @@ -2120,8 +2151,8 @@ export class TypeAnalyzer extends ParseTreeWalker { return type; } - private _enterTemporaryScope(callback: () => void, isConditional?: boolean, - isNotExecuted?: boolean) { + private _enterTemporaryScope(callback: () => void, isConditional ? : boolean, + isNotExecuted ? : boolean) { let prevScope = this._currentScope; let newScope = new Scope(ScopeType.Temporary, prevScope); if (isConditional) { diff --git a/server/src/analyzer/typeAnnotation.ts b/server/src/analyzer/typeAnnotation.ts index 70bea47a8..ac20343e5 100644 --- a/server/src/analyzer/typeAnnotation.ts +++ b/server/src/analyzer/typeAnnotation.ts @@ -10,10 +10,10 @@ import { TextRangeDiagnosticSink } from '../common/diagnosticSink'; import StringMap from '../common/stringMap'; -import { ArgumentCategory, CallExpressionNode, ConstantNode, DictionaryNode, +import { ArgumentCategory, CallExpressionNode, ConstantNode, EllipsisNode, ExpressionNode, FunctionNode, IndexExpressionNode, ListNode, MemberAccessExpressionNode, NameNode, NumberNode, ParameterCategory, - SetNode, StringNode, TupleExpressionNode } from '../parser/parseNodes'; + StringNode, TupleExpressionNode } from '../parser/parseNodes'; import { KeywordToken, KeywordType, QuoteTypeFlags, TokenType } from '../parser/tokenizerTypes'; import { AnalyzerNodeInfo } from './analyzerNodeInfo'; import { DefaultTypeSourceId } from './inferredType'; @@ -29,7 +29,6 @@ interface TypeResult { type: Type; typeList?: TypeResult[]; isClassType?: boolean; - typeVarsRecursive?: TypeVarType[]; node: ExpressionNode; } @@ -336,6 +335,8 @@ export class TypeAnnotation { return [decoratedType, warnIfDuplicate]; } + // Similar to the public getType method except that it returns a full + // TypeResult object with additional information. private static _getType(node: ExpressionNode, currentScope: Scope, diagSink: TextRangeDiagnosticSink, classNamesImplyObjects = true, transformBuiltInTypes = true): TypeResult { let typeResult: TypeResult | undefined; @@ -393,21 +394,6 @@ export class TypeAnnotation { if (typeResult.isClassType) { classNamesImplyObjects = false; } - } else if (node instanceof ListNode) { - // TODO - need to implement - typeResult = { type: UnknownType.create(), node }; - // diagSink.addErrorWithTextRange( - // `'Unsupported type expression: list`, node); - } else if (node instanceof DictionaryNode) { - // TODO - need to implement - typeResult = { type: UnknownType.create(), node }; - diagSink.addErrorWithTextRange( - `'Unsupported type expression: dictionary`, node); - } else if (node instanceof SetNode) { - // TODO - need to implement - typeResult = { type: UnknownType.create(), node }; - diagSink.addErrorWithTextRange( - `'Unsupported type expression: set`, node); } if (typeResult && classNamesImplyObjects) { @@ -419,7 +405,7 @@ export class TypeAnnotation { } diagSink.addErrorWithTextRange( - `Unknown type '${ ParseTreeUtils.printExpression(node) }'`, node); + `Unknown type expression '${ ParseTreeUtils.printExpression(node) }'`, node); return { type: UnknownType.create(), node }; } @@ -604,7 +590,8 @@ export class TypeAnnotation { } if (!type) { - // TODO - need to implement generic support + type = this._createSpecializedClassType(baseTypeResult.type, + typeArgs, diagSink); } } else if (!baseTypeResult.type.isAny()) { diagSink.addErrorWithTextRange( @@ -620,9 +607,6 @@ export class TypeAnnotation { } private static _validateTypeArgs(typeArgs: TypeResult[], diagSink: TextRangeDiagnosticSink) { - // Make sure there are no redundant type args. - // TODO - need to implement - // Make sure type args are reachable according to scoping rules. // TODO - need to implement } @@ -684,7 +668,8 @@ export class TypeAnnotation { } else if (baseTypeResult.type instanceof FunctionType) { // The stdlib collections.pyi stub file defines namedtuple as a function // rather than a class, so we need to check for it here. - if (node.leftExpression instanceof NameNode && node.leftExpression.nameToken.value === 'namedtuple') { + if (node.leftExpression instanceof NameNode && + node.leftExpression.nameToken.value === 'namedtuple') { type = this.getNamedTupleType(node, false, currentScope, diagSink); isClassType = true; } else { @@ -761,11 +746,22 @@ export class TypeAnnotation { `'Generic' requires at least one type argument`, errorNode); } - // Make sure that all of the type args are typeVars. + // Make sure that all of the type args are typeVars and are unique. + let uniqueTypeVars: TypeVarType[] = []; typeArgs.forEach(typeArg => { if (!(typeArg.type instanceof TypeVarType)) { diagSink.addErrorWithTextRange( `Type argument for 'Generic' must be a type variable`, typeArg.node); + } else { + for (let typeVar of uniqueTypeVars) { + if (typeVar === typeArg.type) { + diagSink.addErrorWithTextRange( + `Type argument for 'Generic' must be unique`, typeArg.node); + break; + } + } + + uniqueTypeVars.push(typeArg.type); } }); @@ -811,11 +807,39 @@ export class TypeAnnotation { return functionType; } + private static _createSpecializedClassType(classType: ClassType, + typeArgs: TypeResult[], diagSink: TextRangeDiagnosticSink): Type { + + let typeArgCount = typeArgs.length; + + // Make sure the argument list count is correct. + let typeParameters = classType.getTypeParameters(); + if (typeArgCount > typeParameters.length) { + if (typeParameters.length === 0) { + diagSink.addErrorWithTextRange(`No type arguments were expected`, + typeArgs[typeParameters.length].node); + } else { + diagSink.addErrorWithTextRange( + `Expected at most ${ typeParameters.length } type arguments`, + typeArgs[typeParameters.length].node); + } + typeArgCount = typeParameters.length; + } + + let specializedClass = classType.cloneForSpecialization(); + + // TODO - need to verify constraints of arguments + specializedClass.setTypeArguments(typeArgs.map(t => t.type)); + + return specializedClass; + } + private static _createSpecialType(classType: ClassType, typeArgs: TypeResult[], diagSink: TextRangeDiagnosticSink, paramLimit?: number): Type { let typeArgCount = typeArgs.length; - // Make sure the parameter list count is correct. + + // Make sure the argument list count is correct. if (paramLimit !== undefined && typeArgCount > paramLimit) { diagSink.addErrorWithTextRange( `Expected at most ${ paramLimit } type arguments`, typeArgs[paramLimit].node); @@ -823,9 +847,7 @@ export class TypeAnnotation { } let specializedType = classType.cloneForSpecialization(); - for (let i = 0; i < typeArgCount; i++) { - specializedType.addTypeArgument(typeArgs[i].type); - } + specializedType.setTypeArguments(typeArgs.map(t => t.type)); return specializedType; } diff --git a/server/src/analyzer/typeUtils.ts b/server/src/analyzer/typeUtils.ts index e1e2bc469..51beccb79 100644 --- a/server/src/analyzer/typeUtils.ts +++ b/server/src/analyzer/typeUtils.ts @@ -341,4 +341,46 @@ export class TypeUtils { return undefined; } + + static addTypeVarToListIfUnique(list: TypeVarType[], type: TypeVarType) { + if (list.find(t => t === type) === undefined) { + list.push(type); + } + } + + // Combines two lists of type var types, maintaining the combined order + // but removing any duplicates. + static addTypeVarsToListIfUnique(list1: TypeVarType[], list2: TypeVarType[]) { + for (let t of list2) { + this.addTypeVarToListIfUnique(list1, t); + } + } + + static getTypeVarArgumentsRecursive(type: Type): TypeVarType[] { + if (type instanceof TypeVarType) { + return [type]; + } else if (type instanceof ClassType) { + let combinedList: TypeVarType[] = []; + let typeArgs = type.getTypeArguments(); + + if (typeArgs) { + typeArgs.forEach(typeArg => { + if (typeArg instanceof Type) { + this.addTypeVarsToListIfUnique(combinedList, + this.getTypeVarArgumentsRecursive(typeArg)); + } + }); + } + + return combinedList; + } else if (type instanceof UnionType) { + let combinedList: TypeVarType[] = []; + for (let subtype of type.getTypes()) { + this.addTypeVarsToListIfUnique(combinedList, + this.getTypeVarArgumentsRecursive(subtype)); + } + } + + return []; + } } diff --git a/server/src/analyzer/types.ts b/server/src/analyzer/types.ts index a6fb80043..55daf0c17 100644 --- a/server/src/analyzer/types.ts +++ b/server/src/analyzer/types.ts @@ -56,7 +56,7 @@ export enum TypeCategory { TypeVar } -const AsStringMaxRecursionCount = 4; +const AsStringMaxRecursionCount = 20; export abstract class Type { abstract category: TypeCategory; @@ -81,7 +81,7 @@ export abstract class Type { } asString(): string { - return this.asStringInternal(AsStringMaxRecursionCount); + return this.asStringInternal(0); } abstract asStringInternal(recursionCount: number): string; @@ -112,7 +112,7 @@ export class UnboundType extends Type { return true; } - asStringInternal(recursionCount = AsStringMaxRecursionCount): string { + asStringInternal(recursionCount = 0): string { return 'Unbound'; } } @@ -130,7 +130,7 @@ export class UnknownType extends Type { return true; } - asStringInternal(recursionCount = AsStringMaxRecursionCount): string { + asStringInternal(recursionCount = 0): string { return 'Unknown'; } } @@ -162,7 +162,7 @@ export class ModuleType extends Type { return this._isPartialModule; } - asStringInternal(recursionCount = AsStringMaxRecursionCount): string { + asStringInternal(recursionCount = 0): string { return 'Module'; } } @@ -183,13 +183,6 @@ export enum ClassTypeFlags { SpecialBuiltIn = 0x04 } -// export class TypeReference extends Type { -// // A generic type instantiation will have one or more type -// // arguments and an associated target class type. -// private _typeArguments: Type[] = []; -// private _targetType?: ClassType; -// } - export interface BaseClass { isMetaclass: boolean; type: Type; @@ -295,6 +288,36 @@ export class ClassType extends Type { this._classDetails.instanceFields = nameMap; } + setTypeArguments(typeArgs: (Type | Type[])[]) { + this._typeArguments = typeArgs; + } + + getTypeArguments() { + return this._typeArguments; + } + + getTypeParameters() { + return this._classDetails.typeParameters; + } + + setTypeParameters(params: TypeVarType[]): boolean { + let didParametersChange = false; + + if (this._classDetails.typeParameters.length === params.length) { + for (let i = 0; i < params.length; i++) { + if (!params[i].isSame(this._classDetails.typeParameters[i])) { + didParametersChange = true; + } + } + } else { + didParametersChange = true; + } + + this._classDetails.typeParameters = params; + + return didParametersChange; + } + isSame(type2: Type): boolean { if (!super.isSame(type2)) { return false; @@ -317,26 +340,35 @@ export class ClassType extends Type { return false; } - getObjectName(recursionCount = AsStringMaxRecursionCount): string { + getObjectName(recursionCount = 0): string { let objName = this._classDetails.name; + // If there is a type arguments array, it's a specialized class. if (this._typeArguments) { - objName += '[' + this._typeArguments.map(typeArg => { - if (typeArg instanceof Type) { - return typeArg.asStringInternal(recursionCount); - } else { - return '[' + typeArg.map(type => { - return type.asStringInternal(recursionCount); - }).join(', ') + ']'; - } + if (this._typeArguments.length > 0) { + objName += '[' + this._typeArguments.map(typeArg => { + if (typeArg instanceof Type) { + return typeArg.asStringInternal(recursionCount + 1); + } else { + return '[' + typeArg.map(type => { + return type.asStringInternal(recursionCount + 1); + }).join(', ') + ']'; + } + }).join(', ') + ']'; + } + } else if (this._classDetails.typeParameters.length > 0) { + objName += '[' + this._classDetails.typeParameters.map(typeArg => { + return typeArg.asStringInternal(recursionCount + 1); }).join(', ') + ']'; } return objName; } - asStringInternal(recursionCount = AsStringMaxRecursionCount): string { - return 'Type[' + this.getObjectName(recursionCount) + ']'; + asStringInternal(recursionCount = 0): string { + // Return the same string that we'd use for an instance + // of the class. + return this.getObjectName(recursionCount + 1); } // Determines whether this is a subclass (derived class) @@ -365,17 +397,6 @@ export class ClassType extends Type { return false; } - - addTypeArgument(typeArg: Type | Type[]) { - if (!this._typeArguments) { - this._typeArguments = []; - } - this._typeArguments.push(typeArg); - } - - getTypeArguments() { - return this._typeArguments; - } } export class ObjectType extends Type { @@ -399,8 +420,8 @@ export class ObjectType extends Type { this._classType.isSame((type2 as ObjectType)._classType); } - asStringInternal(recursionCount = AsStringMaxRecursionCount): string { - return this._classType.getObjectName(); + asStringInternal(recursionCount = 0): string { + return this._classType.getObjectName(recursionCount + 1); } } @@ -501,7 +522,7 @@ export class FunctionType extends Type { this._functionDetails.flags &= ~FunctionTypeFlags.HasCustomDecorators; } - asStringInternal(recursionCount = AsStringMaxRecursionCount): string { + asStringInternal(recursionCount = 0): string { let paramTypeString = this._functionDetails.parameters.map(param => { let paramString = ''; if (param.category === ParameterCategory.VarArgList) { @@ -515,8 +536,8 @@ export class FunctionType extends Type { } if (param.category === ParameterCategory.Simple) { - const paramTypeString = recursionCount > 0 ? - param.type.asStringInternal(recursionCount - 1) : ''; + const paramTypeString = recursionCount < AsStringMaxRecursionCount ? + param.type.asStringInternal(recursionCount + 1) : ''; paramString += ': ' + paramTypeString; } return paramString; @@ -524,8 +545,8 @@ export class FunctionType extends Type { let returnTypeString = 'Any'; const returnType = this.getEffectiveReturnType(); - returnTypeString = recursionCount > 0 ? - returnType.asStringInternal(recursionCount - 1) : ''; + returnTypeString = recursionCount < AsStringMaxRecursionCount ? + returnType.asStringInternal(recursionCount + 1) : ''; return `(${ paramTypeString }) -> ${ returnTypeString }`; } @@ -559,9 +580,9 @@ export class OverloadedFunctionType extends Type { } } - asStringInternal(recursionCount = AsStringMaxRecursionCount): string { + asStringInternal(recursionCount = 0): string { const overloads = this._overloads.map(overload => - overload.type.asStringInternal(recursionCount)); + overload.type.asStringInternal(recursionCount + 1)); return `Overload[${ overloads.join(', ') }]`; } } @@ -598,10 +619,10 @@ export class PropertyType extends Type { return this._getter.getEffectiveReturnType(); } - asStringInternal(recursionCount = AsStringMaxRecursionCount): string { + asStringInternal(recursionCount = 0): string { const returnType = this._getter.getEffectiveReturnType(); - let returnTypeString = recursionCount > 0 ? - returnType.asStringInternal(recursionCount - 1) : ''; + let returnTypeString = recursionCount < AsStringMaxRecursionCount ? + returnType.asStringInternal(recursionCount + 1) : ''; return returnTypeString; } } @@ -615,7 +636,7 @@ export class NoneType extends Type { return this._instance; } - asStringInternal(recursionCount = AsStringMaxRecursionCount): string { + asStringInternal(recursionCount = 0): string { return 'None'; } } @@ -715,15 +736,15 @@ export class UnionType extends Type { return this._types.find(t => t.isSame(type)) !== undefined; } - asStringInternal(recursionCount = AsStringMaxRecursionCount): string { + asStringInternal(recursionCount = 0): string { if (this._types.find(t => t.category === TypeCategory.None) !== undefined) { - const optionalType = recursionCount > 0 ? - this.removeOptional().asStringInternal(recursionCount - 1) : ''; + const optionalType = recursionCount < AsStringMaxRecursionCount ? + this.removeOptional().asStringInternal(recursionCount + 1) : ''; return 'Optional[' + optionalType + ']'; } - const unionTypeString = recursionCount > 0 ? - this._types.map(t => t.asStringInternal(recursionCount - 1)).join(', ') : ''; + const unionTypeString = recursionCount < AsStringMaxRecursionCount ? + this._types.map(t => t.asStringInternal(recursionCount + 1)).join(', ') : ''; return 'Union[' + unionTypeString + ']'; } @@ -766,9 +787,9 @@ export class TupleType extends Type { !type2Tuple._entryTypes[index].isSame(t)) === undefined; } - asStringInternal(recursionCount = AsStringMaxRecursionCount): string { - let tupleTypeString = recursionCount > 0 ? - this._entryTypes.map(t => t.asStringInternal(recursionCount - 1)).join(', ') : ''; + asStringInternal(recursionCount = 0): string { + let tupleTypeString = recursionCount < AsStringMaxRecursionCount ? + this._entryTypes.map(t => t.asStringInternal(recursionCount + 1)).join(', ') : ''; return 'Tuple[' + tupleTypeString + ']'; } } @@ -787,6 +808,10 @@ export class TypeVarType extends Type { this._name = name; } + getName() { + return this._name; + } + getConstraints() { return this._constraints; } @@ -833,13 +858,19 @@ export class TypeVarType extends Type { return true; } - asStringInternal(recursionCount = AsStringMaxRecursionCount): string { - let params: string[] = [this._name]; + asStringInternal(recursionCount = 0): string { + // Print the name in a simplified form if it's embedded + // inside another type string. if (recursionCount > 0) { - for (let constraint of this._constraints) { - params.push(constraint.asStringInternal(recursionCount - 1)); + return this._name; + } else { + let params: string[] = [this._name]; + if (recursionCount < AsStringMaxRecursionCount) { + for (let constraint of this._constraints) { + params.push(constraint.asStringInternal(recursionCount + 1)); + } } + return 'TypeVar[' + params.join(', ') + ']'; } - return 'TypeVar[' + params.join(', ') + ']'; } }