Added the ability to clone ClassType instances for specialization purposes.

Hooked up built-in types to their corresponding base classes (e.g. List derives from list).
This commit is contained in:
Eric Traut 2019-03-14 13:40:31 -07:00
parent f3cf0c0a9b
commit 5e47a2fd18
4 changed files with 158 additions and 49 deletions

View File

@ -129,7 +129,8 @@ export abstract class SemanticAnalyzer extends ParseTreeWalker {
classFlags |= ClassTypeFlags.HasDecorators;
}
let classType = new ClassType(node.name.nameToken.value, classFlags);
let classType = new ClassType(node.name.nameToken.value, classFlags,
AnalyzerNodeInfo.getTypeSourceId(node));
// Don't walk the arguments for stub files because of forward
// declarations.

View File

@ -691,8 +691,17 @@ export class TypeAnalyzer extends ParseTreeWalker {
'Set', 'FrozenSet', 'Deque', 'ChainMap'];
if (specialTypes.find(t => t === assignedName)) {
// Synthesize a class.
specialType = new ClassType(assignedName,
ClassTypeFlags.BuiltInClass | ClassTypeFlags.SpecialBuiltIn);
let specialClassType = new ClassType(assignedName,
ClassTypeFlags.BuiltInClass | ClassTypeFlags.SpecialBuiltIn,
DefaultTypeSourceId);
let baseClass = TypeAnnotation.getBuiltInType(this._currentScope,
assignedName.toLowerCase());
if (baseClass instanceof ClassType) {
specialClassType.addBaseClass(baseClass, false);
}
specialType = specialClassType;
}
}
@ -936,8 +945,17 @@ export class TypeAnalyzer extends ParseTreeWalker {
'Final', 'Literal'];
if (specialTypes.find(t => t === assignedName)) {
// Synthesize a class.
specialType = new ClassType(assignedName,
ClassTypeFlags.BuiltInClass | ClassTypeFlags.SpecialBuiltIn);
let specialClassType = new ClassType(assignedName,
ClassTypeFlags.BuiltInClass | ClassTypeFlags.SpecialBuiltIn,
AnalyzerNodeInfo.getTypeSourceId(node));
let baseClass = TypeAnnotation.getBuiltInType(this._currentScope,
assignedName.toLowerCase());
if (baseClass instanceof ClassType) {
specialClassType.addBaseClass(baseClass, false);
}
specialType = specialClassType;
}
if (specialType) {

View File

@ -134,7 +134,8 @@ export class TypeAnnotation {
}
}
let classType = new ClassType(className, ClassTypeFlags.None);
let classType = new ClassType(className, ClassTypeFlags.None,
AnalyzerNodeInfo.getTypeSourceId(node));
const classFields = classType.getClassFields();
classFields.set('__class__', new Symbol(classType, DefaultTypeSourceId));
const instanceFields = classType.getInstanceFields();
@ -437,7 +438,7 @@ export class TypeAnnotation {
switch (className) {
case 'Callable': {
// A 'Callable' with no parameters is a generic function.
type = this._createCallableType([], diagSink);
type = this._createCallableType(type, [], diagSink);
break;
}
@ -450,7 +451,7 @@ export class TypeAnnotation {
case 'Set':
case 'Tuple':
case 'Type': {
type = this._createSpecialType(className, [], currentScope, diagSink);
type = this._createSpecialType(type, [], diagSink);
break;
}
@ -528,14 +529,6 @@ export class TypeAnnotation {
return undefined;
}
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
}
private static _getTypeFromIndexExpression(node: IndexExpressionNode,
currentScope: Scope, diagSink: TextRangeDiagnosticSink): TypeResult {
@ -556,12 +549,14 @@ export class TypeAnnotation {
switch (className) {
case 'Callable': {
type = this._createCallableType(typeArgs, diagSink);
type = this._createCallableType(baseTypeResult.type,
typeArgs, diagSink);
break;
}
case 'Optional': {
type = this._createOptional(node.baseExpression, typeArgs, diagSink);
type = this._createOptional(node.baseExpression,
typeArgs, diagSink);
break;
}
@ -573,27 +568,26 @@ export class TypeAnnotation {
case 'ClassVar':
case 'Deque':
case 'Generic':
case 'List':
case 'FrozenSet':
case 'Set': {
type = this._createSpecialType(className, typeArgs,
currentScope, diagSink, 1);
type = this._createSpecialType(baseTypeResult.type, typeArgs,
diagSink, 1);
break;
}
case 'ChainMap':
case 'Dict':
case 'DefaultDict': {
type = this._createSpecialType(className, typeArgs,
currentScope, diagSink, 2);
type = this._createSpecialType(baseTypeResult.type, typeArgs,
diagSink, 2);
break;
}
case 'Protocol':
case 'Tuple': {
type = this._createSpecialType(className, typeArgs,
currentScope, diagSink);
type = this._createSpecialType(baseTypeResult.type, typeArgs,
diagSink);
break;
}
@ -601,6 +595,11 @@ export class TypeAnnotation {
type = this._createUnionType(typeArgs);
break;
}
case 'Generic':
type = this._createGenericType(node.baseExpression,
baseTypeResult.type, typeArgs, diagSink);
break;
}
}
@ -620,6 +619,14 @@ export class TypeAnnotation {
return { type, isClassType, node };
}
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
}
private static _getTypeArgs(node: ExpressionNode, currentScope: Scope,
diagSink: TextRangeDiagnosticSink): TypeResult[] {
@ -745,16 +752,35 @@ export class TypeAnnotation {
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.
typeArgs.forEach(typeArg => {
if (!(typeArg.type instanceof TypeVarType)) {
diagSink.addErrorWithTextRange(
`Type argument for 'Generic' must be a type variable`, typeArg.node);
}
});
return this._createSpecialType(classType, typeArgs, diagSink);
}
// 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(typeArgs: TypeResult[],
diagSink: TextRangeDiagnosticSink): FunctionType {
private static _createCallableType(classType: Type,
typeArgs: TypeResult[], diagSink: TextRangeDiagnosticSink): FunctionType {
let functionType = new FunctionType(FunctionTypeFlags.None);
functionType.setDeclaredReturnType(AnyType.create());
let paramList: Type[] | undefined;
if (typeArgs.length > 0) {
if (typeArgs[0].typeList) {
@ -785,14 +811,23 @@ export class TypeAnnotation {
return functionType;
}
private static _createSpecialType(className: string, typeArgs: TypeResult[],
currentScope: Scope, diagSink: TextRangeDiagnosticSink,
paramLimit?: number): Type {
private static _createSpecialType(classType: ClassType, typeArgs: TypeResult[],
diagSink: TextRangeDiagnosticSink, paramLimit?: number): Type {
// let typeParam = this.getType(indexExpression, currentScope, diagSink, false);
// TODO - need to implement
let typeArgCount = typeArgs.length;
// Make sure the parameter list count is correct.
if (paramLimit !== undefined && typeArgCount > paramLimit) {
diagSink.addErrorWithTextRange(
`Expected at most ${ paramLimit } type arguments`, typeArgs[paramLimit].node);
typeArgCount = paramLimit;
}
return this.getBuiltInType(currentScope, className.toLowerCase());
let specializedType = classType.cloneForSpecialization();
for (let i = 0; i < typeArgCount; i++) {
specializedType.addTypeArgument(typeArgs[i].type);
}
return specializedType;
}
private static _getBooleanValue(node: ExpressionNode, diagSink: TextRangeDiagnosticSink): boolean {

View File

@ -196,8 +196,9 @@ export interface BaseClass {
}
interface ClassDetails {
classFlags: ClassTypeFlags;
className: string;
name: string;
flags: ClassTypeFlags;
typeSourceId: TypeSourceId;
baseClasses: BaseClass[];
classFields: SymbolTable;
instanceFields: SymbolTable;
@ -213,14 +214,15 @@ export class ClassType extends Type {
// specialized will have type arguments that correspond to
// some or all of the type parameters. Unspecified type
// parameters are undefined.
private _typeArguments?: (Type | undefined)[];
private _typeArguments?: (Type | Type[])[];
constructor(name: string, flags: ClassTypeFlags) {
constructor(name: string, flags: ClassTypeFlags, typeSourceId: TypeSourceId) {
super();
this._classDetails = {
className: name,
classFlags: flags,
name,
flags,
typeSourceId,
baseClasses: [],
classFields: new SymbolTable(),
instanceFields: new SymbolTable(),
@ -228,12 +230,19 @@ export class ClassType extends Type {
};
}
cloneForSpecialization(): ClassType {
let newClassType = new ClassType(this._classDetails.name,
this._classDetails.flags, this._classDetails.typeSourceId);
newClassType._classDetails = this._classDetails;
return newClassType;
}
isSpecialBuiltIn() {
return !!(this._classDetails.classFlags & ClassTypeFlags.SpecialBuiltIn);
return !!(this._classDetails.flags & ClassTypeFlags.SpecialBuiltIn);
}
isBuiltIn() {
return !!(this._classDetails.classFlags & ClassTypeFlags.BuiltInClass);
return !!(this._classDetails.flags & ClassTypeFlags.BuiltInClass);
}
isProtocol() {
@ -249,11 +258,11 @@ export class ClassType extends Type {
}
getClassName() {
return this._classDetails.className;
return this._classDetails.name;
}
hasDecorators() {
return !!(this._classDetails.classFlags & ClassTypeFlags.HasDecorators);
return !!(this._classDetails.flags & ClassTypeFlags.HasDecorators);
}
getBaseClasses(): BaseClass[] {
@ -287,12 +296,47 @@ export class ClassType extends Type {
}
isSame(type2: Type): boolean {
return super.isSame(type2) &&
this._classDetails.className === (type2 as ClassType)._classDetails.className;
if (!super.isSame(type2)) {
return false;
}
let classType2 = type2 as ClassType;
// If the class details are common, it's the same class.
if (this._classDetails === classType2._classDetails) {
return true;
}
// In a few cases (e.g. with NamedTuple classes) we allocate a
// new class type for every type analysis pass. To detect this
// case, we will use the typeSourceId field.
if (this._classDetails.typeSourceId === classType2._classDetails.typeSourceId) {
return true;
}
return false;
}
getObjectName(recursionCount = AsStringMaxRecursionCount): string {
let objName = this._classDetails.name;
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(', ') + ']';
}
}).join(', ') + ']';
}
return objName;
}
asStringInternal(recursionCount = AsStringMaxRecursionCount): string {
return 'class ' + this._classDetails.className;
return 'Type[' + this.getObjectName(recursionCount) + ']';
}
// Determines whether this is a subclass (derived class)
@ -305,7 +349,7 @@ export class ClassType extends Type {
// Handle built-in types like 'dict' and 'list', which are all
// subclasses of object even though they are not explicitly declared
// that way.
if (this.isBuiltIn() && type2._classDetails.className === 'object' && type2.isBuiltIn()) {
if (this.isBuiltIn() && type2._classDetails.name === 'object' && type2.isBuiltIn()) {
return true;
}
@ -321,6 +365,17 @@ 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 {
@ -345,7 +400,7 @@ export class ObjectType extends Type {
}
asStringInternal(recursionCount = AsStringMaxRecursionCount): string {
return this._classType.getClassName();
return this._classType.getObjectName();
}
}