diff --git a/server/src/analyzer/typeAnnotation.ts b/server/src/analyzer/expressionEvaluator.ts similarity index 61% rename from server/src/analyzer/typeAnnotation.ts rename to server/src/analyzer/expressionEvaluator.ts index a1ebc00c9..29f8c00b5 100644 --- a/server/src/analyzer/typeAnnotation.ts +++ b/server/src/analyzer/expressionEvaluator.ts @@ -1,52 +1,408 @@ /* -* typeAnnotation.ts +* expressionEvaluator.ts * Copyright (c) Microsoft Corporation. All rights reserved. * Author: Eric Traut * -* Class that handles interpretation of type annotations, -* converting the parsed annotation into an internal type -* that can be used for type analysis. +* Class that evaluates the type of expressions (parse trees) +* within particular contexts and reports type errors. */ import { TextRangeDiagnosticSink } from '../common/diagnosticSink'; import StringMap from '../common/stringMap'; -import { ArgumentCategory, CallExpressionNode, ConstantNode, - EllipsisNode, ExpressionNode, FunctionNode, IndexExpressionNode, ListNode, - MemberAccessExpressionNode, NameNode, NumberNode, ParameterCategory, - StringNode, TupleExpressionNode } from '../parser/parseNodes'; +import { TextRange } from '../common/textRange'; +import { ArgumentCategory, CallExpressionNode, ConstantNode, EllipsisNode, + ExpressionNode, FunctionNode, IndexExpressionNode, ListNode, + MemberAccessExpressionNode, NameNode, NumberNode, ParameterCategory, StringNode, TupleExpressionNode } from '../parser/parseNodes'; import { KeywordToken, KeywordType, QuoteTypeFlags, TokenType } from '../parser/tokenizerTypes'; +import { ScopeUtils } from '../scopeUtils'; import { AnalyzerNodeInfo } from './analyzerNodeInfo'; import { DefaultTypeSourceId } from './inferredType'; import { ParseTreeUtils } from './parseTreeUtils'; import { Scope, ScopeType } from './scope'; import { Symbol } from './symbol'; -import { AnyType, ClassType, ClassTypeFlags, FunctionParameter, FunctionType, - FunctionTypeFlags, ModuleType, NoneType, ObjectType, OverloadedFunctionType, - PropertyType, TupleType, Type, TypeVarType, UnionType, UnknownType } from './types'; +import { TypeConstraint } from './typeConstraint'; +import { AnyType, ClassType, ClassTypeFlags, FunctionParameter, FunctionType, FunctionTypeFlags, + ModuleType, NoneType, ObjectType, OverloadedFunctionType, PropertyType, TupleType, Type, + TypeVarType, UnionType, UnknownType } from './types'; import { TypeUtils } from './typeUtils'; interface TypeResult { type: Type; typeList?: TypeResult[]; - isClassType?: boolean; node: ExpressionNode; } -export class TypeAnnotation { - static getType(node: ExpressionNode, currentScope: Scope, diagSink: TextRangeDiagnosticSink, - classNamesImplyObjects = true): Type { +export enum EvaluatorFlags { + None = 0, - let typeResult = this._getType(node, currentScope, diagSink, classNamesImplyObjects); + // Interpret a class type as a instance of that class. This + // is the normal mode used for type annotations. + ConvertClassToObject = 1 +} +export class ExpressionEvaluator { + private _scope: Scope; + private _expressionTypeConstraints: TypeConstraint[]; + private _diagnosticSink?: TextRangeDiagnosticSink; + + constructor(scope: Scope, expressionTypeConstraints: TypeConstraint[], + diagnosticSink?: TextRangeDiagnosticSink) { + this._scope = scope; + this._expressionTypeConstraints = expressionTypeConstraints; + this._diagnosticSink = diagnosticSink; + } + + getType(node: ExpressionNode, flags: EvaluatorFlags): Type { + let typeResult = this._getTypeFromExpression(node, flags); return typeResult.type; } - static getTypeVarType(node: CallExpressionNode, currentScope: Scope, - diagSink: TextRangeDiagnosticSink): Type | undefined { + // Specializes the specified (potentially generic) class type using + // 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). + specializeClassType(classType: ClassType, typeArgNode: ExpressionNode, + flags: EvaluatorFlags): Type { + let typeArgs = this._getTypeArgs(typeArgNode); + // Handle the special-case classes that are not defined + // in the type stubs. + if (classType.isSpecialBuiltIn()) { + const className = classType.getClassName(); + + switch (className) { + case 'Callable': { + return this._createCallableType(typeArgs); + } + + case 'Optional': { + return this._createOptional(typeArgNode, typeArgs); + } + + case 'Type': { + return this._createTypeType(typeArgNode, typeArgs); + } + + case 'ClassVar': + case 'Deque': + case 'List': + case 'FrozenSet': + case 'Set': { + return this._createSpecialType(classType, typeArgs, flags, 1); + } + + case 'ChainMap': + case 'Dict': + case 'DefaultDict': { + return this._createSpecialType(classType, typeArgs, flags, 2); + } + + case 'Protocol': + case 'Tuple': { + return this._createSpecialType(classType, typeArgs, flags); + } + + case 'Union': { + return this._createUnionType(typeArgs); + } + + case 'Generic': + if (flags & EvaluatorFlags.ConvertClassToObject) { + this._addError(`Generic allowed only as base class`, typeArgNode); + } + return this._createGenericType(typeArgNode, 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); + } + + let specializedType = this._createSpecializedClassType(classType, typeArgs); + return this._convertClassToObject(specializedType, flags); + } + + // Determines if the function node is a property accessor (getter, setter, deleter). + getPropertyType(node: FunctionNode, type: FunctionType): PropertyType | undefined { + if (ParseTreeUtils.functionHasDecorator(node, 'property')) { + return new PropertyType(type); + } + + const setterOrDeleterDecorator = node.decorators.find(decorator => { + return decorator.callName instanceof MemberAccessExpressionNode && + decorator.callName.leftExpression instanceof NameNode && + (decorator.callName.memberName.nameToken.value === 'setter' || + decorator.callName.memberName.nameToken.value === 'deleter') && + decorator.arguments.length === 0; + }); + + if (setterOrDeleterDecorator) { + let memberAccessNode = setterOrDeleterDecorator.callName as MemberAccessExpressionNode; + const propertyName = (memberAccessNode.leftExpression as NameNode).nameToken.value; + const isSetter = memberAccessNode.memberName.nameToken.value === 'setter'; + + let curValue = this._scope.lookUpSymbol(propertyName); + + if (curValue && curValue.currentType instanceof PropertyType) { + // TODO - check for duplicates. + // TODO - check for type consistency. + if (isSetter) { + curValue.currentType.setSetter(type); + } else { + curValue.currentType.setDeleter(type); + } + return curValue.currentType; + } + } + + return undefined; + } + + getOverloadedFunctionType(node: FunctionNode, type: FunctionType): + [OverloadedFunctionType | undefined, boolean] { + + let warnIfDuplicate = true; + let decoratedType: OverloadedFunctionType | undefined; + let typeSourceId = AnalyzerNodeInfo.getTypeSourceId(node); + + // TODO - make sure this overload decorator is the built-in one. + if (ParseTreeUtils.functionHasDecorator(node, 'overload')) { + let existingSymbol = this._scope.lookUpSymbol(node.name.nameToken.value); + if (existingSymbol && existingSymbol.currentType instanceof OverloadedFunctionType) { + existingSymbol.currentType.addOverload(typeSourceId, type); + decoratedType = existingSymbol.currentType; + warnIfDuplicate = false; + } else { + let newOverloadType = new OverloadedFunctionType(); + newOverloadType.addOverload(typeSourceId, type); + decoratedType = newOverloadType; + } + } + + return [decoratedType, warnIfDuplicate]; + } + + private _getTypeFromExpression(node: ExpressionNode, flags: EvaluatorFlags): TypeResult { + let typeResult: TypeResult | undefined; + + if (node instanceof NameNode) { + typeResult = this._getTypeFromName(node, flags); + } else if (node instanceof MemberAccessExpressionNode) { + typeResult = this._getTypeFromMemberAccessExpression(node, flags); + } else if (node instanceof IndexExpressionNode) { + typeResult = this._getTypeFromIndexExpression(node, flags); + } else if (node instanceof CallExpressionNode) { + typeResult = this._getCallExpression(node, flags); + } else if (node instanceof TupleExpressionNode) { + typeResult = this._getTupleExpression(node, flags); + } else if (node instanceof ConstantNode) { + typeResult = this._getConstantExpression(node, flags); + } else if (node instanceof StringNode) { + let isBytes = (node.tokens[0].quoteTypeFlags & QuoteTypeFlags.Byte) !== 0; + typeResult = this._getBuiltInTypeFromExpression(node, + isBytes ? 'byte' : 'str', flags); + } else if (node instanceof NumberNode) { + typeResult = this._getBuiltInTypeFromExpression(node, + node.token.isInteger ? 'int' : 'float', flags); + } else if (node instanceof EllipsisNode) { + typeResult = { type: AnyType.create(), node }; + } + + if (typeResult) { + return typeResult; + } + + this._addError(`Unknown type expression '${ ParseTreeUtils.printExpression(node) }'`, node); + return { type: UnknownType.create(), node }; + } + + private _getTypeFromName(node: NameNode, flags: EvaluatorFlags): TypeResult | undefined { + let symbolInScope = this._scope.lookUpSymbolRecursive(node.nameToken.value); + if (!symbolInScope) { + return undefined; + } + + let type = symbolInScope.symbol.currentType; + if (!type) { + return undefined; + } + + type = this._convertClassToObject(type, flags); + + return { type, node }; + } + + private _getTypeFromMemberAccessExpression(node: MemberAccessExpressionNode, + flags: EvaluatorFlags): TypeResult | undefined { + + let baseTypeResult = this._getTypeFromExpression(node.leftExpression, EvaluatorFlags.None); + let memberName = node.memberName.nameToken.value; + let type: Type | undefined; + + if (baseTypeResult.type.isAny()) { + type = baseTypeResult.type; + } else if (baseTypeResult.type instanceof ModuleType) { + let fieldInfo = baseTypeResult.type.getFields().get(memberName); + if (fieldInfo) { + type = fieldInfo.currentType; + } else { + this._addError(`'${ memberName }' is not a known member of module`, node.memberName); + type = UnknownType.create(); + } + } else if (baseTypeResult.type instanceof ClassType) { + let fieldInfo = TypeUtils.lookUpClassMember(baseTypeResult.type, memberName); + if (fieldInfo) { + type = TypeUtils.getEffectiveTypeOfMember(fieldInfo); + } else { + this._addError( + `'${ memberName }' is not a known member of '${ baseTypeResult.type.asString() }'`, + node.memberName); + type = UnknownType.create(); + } + } else if (baseTypeResult.type instanceof ObjectType) { + let fieldInfo = TypeUtils.lookUpClassMember(baseTypeResult.type.getClassType(), memberName); + if (fieldInfo) { + type = TypeUtils.getEffectiveTypeOfMember(fieldInfo); + } else { + this._addError( + `'${ memberName }' is not a known member of '${ baseTypeResult.type.asString() }'`, + node.memberName); + type = UnknownType.create(); + } + } + + if (!type) { + return undefined; + } + + type = this._convertClassToObject(type, flags); + + return { type, node }; + } + + private _getTypeFromIndexExpression(node: IndexExpressionNode, flags: EvaluatorFlags): TypeResult { + let type: Type | undefined; + let baseTypeResult = this._getTypeFromExpression(node.baseExpression, EvaluatorFlags.None); + let typeArgs = this._getTypeArgs(node.indexExpression); + + this._validateTypeArgs(typeArgs); + + if (baseTypeResult.type instanceof ClassType) { + type = this.specializeClassType(baseTypeResult.type, node.indexExpression, flags); + } else if (baseTypeResult.type instanceof UnionType) { + // TODO - need to implement + } else if (baseTypeResult.type instanceof FunctionType) { + // TODO - need to implement + } else if (!baseTypeResult.type.isAny()) { + this._addError( + `'Unsupported type expression: indexed (${ baseTypeResult.type.asString() })`, + node.baseExpression); + } + + if (!type) { + type = UnknownType.create(); + } + + return { type, node }; + } + + private _validateTypeArgs(typeArgs: TypeResult[]) { + // Make sure type args are reachable according to scoping rules. + // TODO - need to implement + } + + private _getTypeArgs(node: ExpressionNode): TypeResult[] { + + let typeArgs: TypeResult[] = []; + + if (node instanceof TupleExpressionNode) { + node.expressions.forEach(expr => { + typeArgs.push(this._getTypeArg(expr)); + }); + } else { + typeArgs.push(this._getTypeArg(node)); + } + + return typeArgs; + } + + private _getTypeArg(node: ExpressionNode): TypeResult { + let typeResult: TypeResult; + + if (node instanceof ListNode) { + typeResult = { + type: UnknownType.create(), + typeList: node.entries.map(entry => { + return this._getTypeFromExpression(entry, EvaluatorFlags.ConvertClassToObject); + }), + node + }; + } else { + typeResult = this._getTypeFromExpression(node, EvaluatorFlags.ConvertClassToObject); + } + + return typeResult; + } + + private _getTupleExpression(node: TupleExpressionNode, flags: EvaluatorFlags): TypeResult | undefined { + let tupleType = new TupleType(ScopeUtils.getBuiltInType(this._scope, 'tuple') as ClassType); + + node.expressions.forEach(expr => { + let entryTypeResult = this._getTypeFromExpression(expr, flags); + tupleType.addEntryType(entryTypeResult.type || UnknownType.create()); + }); + + return { + type: tupleType, + node + }; + } + + private _getCallExpression(node: CallExpressionNode, flags: EvaluatorFlags): TypeResult | undefined { + let type: Type | undefined; + let baseTypeResult = this._getTypeFromExpression(node.leftExpression, EvaluatorFlags.None); + + if (baseTypeResult.type instanceof ClassType && baseTypeResult.type.isBuiltIn()) { + const className = baseTypeResult.type.getClassName(); + + if (className === 'TypeVar') { + type = this.createTypeVarType(node); + } else if (className === 'NamedTuple') { + type = this.createNamedTupleType(node, true); + flags &= ~EvaluatorFlags.ConvertClassToObject; + } else { + type = UnknownType.create(); + this._addError(`'${ className }' is not callable`, node); + } + } else if (baseTypeResult.type 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 (baseTypeResult.type.getSpecialBuiltInName() === 'namedtuple') { + type = this.createNamedTupleType(node, false); + flags &= ~EvaluatorFlags.ConvertClassToObject; + } else { + type = baseTypeResult.type.getEffectiveReturnType(); + } + } else if (baseTypeResult.type.isAny()) { + type = UnknownType.create(); + } + + if (type === undefined) { + this._addError(`'Unsupported type expression: call`, node); + return undefined; + } + + type = this._convertClassToObject(type, flags); + + return { type, node }; + } + + createTypeVarType(node: CallExpressionNode): Type | undefined { let typeVarName = ''; if (node.arguments.length === 0) { - diagSink.addErrorWithTextRange('Expected name of type var', node); + this._addError('Expected name of type var', node); return undefined; } @@ -54,7 +410,7 @@ export class TypeAnnotation { if (firstArg.valueExpression instanceof StringNode) { typeVarName = firstArg.valueExpression.getValue(); } else { - diagSink.addErrorWithTextRange('Expected name of type var as first parameter', + this._addError('Expected name of type var as first parameter', firstArg.valueExpression); } @@ -68,19 +424,18 @@ export class TypeAnnotation { if (paramName) { if (paramNameMap.get(paramName)) { - diagSink.addErrorWithTextRange( + this._addError( `Duplicate parameter name '${ paramName }' not allowed`, node.arguments[i]); } if (paramName === 'bound') { typeVar.setBoundType(this.getType( - node.arguments[i].valueExpression, - currentScope, diagSink)); + node.arguments[i].valueExpression, EvaluatorFlags.ConvertClassToObject)); } else if (paramName === 'covariant') { - if (this._getBooleanValue(node.arguments[i].valueExpression, diagSink)) { + if (this._getBooleanValue(node.arguments[i].valueExpression)) { if (typeVar.isContravariant()) { - diagSink.addErrorWithTextRange( + this._addError( `A TypeVar cannot be both covariant and contravariant`, node.arguments[i]); } else { @@ -88,9 +443,9 @@ export class TypeAnnotation { } } } else if (paramName === 'contravariant') { - if (this._getBooleanValue(node.arguments[i].valueExpression, diagSink)) { + if (this._getBooleanValue(node.arguments[i].valueExpression)) { if (typeVar.isContravariant()) { - diagSink.addErrorWithTextRange( + this._addError( `A TypeVar cannot be both covariant and contravariant`, node.arguments[i]); } else { @@ -98,7 +453,7 @@ export class TypeAnnotation { } } } else { - diagSink.addErrorWithTextRange( + this._addError( `'${ paramName }' is unknown parameter to TypeVar`, node.arguments[i]); } @@ -106,26 +461,39 @@ export class TypeAnnotation { paramNameMap.set(paramName, paramName); } else { typeVar.addConstraint(this.getType( - node.arguments[i].valueExpression, - currentScope, diagSink)); + node.arguments[i].valueExpression, EvaluatorFlags.ConvertClassToObject)); } } return typeVar; } + private _getBooleanValue(node: ExpressionNode): boolean { + if (node instanceof ConstantNode) { + if (node.token instanceof KeywordToken) { + if (node.token.keywordType === KeywordType.False) { + return false; + } else if (node.token.keywordType === KeywordType.True) { + return true; + } + } + } + + this._addError('Expected True or False', node); + return false; + } + // Creates a new custom tuple factory class with named values. // Supports both typed and untyped variants. - static createNamedTupleType(node: CallExpressionNode, includesTypes: boolean, - currentScope: Scope, diagSink: TextRangeDiagnosticSink): ClassType { + createNamedTupleType(node: CallExpressionNode, includesTypes: boolean): ClassType { let className = 'namedtuple'; if (node.arguments.length === 0) { - diagSink.addErrorWithTextRange('Expected named tuple class name as firat parameter', + this._addError('Expected named tuple class name as firat parameter', node.leftExpression); } else { const nameArg = node.arguments[0]; if (nameArg.argumentCategory !== ArgumentCategory.Simple) { - diagSink.addErrorWithTextRange('Expected named tuple class name as firat parameter', + this._addError('Expected named tuple class name as firat parameter', node.arguments[0].valueExpression); } else if (nameArg.valueExpression instanceof StringNode) { className = nameArg.valueExpression.getValue(); @@ -134,12 +502,12 @@ export class TypeAnnotation { let classType = new ClassType(className, ClassTypeFlags.None, AnalyzerNodeInfo.getTypeSourceId(node)); - classType.addBaseClass(this.getBuiltInType(currentScope, 'NamedTuple'), false); + classType.addBaseClass(ScopeUtils.getBuiltInType(this._scope, 'NamedTuple'), false); const classFields = classType.getClassFields(); classFields.set('__class__', new Symbol(classType, DefaultTypeSourceId)); const instanceFields = classType.getInstanceFields(); - let tupleType = new TupleType(this.getBuiltInType(currentScope, 'tuple') as ClassType); + let tupleType = new TupleType(ScopeUtils.getBuiltInType(this._scope, 'tuple') as ClassType); let constructorType = new FunctionType(FunctionTypeFlags.ClassMethod); constructorType.setDeclaredReturnType(tupleType); constructorType.addParameter({ @@ -160,7 +528,7 @@ export class TypeAnnotation { let addGenericGetAttribute = false; if (node.arguments.length < 2) { - diagSink.addErrorWithTextRange('Expected named tuple entry list as second parameter', + this._addError('Expected named tuple entry list as second parameter', node.leftExpression); addGenericGetAttribute = true; } else { @@ -200,10 +568,13 @@ export class TypeAnnotation { // Handle the variant that includes name/type tuples. if (entry instanceof TupleExpressionNode && entry.expressions.length === 2) { entryNameNode = entry.expressions[0]; - entryType = this.getType(entry.expressions[1], currentScope, - diagSink); + let entryTypeInfo = this._getTypeFromExpression(entry.expressions[1], + EvaluatorFlags.ConvertClassToObject); + if (entryTypeInfo) { + entryType = entryTypeInfo.type; + } } else { - diagSink.addErrorWithTextRange( + this._addError( 'Expected two-entry tuple specifying entry name and type', entry); } } else { @@ -214,11 +585,11 @@ export class TypeAnnotation { if (entryNameNode instanceof StringNode) { entryName = entryNameNode.getValue(); if (!entryName) { - diagSink.addErrorWithTextRange( + this._addError( 'Names within a named tuple cannot be empty', entryNameNode); } } else { - diagSink.addErrorWithTextRange( + this._addError( 'Expected string literal for entry name', entryNameNode || entry); } @@ -227,7 +598,7 @@ export class TypeAnnotation { } if (entryMap[entryName]) { - diagSink.addErrorWithTextRange( + this._addError( 'Names within a named tuple must be unique', entryNameNode || entry); } @@ -262,13 +633,13 @@ export class TypeAnnotation { classFields.set('__init__', new Symbol(initType, DefaultTypeSourceId)); let keysItemType = new FunctionType(FunctionTypeFlags.None); - keysItemType.setDeclaredReturnType(this.getBuiltInObject(currentScope, 'list', - [this.getBuiltInObject(currentScope, 'str')])); + keysItemType.setDeclaredReturnType(ScopeUtils.getBuiltInObject(this._scope, 'list', + [ScopeUtils.getBuiltInObject(this._scope, 'str')])); classFields.set('keys', new Symbol(keysItemType, DefaultTypeSourceId)); classFields.set('items', new Symbol(keysItemType, DefaultTypeSourceId)); let lenType = new FunctionType(FunctionTypeFlags.InstanceMethod); - lenType.setDeclaredReturnType(this.getBuiltInObject(currentScope, 'int')); + lenType.setDeclaredReturnType(ScopeUtils.getBuiltInObject(this._scope, 'int')); lenType.addParameter(selfParameter); classFields.set('__len__', new Symbol(lenType, DefaultTypeSourceId)); @@ -279,7 +650,7 @@ export class TypeAnnotation { getAttribType.addParameter({ category: ParameterCategory.Simple, name: 'name', - type: this.getBuiltInObject(currentScope, 'str') + type: ScopeUtils.getBuiltInObject(this._scope, 'str') }); classFields.set('__getattribute__', new Symbol(getAttribType, DefaultTypeSourceId)); } @@ -287,529 +658,47 @@ export class TypeAnnotation { return classType; } - static getBuiltInType(currentScope: Scope, name: string): Type { - // Starting at the current scope, find the built-in scope, which should - // be the top-most parent. - let builtInScope = currentScope; - while (builtInScope.getType() !== ScopeType.BuiltIn) { - builtInScope = builtInScope.getParent()!; - } - - let nameType = builtInScope.lookUpSymbol(name); - if (nameType) { - return nameType.currentType; - } - - return UnknownType.create(); - } - - static getBuiltInObject(currentScope: Scope, className: string, - typeArguments?: Type[]): Type { - - let nameType = this.getBuiltInType(currentScope, className); - if (nameType instanceof ClassType) { - let classType = nameType; - if (typeArguments) { - classType = classType.cloneForSpecialization(); - classType.setTypeArguments(typeArguments); - } - return new ObjectType(classType); - } - - return nameType; - } - - // Determines if the function node is a property accessor (getter, setter, deleter). - static getPropertyType(node: FunctionNode, type: FunctionType, - currentScope: Scope): PropertyType | undefined { - if (ParseTreeUtils.functionHasDecorator(node, 'property')) { - return new PropertyType(type); - } - - const setterOrDeleterDecorator = node.decorators.find(decorator => { - return decorator.callName instanceof MemberAccessExpressionNode && - decorator.callName.leftExpression instanceof NameNode && - (decorator.callName.memberName.nameToken.value === 'setter' || - decorator.callName.memberName.nameToken.value === 'deleter') && - decorator.arguments.length === 0; - }); - - if (setterOrDeleterDecorator) { - let memberAccessNode = setterOrDeleterDecorator.callName as MemberAccessExpressionNode; - const propertyName = (memberAccessNode.leftExpression as NameNode).nameToken.value; - const isSetter = memberAccessNode.memberName.nameToken.value === 'setter'; - - let curValue = currentScope.lookUpSymbol(propertyName); - - if (curValue && curValue.currentType instanceof PropertyType) { - // TODO - check for duplicates. - // TODO - check for type consistency. - if (isSetter) { - curValue.currentType.setSetter(type); - } else { - curValue.currentType.setDeleter(type); - } - return curValue.currentType; - } - } - - return undefined; - } - - static getOverloadedFunctionType(node: FunctionNode, type: FunctionType, - currentScope: Scope): [OverloadedFunctionType | undefined, boolean] { - - let warnIfDuplicate = true; - let decoratedType: OverloadedFunctionType | undefined; - let typeSourceId = AnalyzerNodeInfo.getTypeSourceId(node); - - // TODO - make sure this overload decorator is the built-in one. - if (ParseTreeUtils.functionHasDecorator(node, 'overload')) { - let existingSymbol = currentScope.lookUpSymbol(node.name.nameToken.value); - if (existingSymbol && existingSymbol.currentType instanceof OverloadedFunctionType) { - existingSymbol.currentType.addOverload(typeSourceId, type); - decoratedType = existingSymbol.currentType; - warnIfDuplicate = false; - } else { - let newOverloadType = new OverloadedFunctionType(); - newOverloadType.addOverload(typeSourceId, type); - decoratedType = newOverloadType; - } - } - - return [decoratedType, warnIfDuplicate]; - } - - // Specializes the specified (potentially generic) class type using - // 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). - static specializeClassType(classType: ClassType, typeArgNode: ExpressionNode, - currentScope: Scope, diagSink: TextRangeDiagnosticSink): [Type, boolean] { - - let typeArgs = this._getTypeArgs(typeArgNode, currentScope, diagSink); - - // Handle the special-case classes that are not defined - // in the type stubs. - if (classType.isSpecialBuiltIn()) { - const className = classType.getClassName(); - - switch (className) { - case 'Callable': { - return [ - this._createCallableType(classType, typeArgs, diagSink), - false - ]; - } - - case 'Optional': { - return [ - this._createOptional(typeArgNode, typeArgs, diagSink), - false - ]; - } - - case 'Type': { - return [ - this._createTypeType(typeArgNode, typeArgs, diagSink), - true - ]; - } - - case 'ClassVar': - case 'Deque': - case 'List': - case 'FrozenSet': - case 'Set': { - return [ - this._createSpecialType(classType, typeArgs, diagSink, 1), - false - ]; - } - - case 'ChainMap': - case 'Dict': - case 'DefaultDict': { - return [ - this._createSpecialType(classType, typeArgs, diagSink, 2), - false - ]; - } - - case 'Protocol': - case 'Tuple': { - return [ - this._createSpecialType(classType, typeArgs, diagSink), - false - ]; - } - - case 'Union': { - return [ - this._createUnionType(typeArgs), - false - ]; - } - - case 'Generic': - return [ - this._createGenericType(typeArgNode, classType, typeArgs, diagSink), - false - ]; - } - } - - if (classType === this.getBuiltInType(currentScope, '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, diagSink), - true - ]; - } - - return [ - this._createSpecializedClassType(classType, typeArgs, diagSink), - false - ]; - } - - // 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): TypeResult { - let typeResult: TypeResult | undefined; - - if (node instanceof NameNode) { - typeResult = this._getTypeFromName(node, currentScope); - } else if (node instanceof EllipsisNode) { - typeResult = { - type: AnyType.create(), - node - }; - } else if (node instanceof MemberAccessExpressionNode) { - typeResult = this._getTypeFromMemberAccessExpression(node, currentScope, diagSink); - } else if (node instanceof IndexExpressionNode) { - typeResult = this._getTypeFromIndexExpression(node, currentScope, diagSink); - if (typeResult.isClassType) { - classNamesImplyObjects = false; - } - } else if (node instanceof TupleExpressionNode) { - let tupleType = new TupleType(this.getBuiltInType(currentScope, 'tuple') as ClassType); - node.expressions.forEach(expr => { - let entryTypeResult = this._getType(expr, - currentScope, diagSink, classNamesImplyObjects); - tupleType.addEntryType(entryTypeResult.type || UnknownType.create()); - }); - typeResult = { - type: tupleType, - node - }; - } else if (node instanceof ConstantNode) { - if (node.token.type === TokenType.Keyword) { - if (node.token.keywordType === KeywordType.None) { - typeResult = { type: NoneType.create(), node }; - } else if (node.token.keywordType === KeywordType.True || - node.token.keywordType === KeywordType.False || - node.token.keywordType === KeywordType.Debug) { - typeResult = { type: this.getBuiltInType(currentScope, 'bool'), node }; - } - } - } else if (node instanceof StringNode) { - if (node.tokens[0].quoteTypeFlags & QuoteTypeFlags.Byte) { - typeResult = { type: this.getBuiltInType(currentScope, 'byte'), node }; - } else { - typeResult = { type: this.getBuiltInType(currentScope, 'str'), node }; - } - } else if (node instanceof NumberNode) { - if (node.token.isInteger) { - typeResult = { type: this.getBuiltInType(currentScope, 'int'), node }; - } else { - typeResult = { type: this.getBuiltInType(currentScope, 'float'), node }; - } - } else if (node instanceof CallExpressionNode) { - typeResult = this._getCallExpression(node, currentScope, diagSink); - if (typeResult.isClassType) { - classNamesImplyObjects = false; - } - } - - if (typeResult && classNamesImplyObjects) { - typeResult.type = this._convertClassToObject(typeResult.type); - } - - if (typeResult) { - return typeResult; - } - - diagSink.addErrorWithTextRange( - `Unknown type expression '${ ParseTreeUtils.printExpression(node) }'`, node); - return { type: UnknownType.create(), node }; - } - - private static _getTypeFromName(node: NameNode, currentScope: Scope): TypeResult | undefined { - let symbolInScope = currentScope.lookUpSymbolRecursive(node.nameToken.value); - if (!symbolInScope) { - return undefined; - } - - let type = symbolInScope.symbol.currentType; - if (type) { - return { type, node }; - } - - return undefined; - } - - private static _getTypeFromMemberAccessExpression(node: MemberAccessExpressionNode, - currentScope: Scope, diagSink: TextRangeDiagnosticSink): TypeResult | undefined { - - let baseTypeResult = this._getType(node.leftExpression, currentScope, - diagSink, true); - let memberName = node.memberName.nameToken.value; + private _getConstantExpression(node: ConstantNode, flags: EvaluatorFlags): TypeResult | undefined { let type: Type | undefined; - if (baseTypeResult.type.isAny()) { - type = baseTypeResult.type; - } else if (baseTypeResult.type instanceof ModuleType) { - let fieldInfo = baseTypeResult.type.getFields().get(memberName); - if (fieldInfo) { - type = fieldInfo.currentType; - } else { - diagSink.addErrorWithTextRange( - `'${ memberName }' is not a known member of module`, node.memberName); - type = UnknownType.create(); + if (node.token.type === TokenType.Keyword) { + if (node.token.keywordType === KeywordType.None) { + type = NoneType.create(); + } else if (node.token.keywordType === KeywordType.True || + node.token.keywordType === KeywordType.False || + node.token.keywordType === KeywordType.Debug) { + type = ScopeUtils.getBuiltInType(this._scope, 'bool'); } - } else if (baseTypeResult.type instanceof ClassType) { - let fieldInfo = TypeUtils.lookUpClassMember(baseTypeResult.type, memberName); - if (fieldInfo) { - type = TypeUtils.getEffectiveTypeOfMember(fieldInfo); - } else { - diagSink.addErrorWithTextRange( - `'${ memberName }' is not a known member of '${ baseTypeResult.type.asString() }'`, - node.memberName); - type = UnknownType.create(); - } - } else if (baseTypeResult.type instanceof ObjectType) { - let fieldInfo = TypeUtils.lookUpClassMember(baseTypeResult.type.getClassType(), memberName); - if (fieldInfo) { - type = TypeUtils.getEffectiveTypeOfMember(fieldInfo); - } else { - diagSink.addErrorWithTextRange( - `'${ memberName }' is not a known member of '${ baseTypeResult.type.asString() }'`, - node.memberName); - type = UnknownType.create(); - } - } - - if (type) { - return { type, node }; - } - - return undefined; - } - - private static _getTypeFromIndexExpression(node: IndexExpressionNode, - currentScope: Scope, diagSink: TextRangeDiagnosticSink): TypeResult { - - let isClassType = false; - - let type: Type | undefined; - let baseTypeResult = this._getType(node.baseExpression, currentScope, - diagSink, false); - - let typeArgs = this._getTypeArgs(node.indexExpression, currentScope, diagSink); - - this._validateTypeArgs(typeArgs, diagSink); - - if (baseTypeResult.type instanceof ClassType) { - [type, isClassType] = this.specializeClassType(baseTypeResult.type, - node.indexExpression, currentScope, diagSink); - } else if (baseTypeResult.type instanceof UnionType) { - // TODO - need to implement - } else if (baseTypeResult.type instanceof FunctionType) { - // TODO - need to implement - } else if (!baseTypeResult.type.isAny()) { - diagSink.addErrorWithTextRange( - `'Unsupported type expression: indexed other (${ baseTypeResult.type.asString() })`, - node.baseExpression); } if (!type) { - type = UnknownType.create(); + return undefined; } - return { type, isClassType, node }; + type = this._convertClassToObject(type, flags); + + return { type, node }; } - private static _validateTypeArgs(typeArgs: TypeResult[], diagSink: TextRangeDiagnosticSink) { - // Make sure type args are reachable according to scoping rules. - // TODO - need to implement - } + private _getBuiltInTypeFromExpression(node: ExpressionNode, typeName: string, + flags: EvaluatorFlags): TypeResult | undefined { - private static _getTypeArgs(node: ExpressionNode, currentScope: Scope, - diagSink: TextRangeDiagnosticSink): TypeResult[] { + let type = ScopeUtils.getBuiltInType(this._scope, typeName); - let typeArgs: TypeResult[] = []; - - if (node instanceof TupleExpressionNode) { - node.expressions.forEach(expr => { - typeArgs.push(this._getTypeArg(expr, currentScope, diagSink)); - }); - } else { - typeArgs.push(this._getTypeArg(node, currentScope, diagSink)); + if (!type) { + return undefined; } - return typeArgs; - } + type = this._convertClassToObject(type, flags); - private static _getTypeArg(node: ExpressionNode, currentScope: Scope, - diagSink: TextRangeDiagnosticSink): TypeResult { - - let typeResult: TypeResult; - - if (node instanceof ListNode) { - typeResult = { - type: UnknownType.create(), - typeList: node.entries.map(entry => { - return this._getType(entry, currentScope, diagSink); - }), - node - }; - } else { - typeResult = this._getType(node, currentScope, diagSink); - } - - return typeResult; - } - - private static _getCallExpression(node: CallExpressionNode, - currentScope: Scope, diagSink: TextRangeDiagnosticSink): TypeResult { - - let isClassType = false; - let type: Type | undefined; - let baseTypeResult = this._getType(node.leftExpression, currentScope, diagSink, false); - if (baseTypeResult.type instanceof ClassType && baseTypeResult.type.isBuiltIn()) { - const className = baseTypeResult.type.getClassName(); - - if (className === 'TypeVar') { - type = this.getTypeVarType(node, currentScope, diagSink); - } else if (className === 'NamedTuple') { - type = this.createNamedTupleType(node, true, currentScope, diagSink); - isClassType = true; - } else { - type = UnknownType.create(); - diagSink.addErrorWithTextRange(`'${ className }' is not callable`, node); - } - } else if (baseTypeResult.type 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 (baseTypeResult.type.getSpecialBuiltInName() === 'namedtuple') { - type = this.createNamedTupleType(node, false, currentScope, diagSink); - isClassType = true; - } else { - type = baseTypeResult.type.getEffectiveReturnType(); - } - } else if (baseTypeResult.type.isAny()) { - type = UnknownType.create(); - } - - if (type === undefined) { - type = baseTypeResult.type; - diagSink.addErrorWithTextRange( - `'Unsupported type expression: call`, node); - } - - return { type, isClassType, node }; - } - - // Creates an Optional type annotation. - private static _createOptional(errorNode: ExpressionNode, typeArgs: TypeResult[], - diagSink: TextRangeDiagnosticSink): Type { - - if (typeArgs.length !== 1) { - diagSink.addErrorWithTextRange(`Expected one type parameter after Optional`, errorNode); - return UnknownType.create(); - } - - return TypeUtils.combineTypes(typeArgs[0].type, NoneType.create()); - } - - // Creates a Type type annotation. - private static _createTypeType(errorNode: ExpressionNode, typeArgs: TypeResult[], - diagSink: TextRangeDiagnosticSink): Type { - - if (typeArgs.length !== 1) { - diagSink.addErrorWithTextRange( - `Expected one type parameter after Type`, errorNode); - return UnknownType.create(); - } - - let type = typeArgs[0].type; - if (type instanceof ObjectType) { - return type.getClassType(); - } else if (type instanceof TypeVarType) { - // TODO - need to find a way to encode "type of" typeVar - return type; - } else if (!type.isAny()) { - diagSink.addErrorWithTextRange( - 'Expected type argument after Type', errorNode); - } - - return UnknownType.create(); - } - - // Unpacks the index expression for a Union type annotation. - private static _createUnionType(typeArgs: TypeResult[]): Type { - let types: Type[] = []; - - for (let typeArg of typeArgs) { - if (typeArg.type) { - types.push(typeArg.type); - } - } - - return TypeUtils.combineTypesArray(types); - } - - private static _createGenericType(errorNode: ExpressionNode, classType: ClassType, - typeArgs: TypeResult[], diagSink: TextRangeDiagnosticSink): Type { - - // Make sure there's at least one type arg. - if (typeArgs.length === 0) { - diagSink.addErrorWithTextRange( - `'Generic' requires at least one type argument`, errorNode); - } - - // 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); - } - }); - - return this._createSpecialType(classType, typeArgs, diagSink); + return { type, node }; } // Converts the type parameters for a Callable type. It should // have zero to two parameters. The first parameter, if present, should be // either an ellipsis or a list of parameter types. The second parameter, if // present, should specify the return type. - private static _createCallableType(classType: Type, - typeArgs: TypeResult[], diagSink: TextRangeDiagnosticSink): FunctionType { - + private _createCallableType(typeArgs: TypeResult[]): FunctionType { let functionType = new FunctionType(FunctionTypeFlags.None); functionType.setDeclaredReturnType(AnyType.create()); @@ -825,8 +714,7 @@ export class TypeAnnotation { } else if (typeArgs[0].type instanceof AnyType) { TypeUtils.addDefaultFunctionParameters(functionType); } else { - diagSink.addErrorWithTextRange( - `Expected parameter type list or '...'`, typeArgs[0].node); + this._addError(`Expected parameter type list or '...'`, typeArgs[0].node); } } @@ -835,26 +723,115 @@ export class TypeAnnotation { } if (typeArgs.length > 2) { - diagSink.addErrorWithTextRange( - `Expected only two type arguments to 'Callable'`, typeArgs[2].node); + this._addError(`Expected only two type arguments to 'Callable'`, typeArgs[2].node); } return functionType; } - private static _createSpecializedClassType(classType: ClassType, - typeArgs: TypeResult[], diagSink: TextRangeDiagnosticSink): Type { + // Creates an Optional type annotation. + private _createOptional(errorNode: ExpressionNode, typeArgs: TypeResult[]): Type { + if (typeArgs.length !== 1) { + this._addError(`Expected one type parameter after Optional`, errorNode); + return UnknownType.create(); + } + return TypeUtils.combineTypes(typeArgs[0].type, NoneType.create()); + } + + // Creates a Type type annotation. + private _createTypeType(errorNode: ExpressionNode, typeArgs: TypeResult[]): Type { + if (typeArgs.length !== 1) { + this._addError(`Expected one type parameter after Type`, errorNode); + return UnknownType.create(); + } + + let type = typeArgs[0].type; + if (type instanceof ObjectType) { + return type.getClassType(); + } else if (type instanceof TypeVarType) { + // TODO - need to find a way to encode "type of" typeVar + return type; + } else if (!type.isAny()) { + this._addError('Expected type argument after Type', errorNode); + } + + return UnknownType.create(); + } + + private _createSpecialType(classType: ClassType, typeArgs: TypeResult[], + flags: EvaluatorFlags, paramLimit?: number): Type { + + let typeArgCount = typeArgs.length; + + // Make sure the argument list count is correct. + if (paramLimit !== undefined && typeArgCount > paramLimit) { + this._addError( + `Expected at most ${ paramLimit } type arguments`, typeArgs[paramLimit].node); + typeArgCount = paramLimit; + } + + let specializedType = classType.cloneForSpecialization(); + specializedType.setTypeArguments(typeArgs.map(t => t.type)); + + return this._convertClassToObject(specializedType, flags); + } + + // Unpacks the index expression for a Union type annotation. + private _createUnionType(typeArgs: TypeResult[]): Type { + let types: Type[] = []; + + for (let typeArg of typeArgs) { + if (typeArg.type) { + types.push(typeArg.type); + } + } + + return TypeUtils.combineTypesArray(types); + } + + private _createGenericType(errorNode: ExpressionNode, classType: ClassType, + typeArgs: TypeResult[]): Type { + + // Make sure there's at least one type arg. + if (typeArgs.length === 0) { + this._addError( + `'Generic' requires at least one type argument`, errorNode); + } + + // Make sure that all of the type args are typeVars and are unique. + let uniqueTypeVars: TypeVarType[] = []; + typeArgs.forEach(typeArg => { + if (!(typeArg.type instanceof TypeVarType)) { + this._addError( + `Type argument for 'Generic' must be a type variable`, typeArg.node); + } else { + for (let typeVar of uniqueTypeVars) { + if (typeVar === typeArg.type) { + this._addError( + `Type argument for 'Generic' must be unique`, typeArg.node); + break; + } + } + + uniqueTypeVars.push(typeArg.type); + } + }); + + return this._createSpecialType(classType, typeArgs, EvaluatorFlags.None); + } + + private _createSpecializedClassType(classType: ClassType, typeArgs: TypeResult[]): 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(`Expected no type arguments`, + this._addError(`Expected no type arguments`, typeArgs[typeParameters.length].node); } else { - diagSink.addErrorWithTextRange( + this._addError( `Expected at most ${ typeParameters.length } type arguments`, typeArgs[typeParameters.length].node); } @@ -869,44 +846,52 @@ export class TypeAnnotation { return specializedClass; } - private static _createSpecialType(classType: ClassType, typeArgs: TypeResult[], - diagSink: TextRangeDiagnosticSink, paramLimit?: number): Type { + private _applyTypeConstraint(node: ExpressionNode, unconstrainedType: Type): Type { + // Apply constraints associated with the expression we're + // currently walking. + let constrainedType = unconstrainedType; + this._expressionTypeConstraints.forEach(constraint => { + constrainedType = constraint.applyToType(node, constrainedType); + }); - let typeArgCount = typeArgs.length; - - // Make sure the argument list count is correct. - if (paramLimit !== undefined && typeArgCount > paramLimit) { - diagSink.addErrorWithTextRange( - `Expected at most ${ paramLimit } type arguments`, typeArgs[paramLimit].node); - typeArgCount = paramLimit; - } - - let specializedType = classType.cloneForSpecialization(); - specializedType.setTypeArguments(typeArgs.map(t => t.type)); - - return specializedType; + // Apply constraints from the current scope and its outer scopes. + return this._applyScopeTypeConstraintRecursive(node, constrainedType); } - private static _getBooleanValue(node: ExpressionNode, diagSink: TextRangeDiagnosticSink): boolean { - if (node instanceof ConstantNode) { - if (node.token instanceof KeywordToken) { - if (node.token.keywordType === KeywordType.False) { - return false; - } else if (node.token.keywordType === KeywordType.True) { - return true; - } + private _applyScopeTypeConstraintRecursive(node: ExpressionNode, type: Type, + scope = this._scope): Type { + // If we've hit a permanent scope, don't recurse any further. + if (scope.getType() !== ScopeType.Temporary) { + return type; + } + + // Recursively allow the parent scopes to apply their type constraints. + const parentScope = scope.getParent(); + if (parentScope) { + type = this._applyScopeTypeConstraintRecursive(node, type, parentScope); + } + + // Apply the constraints within the current scope. + scope.getTypeConstraints().forEach(constraint => { + type = constraint.applyToType(node, type); + }); + + return type; + } + + private _convertClassToObject(type: Type, flags: EvaluatorFlags): Type { + if (flags & EvaluatorFlags.ConvertClassToObject) { + if (type instanceof ClassType) { + type = new ObjectType(type); } } - diagSink.addErrorWithTextRange('Expected True or False', node); - return false; - } - - private static _convertClassToObject(type: Type): Type { - if (type instanceof ClassType) { - type = new ObjectType(type); - } - return type; } + + private _addError(message: string, range: TextRange) { + if (this._diagnosticSink) { + this._diagnosticSink.addErrorWithTextRange(message, range); + } + } } diff --git a/server/src/analyzer/semanticAnalyzer.ts b/server/src/analyzer/semanticAnalyzer.ts index 4c1d8260a..711da966c 100644 --- a/server/src/analyzer/semanticAnalyzer.ts +++ b/server/src/analyzer/semanticAnalyzer.ts @@ -28,8 +28,10 @@ import { AssignmentNode, ClassNode, DelNode, ExceptNode, ExpressionNode, ForNode ModuleNameNode, ModuleNode, NameNode, NonlocalNode, ParameterCategory, ParameterNode, RaiseNode, ReturnNode, StarExpressionNode, TryNode, TupleExpressionNode, TypeAnnotationExpressionNode, WithNode } from '../parser/parseNodes'; +import { ScopeUtils } from '../scopeUtils'; import { AnalyzerFileInfo } from './analyzerFileInfo'; import { AnalyzerNodeInfo } from './analyzerNodeInfo'; +import { EvaluatorFlags, ExpressionEvaluator } from './expressionEvaluator'; import { ExpressionUtils } from './expressionUtils'; import { ImportType } from './importResult'; import { DefaultTypeSourceId } from './inferredType'; @@ -37,7 +39,6 @@ import { ParseTreeUtils } from './parseTreeUtils'; import { ParseTreeWalker } from './parseTreeWalker'; import { Scope, ScopeType } from './scope'; import { Declaration, Symbol, SymbolCategory } from './symbol'; -import { TypeAnnotation } from './typeAnnotation'; import { AnyType, ClassType, ClassTypeFlags, FunctionParameter, FunctionType, FunctionTypeFlags, ModuleType, OverloadedFunctionType, Type, TypeCategory, UnboundType, UnknownType } from './types'; import { TypeUtils } from './typeUtils'; @@ -139,6 +140,7 @@ export abstract class SemanticAnalyzer extends ParseTreeWalker { } let sawMetaclass = false; + let evaluator = new ExpressionEvaluator(this._currentScope, [], this._fileInfo.diagnosticSink); node.arguments.forEach(arg => { let argType: Type; @@ -147,8 +149,7 @@ export abstract class SemanticAnalyzer extends ParseTreeWalker { // time because forward declarations are supported in stub files. argType = UnknownType.create(); } else { - argType = TypeAnnotation.getType(arg.valueExpression, - this._currentScope, this._fileInfo.diagnosticSink, false); + argType = evaluator.getType(arg.valueExpression, EvaluatorFlags.None); } let isMetaclass = false; @@ -175,7 +176,7 @@ export abstract class SemanticAnalyzer extends ParseTreeWalker { }); if (node.arguments.length === 0) { - let objectType = TypeAnnotation.getBuiltInType(this._currentScope, 'object'); + let objectType = ScopeUtils.getBuiltInType(this._currentScope, 'object'); // Make sure we don't have 'object' derive from itself. Infinite // recursion will result. if (objectType !== classType) { @@ -242,8 +243,8 @@ export abstract class SemanticAnalyzer extends ParseTreeWalker { // Handle overload decorators specially. let overloadedType: OverloadedFunctionType | undefined; - [overloadedType, warnIfDuplicate] = TypeAnnotation.getOverloadedFunctionType(node, - functionType, this._currentScope); + let evaluator = new ExpressionEvaluator(this._currentScope, []); + [overloadedType, warnIfDuplicate] = evaluator.getOverloadedFunctionType(node, functionType); if (overloadedType) { functionType.clearHasCustomDecoratorsFlag(); decoratedType = overloadedType; @@ -251,7 +252,7 @@ export abstract class SemanticAnalyzer extends ParseTreeWalker { // Determine if the function is a property getter or setter. if (ParseTreeUtils.isFunctionInClass(node)) { functionType.clearHasCustomDecoratorsFlag(); - let propertyType = TypeAnnotation.getPropertyType(node, functionType, this._currentScope); + let propertyType = evaluator.getPropertyType(node, functionType); if (propertyType) { decoratedType = propertyType; @@ -932,13 +933,13 @@ export class ModuleScopeAnalyzer extends SemanticAnalyzer { private _bindImplicitNames() { // List taken from https://docs.python.org/3/reference/import.html#__name__ - this._bindNameToType('__name__', TypeAnnotation.getBuiltInObject(this._currentScope, 'str')); + this._bindNameToType('__name__', ScopeUtils.getBuiltInObject(this._currentScope, 'str')); this._bindNameToType('__loader__', AnyType.create()); - this._bindNameToType('__package__', TypeAnnotation.getBuiltInObject(this._currentScope, 'str')); + this._bindNameToType('__package__', ScopeUtils.getBuiltInObject(this._currentScope, 'str')); this._bindNameToType('__spec__', AnyType.create()); - this._bindNameToType('__path__', TypeAnnotation.getBuiltInObject(this._currentScope, 'str')); - this._bindNameToType('__file__', TypeAnnotation.getBuiltInObject(this._currentScope, 'str')); - this._bindNameToType('__cached__', TypeAnnotation.getBuiltInObject(this._currentScope, 'str')); + this._bindNameToType('__path__', ScopeUtils.getBuiltInObject(this._currentScope, 'str')); + this._bindNameToType('__file__', ScopeUtils.getBuiltInObject(this._currentScope, 'str')); + this._bindNameToType('__cached__', ScopeUtils.getBuiltInObject(this._currentScope, 'str')); } } @@ -980,7 +981,7 @@ export class ClassScopeAnalyzer extends SemanticAnalyzer { assert(classType instanceof ClassType); this._bindNameToType('__class__', classType!); this._bindNameToType('__dict__', AnyType.create()); - this._bindNameToType('__name__', TypeAnnotation.getBuiltInObject(this._currentScope, 'str')); + this._bindNameToType('__name__', ScopeUtils.getBuiltInObject(this._currentScope, 'str')); } } @@ -1022,12 +1023,12 @@ export class FunctionScopeAnalyzer extends SemanticAnalyzer { private _bindImplicitNames() { // List taken from https://docs.python.org/3/reference/datamodel.html - this._bindNameToType('__doc__', TypeAnnotation.getBuiltInObject(this._currentScope, 'str')); - this._bindNameToType('__name__', TypeAnnotation.getBuiltInObject(this._currentScope, 'str')); + this._bindNameToType('__doc__', ScopeUtils.getBuiltInObject(this._currentScope, 'str')); + this._bindNameToType('__name__', ScopeUtils.getBuiltInObject(this._currentScope, 'str')); if (this._fileInfo.executionEnvironment.pythonVersion >= PythonVersion.V33) { - this._bindNameToType('__qualname__', TypeAnnotation.getBuiltInObject(this._currentScope, 'str')); + this._bindNameToType('__qualname__', ScopeUtils.getBuiltInObject(this._currentScope, 'str')); } - this._bindNameToType('__module__', TypeAnnotation.getBuiltInObject(this._currentScope, 'str')); + this._bindNameToType('__module__', ScopeUtils.getBuiltInObject(this._currentScope, 'str')); this._bindNameToType('__defaults__', AnyType.create()); this._bindNameToType('__code__', AnyType.create()); this._bindNameToType('__globals__', AnyType.create()); diff --git a/server/src/analyzer/typeAnalyzer.ts b/server/src/analyzer/typeAnalyzer.ts index 1a456ac3b..59b967746 100644 --- a/server/src/analyzer/typeAnalyzer.ts +++ b/server/src/analyzer/typeAnalyzer.ts @@ -24,8 +24,10 @@ import { ArgumentCategory, AssignmentNode, AwaitExpressionNode, BinaryExpression TypeAnnotationExpressionNode, UnaryExpressionNode, WithNode, YieldExpressionNode, YieldFromExpressionNode } from '../parser/parseNodes'; import { KeywordType, OperatorType, QuoteTypeFlags } from '../parser/tokenizerTypes'; +import { ScopeUtils } from '../scopeUtils'; import { AnalyzerFileInfo } from './analyzerFileInfo'; import { AnalyzerNodeInfo } from './analyzerNodeInfo'; +import { EvaluatorFlags, ExpressionEvaluator } from './expressionEvaluator'; import { ExpressionUtils } from './expressionUtils'; import { ImportResult } from './importResult'; import { DefaultTypeSourceId, TypeSourceId } from './inferredType'; @@ -33,7 +35,6 @@ import { ParseTreeUtils } from './parseTreeUtils'; import { ParseTreeWalker } from './parseTreeWalker'; import { Scope, ScopeType } from './scope'; import { Declaration, Symbol, SymbolCategory, SymbolTable } from './symbol'; -import { TypeAnnotation } from './typeAnnotation'; import { TypeConstraint, TypeConstraintBuilder, TypeConstraintResults } from './typeConstraint'; import { AnyType, ClassType, ClassTypeFlags, FunctionType, FunctionTypeFlags, ModuleType, NoneType, ObjectType, OverloadedFunctionType, PropertyType, TupleType, Type, TypeCategory, @@ -111,9 +112,10 @@ export class TypeAnalyzer extends ParseTreeWalker { let classType = AnalyzerNodeInfo.getExpressionType(node) as ClassType; assert(classType instanceof ClassType); + let evaluator = this._getEvaluator(); + node.arguments.forEach((arg, index) => { - let argType = TypeAnnotation.getType(arg.valueExpression, - this._currentScope, this._getConditionalDiagnosticSink(), false); + let argType = evaluator.getType(arg.valueExpression, EvaluatorFlags.None); // In some stub files, classes are conditionally defined (e.g. based // on platform type). We'll assume that the conditional logic is correct @@ -169,14 +171,16 @@ export class TypeAnalyzer extends ParseTreeWalker { functionType.setSpecialBuiltInName(node.name.nameToken.value); } + let evaluator = this._getEvaluator(); + const functionParams = functionType.getParameters(); node.parameters.forEach((param, index) => { let annotatedType: Type | undefined; if (param.typeAnnotation) { this.walk(param.typeAnnotation.expression); - annotatedType = TypeAnnotation.getType(param.typeAnnotation.expression, - this._currentScope, this._getConditionalDiagnosticSink()); + annotatedType = evaluator.getType(param.typeAnnotation.expression, + EvaluatorFlags.ConvertClassToObject); // PEP 484 indicates that if a parameter has a default value of 'None' // the type checker should assume that the type is optional (i.e. a union @@ -218,8 +222,8 @@ export class TypeAnalyzer extends ParseTreeWalker { if (node.returnTypeAnnotation) { this.walk(node.returnTypeAnnotation.expression); - const returnType = TypeAnnotation.getType(node.returnTypeAnnotation.expression, - this._currentScope, this._getConditionalDiagnosticSink()); + const returnType = evaluator.getType(node.returnTypeAnnotation.expression, + EvaluatorFlags.ConvertClassToObject); if (functionType.setDeclaredReturnType(returnType)) { this._setAnalysisChanged(); } @@ -327,15 +331,13 @@ export class TypeAnalyzer extends ParseTreeWalker { // Handle overload decorators specially. let overloadedType: OverloadedFunctionType | undefined; - [overloadedType] = TypeAnnotation.getOverloadedFunctionType(node, - functionType, this._currentScope); + [overloadedType] = evaluator.getOverloadedFunctionType(node, functionType); if (overloadedType) { decoratedType = overloadedType; } else { // Determine if the function is a property getter or setter. if (ParseTreeUtils.isFunctionInClass(node)) { - let propertyType = TypeAnnotation.getPropertyType( - node, functionType, this._currentScope); + let propertyType = evaluator.getPropertyType(node, functionType); if (propertyType) { decoratedType = propertyType; } @@ -642,8 +644,8 @@ export class TypeAnalyzer extends ParseTreeWalker { if (node.typeExpression && node.name) { this._currentScope.addUnboundSymbol(node.name.nameToken.value); - let exceptionType = TypeAnnotation.getType(node.typeExpression, - this._currentScope, this._getConditionalDiagnosticSink(), false); + let evaluator = this._getEvaluator(); + let exceptionType = evaluator.getType(node.typeExpression, EvaluatorFlags.None); // If more than one type was specified for the exception, // handle that here. @@ -720,7 +722,7 @@ export class TypeAnalyzer extends ParseTreeWalker { ClassTypeFlags.BuiltInClass | ClassTypeFlags.SpecialBuiltIn, DefaultTypeSourceId); - let aliasClass = TypeAnnotation.getBuiltInType(this._currentScope, + let aliasClass = ScopeUtils.getBuiltInType(this._currentScope, assignedName.toLowerCase()); if (aliasClass instanceof ClassType) { specialClassType.addBaseClass(aliasClass, false); @@ -975,7 +977,7 @@ export class TypeAnalyzer extends ParseTreeWalker { ClassTypeFlags.BuiltInClass | ClassTypeFlags.SpecialBuiltIn, AnalyzerNodeInfo.getTypeSourceId(node)); - let aliasClass = TypeAnnotation.getBuiltInType(this._currentScope, + let aliasClass = ScopeUtils.getBuiltInType(this._currentScope, assignedName.toLowerCase()); if (aliasClass instanceof ClassType) { specialClassType.addBaseClass(aliasClass, false); @@ -1000,8 +1002,9 @@ export class TypeAnalyzer extends ParseTreeWalker { } } - let typeHint = TypeAnnotation.getType(node.typeAnnotation.expression, - this._currentScope, this._getConditionalDiagnosticSink()); + let evaluator = this._getEvaluator(); + let typeHint = evaluator.getType(node.typeAnnotation.expression, + EvaluatorFlags.ConvertClassToObject); if (typeHint) { if (!(node.valueExpression instanceof NameNode) || !this._assignTypeForPossibleEnumeration(node.valueExpression, typeHint)) { @@ -1119,7 +1122,7 @@ export class TypeAnalyzer extends ParseTreeWalker { const classType = exceptionType.getClassType(); const validTypes = ['list', 'tuple', 'set']; const isValid = validTypes.find(t => { - const builtInType = TypeAnnotation.getBuiltInType(this._currentScope, t); + const builtInType = ScopeUtils.getBuiltInType(this._currentScope, t); if (!builtInType || !(builtInType instanceof ClassType)) { return false; } @@ -1273,21 +1276,21 @@ export class TypeAnalyzer extends ParseTreeWalker { exprType = this._getTypeOfName(node.nameToken.value); } else if (node instanceof StringNode) { if (node.tokens[0].quoteTypeFlags & QuoteTypeFlags.Byte) { - exprType = TypeAnnotation.getBuiltInObject(this._currentScope, 'byte'); + exprType = ScopeUtils.getBuiltInObject(this._currentScope, 'byte'); } else { - exprType = TypeAnnotation.getBuiltInObject(this._currentScope, 'str'); + exprType = ScopeUtils.getBuiltInObject(this._currentScope, 'str'); } } else if (node instanceof NumberNode) { if (node.token.isInteger) { - exprType = TypeAnnotation.getBuiltInObject(this._currentScope, 'int'); + exprType = ScopeUtils.getBuiltInObject(this._currentScope, 'int'); } else { - exprType = TypeAnnotation.getBuiltInObject(this._currentScope, 'float'); + exprType = ScopeUtils.getBuiltInObject(this._currentScope, 'float'); } } else if (node instanceof ConstantNode) { if (node.token.keywordType === KeywordType.True || node.token.keywordType === KeywordType.False || node.token.keywordType === KeywordType.Debug) { - exprType = TypeAnnotation.getBuiltInObject(this._currentScope, 'bool'); + exprType = ScopeUtils.getBuiltInObject(this._currentScope, 'bool'); } else { assert(node.token.keywordType === KeywordType.None); exprType = NoneType.create(); @@ -1308,7 +1311,7 @@ export class TypeAnalyzer extends ParseTreeWalker { this._getTypeOfExpression(node.rightExpression); } else if (node instanceof TupleExpressionNode) { let tupleType = new TupleType( - TypeAnnotation.getBuiltInType(this._currentScope, 'tuple') as ClassType); + ScopeUtils.getBuiltInType(this._currentScope, 'tuple') as ClassType); node.expressions.forEach(expr => { tupleType.addEntryType(this._getTypeOfExpression(expr)); }); @@ -1320,7 +1323,7 @@ export class TypeAnalyzer extends ParseTreeWalker { this._getTypeOfExpression(expr); }); // TODO - infer list type - exprType = TypeAnnotation.getBuiltInObject( + exprType = ScopeUtils.getBuiltInObject( this._currentScope, 'list', []); } else if (node instanceof SliceExpressionNode) { // TODO - need to implement @@ -1334,7 +1337,7 @@ export class TypeAnalyzer extends ParseTreeWalker { this._getTypeOfExpression(node.stepValue); } // TODO - infer set type - exprType = TypeAnnotation.getBuiltInObject( + exprType = ScopeUtils.getBuiltInObject( this._currentScope, 'set', []); } else if (node instanceof AwaitExpressionNode) { // TODO - need to implement @@ -1348,11 +1351,11 @@ export class TypeAnalyzer extends ParseTreeWalker { } else if (node instanceof ListComprehensionNode) { // TODO - infer list type this._getTypeOfExpression(node.baseExpression); - exprType = TypeAnnotation.getBuiltInObject( + exprType = ScopeUtils.getBuiltInObject( this._currentScope, 'list', []); } else if (node instanceof DictionaryNode) { // TODO - infer dict type - exprType = TypeAnnotation.getBuiltInObject( + exprType = ScopeUtils.getBuiltInObject( this._currentScope, 'dict', []); } else if (node instanceof LambdaNode) { exprType = AnalyzerNodeInfo.getExpressionType(node); @@ -1361,7 +1364,7 @@ export class TypeAnalyzer extends ParseTreeWalker { this._getTypeOfExpression(expr); }); // TODO - infer set type - exprType = TypeAnnotation.getBuiltInObject( + exprType = ScopeUtils.getBuiltInObject( this._currentScope, 'set', []); } else if (node instanceof AssignmentNode) { this._getTypeOfExpression(node.rightExpression); @@ -1391,8 +1394,9 @@ export class TypeAnalyzer extends ParseTreeWalker { // Determine if this is a generic class or function. if (baseType instanceof ClassType) { if (baseType.isGeneric() || baseType.isSpecialBuiltIn()) { - [baseType] = TypeAnnotation.specializeClassType(baseType, node.indexExpression, - this._currentScope, this._fileInfo.diagnosticSink); + let evaluator = this._getEvaluator(); + baseType = evaluator.specializeClassType(baseType, node.indexExpression, + EvaluatorFlags.None); } return baseType; @@ -1410,7 +1414,7 @@ export class TypeAnalyzer extends ParseTreeWalker { let leftType = this._getTypeOfExpression(node.leftExpression); let memberName = node.memberName.nameToken.value; - if (memberName && !leftType.isAny()) { + if (!leftType.isAny()) { if (leftType instanceof ObjectType) { let classMemberType = TypeUtils.lookUpClassMember( leftType.getClassType(), node.memberName.nameToken.value, true); @@ -1457,8 +1461,8 @@ export class TypeAnalyzer extends ParseTreeWalker { // The stdlib collections.pyi stub file defines namedtuple as a function // rather than a class, so we need to check for it here. if (callType.getSpecialBuiltInName() === 'namedtuple') { - exprType = TypeAnnotation.createNamedTupleType(node, false, - this._currentScope, this._fileInfo.diagnosticSink); + let evaluator = this._getEvaluator(); + exprType = evaluator.createNamedTupleType(node, false); } else { exprType = callType.getEffectiveReturnType(); } @@ -1488,12 +1492,12 @@ export class TypeAnalyzer extends ParseTreeWalker { exprType = UnknownType.create(); } } else if (className === 'TypeVar') { - exprType = TypeAnnotation.getTypeVarType(node, - this._currentScope, this._getConditionalDiagnosticSink()); + let evaluator = this._getEvaluator(); + exprType = evaluator.createTypeVarType(node); } else if (className === 'NamedTuple') { // Handle the NamedTuple case specially because it's a class factory. - exprType = TypeAnnotation.createNamedTupleType(node, true, - this._currentScope, this._getConditionalDiagnosticSink()); + let evaluator = this._getEvaluator(); + exprType = evaluator.createNamedTupleType(node, true); } } @@ -1614,8 +1618,9 @@ export class TypeAnalyzer extends ParseTreeWalker { }); } } else if (target instanceof TypeAnnotationExpressionNode) { - let typeHint = TypeAnnotation.getType(target.typeAnnotation.expression, - this._currentScope, this._getConditionalDiagnosticSink()); + let evaluator = this._getEvaluator(); + let typeHint = evaluator.getType(target.typeAnnotation.expression, + EvaluatorFlags.ConvertClassToObject); if (!TypeUtils.canAssignType(typeHint, type)) { this._addError( @@ -2177,15 +2182,16 @@ export class TypeAnalyzer extends ParseTreeWalker { (node: ExpressionNode) => this._getTypeOfExpressionWithTypeConstraints(node)); } - private _applyTypeConstraint(node: ExpressionNode, type: Type): Type { + private _applyTypeConstraint(node: ExpressionNode, unconstrainedType: Type): Type { // Apply constraints associated with the expression we're // currently walking. + let constrainedType = unconstrainedType; this._expressionTypeConstraints.forEach(constraint => { - type = constraint.applyToType(node, type); + constrainedType = constraint.applyToType(node, constrainedType); }); // Apply constraints from the current scope and its outer scopes. - return this._applyScopeTypeConstraintRecursive(node, type); + return this._applyScopeTypeConstraintRecursive(node, constrainedType); } private _applyScopeTypeConstraintRecursive(node: ExpressionNode, type: Type, @@ -2267,14 +2273,17 @@ export class TypeAnalyzer extends ParseTreeWalker { } } - private _getConditionalDiagnosticSink(): TextRangeDiagnosticSink { + private _getEvaluator() { + let diagSink: TextRangeDiagnosticSink | undefined = this._fileInfo.diagnosticSink; + // If the current scope isn't executed, create a dummy sink // for any errors that are reported. if (this._currentScope.isNotExecuted() && !this._isDiagnosticsSuppressed) { - return new TextRangeDiagnosticSink(this._fileInfo.lines); + diagSink = undefined; } - return this._fileInfo.diagnosticSink; + return new ExpressionEvaluator(this._currentScope, + this._expressionTypeConstraints, diagSink); } private _setAnalysisChanged() { diff --git a/server/src/analyzer/typeUtils.ts b/server/src/analyzer/typeUtils.ts index 005f92482..4af2ab220 100644 --- a/server/src/analyzer/typeUtils.ts +++ b/server/src/analyzer/typeUtils.ts @@ -12,8 +12,7 @@ import { ParameterCategory } from '../parser/parseNodes'; import { Symbol } from './symbol'; import { ClassType, FunctionType, ObjectType, OverloadedFunctionType, TupleType, Type, TypeCategory, TypeVarType, UnboundType, - UnionType, - UnknownType } from './types'; + UnionType, UnknownType } from './types'; export interface ClassMember { symbol?: Symbol; diff --git a/server/src/analyzer/types.ts b/server/src/analyzer/types.ts index 8b5c3aec1..5bcb64223 100644 --- a/server/src/analyzer/types.ts +++ b/server/src/analyzer/types.ts @@ -420,7 +420,7 @@ export class ClassType extends Type { asStringInternal(recursionCount = 0): string { // Return the same string that we'd use for an instance // of the class. - return this.getObjectName(recursionCount + 1); + return 'Type[' + this.getObjectName(recursionCount + 1) + ']'; } // Determines whether this is a subclass (derived class) diff --git a/server/src/scopeUtils.ts b/server/src/scopeUtils.ts new file mode 100644 index 000000000..ef67baae6 --- /dev/null +++ b/server/src/scopeUtils.ts @@ -0,0 +1,46 @@ +/* +* scopeUtils.ts +* Copyright (c) Microsoft Corporation. All rights reserved. +* Author: Eric Traut +* +* Static utility methods related to scopes and their related +* symbol tables. +*/ + +import { Scope, ScopeType } from './analyzer/scope'; +import { ClassType, ObjectType, Type, UnknownType } from './analyzer/types'; + +export class ScopeUtils { + static getBuiltInType(currentScope: Scope, name: string): Type { + // Starting at the current scope, find the built-in scope, which should + // be the top-most parent. + let builtInScope = currentScope; + while (builtInScope.getType() !== ScopeType.BuiltIn) { + builtInScope = builtInScope.getParent()!; + } + + let nameType = builtInScope.lookUpSymbol(name); + if (nameType) { + return nameType.currentType; + } + + return UnknownType.create(); + } + + static getBuiltInObject(currentScope: Scope, className: string, + typeArguments?: Type[]): Type { + + let nameType = this.getBuiltInType(currentScope, className); + if (nameType instanceof ClassType) { + let classType = nameType; + if (typeArguments) { + classType = classType.cloneForSpecialization(); + classType.setTypeArguments(typeArguments); + } + + return new ObjectType(classType); + } + + return nameType; + } +}