Fixed more bugs in expression evaluator.

This commit is contained in:
Eric Traut 2019-03-16 12:14:27 -07:00
parent 5c0c0ce016
commit 64ef65546b
4 changed files with 121 additions and 204 deletions

View File

@ -69,73 +69,6 @@ export class ExpressionEvaluator {
return typeResult.type;
}
// 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')) {
@ -300,52 +233,40 @@ export class ExpressionEvaluator {
return typeResult;
}
private _getTypeFromName(node: NameNode, flags: EvaluatorFlags): TypeResult | undefined {
private _getTypeFromName(node: NameNode, flags: EvaluatorFlags): TypeResult {
const name = node.nameToken.value;
let type: Type | undefined;
// Look for the scope that contains the value definition and
// see if it has a declared type.
let scope: Scope | undefined = this._scope;
let beyondLocalScope = false;
while (scope) {
let symbol = scope.lookUpSymbol(name);
if (symbol) {
let declaration = symbol.declarations ? symbol.declarations[0] : undefined;
const symbolWithScope = this._scope.lookUpSymbolRecursive(name);
if (symbolWithScope) {
const symbol = symbolWithScope.symbol;
let declaration = symbol.declarations ? symbol.declarations[0] : undefined;
if (declaration && declaration.declaredType) {
// Was there a defined type hint?
if (declaration && declaration.declaredType) {
type = declaration.declaredType;
break;
}
type = declaration.declaredType;
} else if (declaration && declaration.category !== SymbolCategory.Variable) {
// If this is a non-variable type (e.g. a class, function, method), we
// can assume that it's not going to be modified outside the local scope.
if (declaration && declaration.category !== SymbolCategory.Variable) {
type = symbol.currentType;
break;
}
type = symbol.currentType;
} else if (symbolWithScope.isBeyondLocalScope) {
// If we haven't already gone beyond the local scope, we can
// trust the current type. If we've moved beyond the local
// scope to some other outer scope (e.g. the global scope), we
// cannot trust the current type.
if (beyondLocalScope) {
type = symbol.inferredType.getType();
} else {
type = symbol.currentType;
}
break;
type = symbol.inferredType.getType();
} else {
type = symbol.currentType;
}
if (scope.getType() !== ScopeType.Temporary) {
beyondLocalScope = true;
}
scope = scope.getParent();
}
if (!type) {
return undefined;
this._addError(`'${ name }' is not a known symbol`, node);
type = UnknownType.create();
}
type = this._convertClassToObject(type, flags);
@ -458,6 +379,11 @@ export class ExpressionEvaluator {
} else if (baseType instanceof PropertyType) {
// TODO - need to come up with new strategy for properties
type = UnknownType.create();
} else if (baseType instanceof FunctionType) {
if (baseType.hasCustomDecorators()) {
// TODO - deal with custom decorators in a better way
type = UnknownType.create();
}
}
if (!type) {
@ -480,21 +406,26 @@ export class ExpressionEvaluator {
this._validateTypeArgs(typeArgs);
if (baseType instanceof ClassType) {
type = this.specializeClassType(baseType, node.indexExpression, flags);
if (baseType.isAny()) {
type = baseType;
} else if (baseType instanceof ClassType) {
type = this._createSpecializeClassType(baseType, node.indexExpression, flags);
} else if (baseType instanceof UnionType) {
// TODO - need to implement
type = UnknownType.create();
} else if (baseType instanceof FunctionType) {
// TODO - need to implement
} else if (!baseType.isAny()) {
if ((flags & EvaluatorFlags.ConvertClassToObject) !== 0) {
this._addError(
`'Unsupported type expression: indexed (${ baseType.asString() })`,
node.baseExpression);
}
type = UnknownType.create();
} else if (baseType instanceof ObjectType) {
// TODO - need to implement
type = UnknownType.create();
}
if (!type) {
this._addError(
`'Unsupported expression type: indexed (${ baseType.asString() })`,
node.baseExpression);
type = UnknownType.create();
}
@ -1177,6 +1108,74 @@ export class ExpressionEvaluator {
return type;
}
// 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).
private _createSpecializeClassType(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);
}
private _convertClassToObject(type: Type, flags: EvaluatorFlags): Type {
if (flags & EvaluatorFlags.ConvertClassToObject) {
if (type instanceof ClassType) {

View File

@ -132,7 +132,7 @@ export class Scope {
}
lookUpSymbolRecursive(name: string): SymbolWithScope | undefined {
return this._lookUpSymbolRecursiveInternal(name, false);
return this._lookUpSymbolRecursiveInternal(name, false, false);
}
// Adds a new (unbound) symbol to the scope.
@ -351,8 +351,8 @@ export class Scope {
this._typeConstraints.push(constraint);
}
private _lookUpSymbolRecursiveInternal(name: string, isOutsideCallerModule: boolean):
SymbolWithScope | undefined {
private _lookUpSymbolRecursiveInternal(name: string, isOutsideCallerModule: boolean,
isBeyondLocalScope: boolean): SymbolWithScope | undefined {
// If we're searching outside of the original caller's module (global) scope,
// hide any names that are not meant to be visible to importers.
if (isOutsideCallerModule && this._hiddenNameMap[name]) {
@ -363,6 +363,8 @@ export class Scope {
if (symbol) {
return {
symbol,
isBeyondLocalScope,
isOutsideCallerModule,
scope: this
};
}
@ -371,7 +373,8 @@ export class Scope {
// If our recursion is about to take us outside the scope of the current
// module (i.e. into a built-in scope), indicate as such with the second parameter.
return this._parent._lookUpSymbolRecursiveInternal(name,
isOutsideCallerModule || this._scopeType === ScopeType.Global);
isOutsideCallerModule || this._scopeType === ScopeType.Global,
isBeyondLocalScope || this._scopeType !== ScopeType.Temporary);
}
return undefined;
@ -380,5 +383,7 @@ export class Scope {
export interface SymbolWithScope {
symbol: Symbol;
isBeyondLocalScope: boolean;
isOutsideCallerModule: boolean;
scope: Scope;
}

View File

@ -781,7 +781,6 @@ export class TypeAnalyzer extends ParseTreeWalker {
// Set the member type for the hover provider.
this._updateExpressionTypeForNode(node.memberName, this._getTypeOfExpression(node));
// Don't walk the member name.
return false;
}
@ -1709,7 +1708,7 @@ export class TypeAnalyzer extends ParseTreeWalker {
}
}
private _validateMemberAccess(baseType: Type, memberName: NameNode): boolean {
private _validateMemberAccess(baseType: Type, memberName: NameNode): void {
// TODO - most of this logic is now redudnant with the expression evaluation
// logic. The only part that remains is the calls to setDeclaration. Clean
// this up at some point.
@ -1722,75 +1721,21 @@ export class TypeAnalyzer extends ParseTreeWalker {
if (classMemberInfo.symbol && classMemberInfo.symbol.declarations) {
AnalyzerNodeInfo.setDeclaration(memberName, classMemberInfo.symbol.declarations[0]);
}
return true;
} else {
// See if the class has a "__getattribute__" or "__getattr__" method.
// If so, aribrary members are supported.
let getAttribMember = TypeUtils.lookUpClassMember(
baseType.getClassType(), '__getattribute__');
if (getAttribMember && getAttribMember.class) {
const isObjectClass = getAttribMember.class.isBuiltIn() &&
getAttribMember.class.getClassName() === 'object';
// The built-in 'object' class, from which every class derives,
// implements the default __getattribute__ method. We want to ignore
// this one. If this method is overridden, we need to assume that
// all members can be accessed.
if (!isObjectClass) {
return true;
}
}
let getAttrMember = TypeUtils.lookUpClassMember(
baseType.getClassType(), '__getattr__');
if (getAttrMember) {
return true;
}
// If the class has decorators, there may be additional fields
// added that we don't know about.
// TODO - figure out a better approach here.
if (!baseType.getClassType().hasDecorators()) {
this._addError(
`'${ memberNameValue }' is not a known member of type '${ baseType.asString() }'`,
memberName);
}
return false;
}
}
if (baseType instanceof ModuleType) {
let moduleMemberInfo = baseType.getFields().get(memberNameValue);
if (!moduleMemberInfo) {
this._addError(
`'${ memberNameValue }' is not a known member of module`,
memberName);
return false;
}
if (moduleMemberInfo.declarations) {
if (moduleMemberInfo && moduleMemberInfo.declarations) {
AnalyzerNodeInfo.setDeclaration(memberName, moduleMemberInfo.declarations[0]);
}
return true;
}
if (baseType instanceof ClassType) {
let classMemberInfo = TypeUtils.lookUpClassMember(baseType, memberNameValue, false);
if (!classMemberInfo) {
// If the class has decorators, there may be additional fields
// added that we don't know about.
// TODO - figure out a better approach here.
if (!baseType.hasDecorators()) {
this._addError(
`'${ memberNameValue }' is not a known member of '${ baseType.asString() }'`,
memberName);
}
return false;
}
if (classMemberInfo.symbol && classMemberInfo.symbol.declarations) {
if (classMemberInfo && classMemberInfo.symbol && classMemberInfo.symbol.declarations) {
AnalyzerNodeInfo.setDeclaration(memberName, classMemberInfo.symbol.declarations[0]);
}
return true;
}
if (baseType instanceof UnionType) {
@ -1799,44 +1744,12 @@ export class TypeAnalyzer extends ParseTreeWalker {
let simplifiedType = baseType.removeOptional();
if (simplifiedType instanceof UnionType) {
for (let t of simplifiedType.getTypes()) {
if (!this._validateMemberAccess(t, memberName)) {
return false;
}
this._validateMemberAccess(t, memberName);
}
return true;
} else {
this._validateMemberAccess(simplifiedType, memberName);
}
return this._validateMemberAccess(simplifiedType, memberName);
}
if (baseType instanceof UnboundType) {
this._addError(
`'${ memberNameValue }' cannot be accessed from unbound variable`, memberName);
return false;
}
if (baseType instanceof PropertyType) {
// TODO - need to implement this check
return true;
}
if (baseType instanceof FunctionType) {
// TODO - need to implement this check
return true;
}
if (baseType instanceof TypeVarType) {
// TODO - need to handle this check
return true;
}
if (!baseType.isAny()) {
this._addError(
`'${ memberNameValue }' is not a known member of type ${ baseType.asString() }`, memberName);
return false;
}
return true;
}
private _useExpressionTypeConstraint(typeConstraints: TypeConstraintResults | undefined,

View File

@ -10,9 +10,9 @@ import * as assert from 'assert';
import { ParameterCategory } from '../parser/parseNodes';
import { Symbol } from './symbol';
import { ClassType, FunctionType, ObjectType, OverloadedFunctionType,
TupleType, Type, TypeCategory, TypeVarType, UnboundType,
UnionType, UnknownType } from './types';
import { ClassType, FunctionType, ObjectType,
OverloadedFunctionType, TupleType, Type, TypeCategory, TypeVarType,
UnboundType, UnionType, UnknownType } from './types';
export interface ClassMember {
symbol?: Symbol;