Fixed a false positive error introduced with abstract class instantiation test.

This commit is contained in:
Eric Traut 2019-04-10 00:50:51 -07:00
parent 138abaa2dd
commit f58ee2b8f4
5 changed files with 35 additions and 13 deletions

View File

@ -726,6 +726,7 @@ export class ExpressionEvaluator {
flags &= ~EvaluatorFlags.ConvertClassToObject;
}
} else if (callType.isAbstractClass()) {
// If the class is abstract, it can't be instantiated.
const symbolTable = new SymbolTable();
TypeUtils.getAbstractMethodsRecursive(callType, symbolTable);

View File

@ -269,23 +269,25 @@ export class TypeAnalyzer extends ParseTreeWalker {
} else if (index === 0 && (functionType.isInstanceMethod() || functionType.isClassMethod())) {
// Specify type of "self" or "cls" parameter for instance or class methods
// if the type is not explicitly provided.
const classNode = ParseTreeUtils.getEnclosingClass(node);
if (classNode) {
const paramType = functionType.getParameters()[index].type;
if (containingClassType) {
const paramType = functionType.getParameters()[0].type;
if (paramType instanceof UnknownType) {
const inferredClassType = AnalyzerNodeInfo.getExpressionType(classNode) as ClassType;
// Don't specialize the "self" for protocol classes because type
// comparisons will fail during structural typing analysis.
if (inferredClassType && !inferredClassType.isProtocol()) {
const specializedClassType = TypeUtils.selfSpecializeClassType(inferredClassType);
if (containingClassType && !containingClassType.isProtocol()) {
if (functionType.isInstanceMethod()) {
const specializedClassType = TypeUtils.selfSpecializeClassType(
containingClassType);
if (functionType.setParameterType(index, new ObjectType(specializedClassType))) {
this._setAnalysisChanged();
}
} else if (functionType.isClassMethod()) {
// For class methods, the cls parameter is allowed to skip the
// abstract class test because the caller is possibly passing
// in a non-abstract subclass.
const specializedClassType = TypeUtils.selfSpecializeClassType(
containingClassType, true);
if (functionType.setParameterType(index, specializedClassType)) {
this._setAnalysisChanged();
}

View File

@ -989,13 +989,13 @@ export class TypeUtils {
// If the class is generic, the type is cloned, and its own
// type parameters are used as type arguments. This is useful
// for typing "self" or "cls" within a class's implementation.
static selfSpecializeClassType(type: ClassType): ClassType {
if (!type.isGeneric()) {
static selfSpecializeClassType(type: ClassType, skipAbstractClassTest = false): ClassType {
if (!type.isGeneric() && !skipAbstractClassTest) {
return type;
}
let typeArgs = type.getTypeParameters();
return type.cloneForSpecialization(typeArgs);
return type.cloneForSpecialization(typeArgs, skipAbstractClassTest);
}
// Removes the first parameter of the function and returns a new function.

View File

@ -226,6 +226,8 @@ export class ClassType extends Type {
// parameters are undefined.
private _typeArguments?: Type[];
private _skipAbstractClassTest = false;
constructor(name: string, flags: ClassTypeFlags, typeSourceId: TypeSourceId) {
super();
@ -242,11 +244,14 @@ export class ClassType extends Type {
};
}
cloneForSpecialization(typeArguments: Type[]): ClassType {
cloneForSpecialization(typeArguments: Type[], skipAbstractClassTest = false): ClassType {
let newClassType = new ClassType(this._classDetails.name,
this._classDetails.flags, this._classDetails.typeSourceId);
newClassType._classDetails = this._classDetails;
newClassType.setTypeArguments(typeArguments);
if (skipAbstractClassTest) {
newClassType._setSkipAbstracClassTest();
}
return newClassType;
}
@ -300,7 +305,8 @@ export class ClassType extends Type {
}
isAbstractClass() {
return this._classDetails.isAbstractClass;
return this._classDetails.isAbstractClass &&
!this._skipAbstractClassTest;
}
getClassName() {
@ -539,6 +545,10 @@ export class ClassType extends Type {
return false;
}
private _setSkipAbstracClassTest() {
this._skipAbstractClassTest = true;
}
}
export class ObjectType extends Type {

View File

@ -15,6 +15,15 @@ class AbstractFoo(ABC):
def foo3(self):
return 3
@classmethod
def foo4(cls):
# This should not generate an error even though
# it would appear to be attempting to instantiate
# an abstract class. That's because we need to
# assume that the caller is making this call on
# a non-abstract subclass.
return cls()
# This should generate an error because AbstractFoo
# is an abstract class.