Continued implementation of generic classes.

This commit is contained in:
Eric Traut 2019-03-14 16:38:52 -07:00
parent 5e47a2fd18
commit f0bf4a1f74
5 changed files with 229 additions and 100 deletions

View File

@ -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;
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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 [];
}
}

View File

@ -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(', ') + ']';
}
}