import { ParameterCategory } from '../parser/parseNodes' ; import * as ParseTreeUtils from './parseTreeUtils' ; import { ClassType , EnumLiteral , FunctionType , isAnyOrUnknown , isClassInstance , isInstantiableClass , isNever , isNoneInstance , isParamSpec , isTypeSame , isVariadicTypeVar , maxTypeRecursionCount , removeNoneFromUnion , Type , TypeBase , TypeCategory , TypeVarType , } from './types' ; import { doForEachSubtype , isTupleClass } from './typeUtils' ; const singleTickRegEx = /'/g ; const escapedDoubleQuoteRegEx = /\\"/g ; export const enum PrintTypeFlags { None = 0 , PrintUnknownWithAny = 1 << 0 , OmitTypeArgumentsIfAny = 1 << 1 , OmitUnannotatedParamType = 1 << 2 , PEP604 = 1 << 3 , ParenthesizeUnion = 1 << 4 , ExpandTypeAlias = 1 << 5 , OmitConditionalConstraint = 1 << 6 , ParenthesizeCallable = 1 << 7 } export type FunctionReturnTypeCallback = ( type : FunctionType ) => Type ; export function printType ( type , printTypeFlags , returnTypeCallback , recursionTypes = [ ] ) { const parenthesizeUnion = ( printTypeFlags & PrintTypeFlags . ParenthesizeUnion ) !== 0 ; const parenthesizeCallable = ( printTypeFlags & PrintTypeFlags . ParenthesizeCallable ) !== 0 ; printTypeFlags &= ~ ( PrintTypeFlags . ParenthesizeUnion | PrintTypeFlags . ParenthesizeCallable ) ; if ( type . typeAliasInfo ) { let expandTypeAlias = true ; if ( ( printTypeFlags & PrintTypeFlags . ExpandTypeAlias ) === 0 ) { expandTypeAlias = false ; } else { if ( recursionTypes . find ( ( t ) => t === type ) ) { expandTypeAlias = false ; } } if ( ! expandTypeAlias ) { try { recursionTypes . push ( type ) ; let aliasName = type . typeAliasInfo . name ; const typeParams = type . typeAliasInfo . typeParameters ; if ( typeParams ) { let argumentStrings ; if ( type . typeAliasInfo . typeArguments ) { if ( ( printTypeFlags & PrintTypeFlags . OmitTypeArgumentsIfAny ) === 0 || type . typeAliasInfo . typeArguments . some ( ( typeArg ) => ! isAnyOrUnknown ( typeArg ) ) ) { argumentStrings = [ ] ; type . typeAliasInfo . typeArguments . forEach ( ( typeArg , index ) => { const typeParam = index < typeParams . length ? typeParams [ index ] : typeParams [ typeParams . length - 1 ] ; if ( isVariadicTypeVar ( typeParam ) && isClassInstance ( typeArg ) && isTupleClass ( typeArg ) && typeArg . tupleTypeArguments ) { typeArg . tupleTypeArguments . forEach ( ( tupleTypeArg ) => { argumentStrings ! . push ( printType ( tupleTypeArg , printTypeFlags , returnTypeCallback , recursionTypes ) ) ; } ) ; } else { argumentStrings ! . push ( printType ( typeArg , printTypeFlags , returnTypeCallback , recursionTypes ) ) ; } } ) ; } } else { if ( ( printTypeFlags & PrintTypeFlags . OmitTypeArgumentsIfAny ) === 0 || typeParams . some ( ( typeParam ) => ! isAnyOrUnknown ( typeParam ) ) ) { argumentStrings = [ ] ; typeParams . forEach ( ( typeParam ) => { argumentStrings ! . push ( printType ( typeParam , printTypeFlags , returnTypeCallback , recursionTypes ) ) ; } ) ; } } if ( argumentStrings ) { if ( argumentStrings . length === 0 ) { aliasName += `[()]` ; } else { aliasName += `[${ argumentStrings . join ( ', ' ) }]` ; } } } if ( type . category !== TypeCategory . TypeVar ) { return aliasName ; } } finally { recursionTypes . pop ( ) ; } } } if ( recursionTypes . find ( ( t ) => t === type ) || recursionTypes . length > maxTypeRecursionCount ) { return '...' ; } try { recursionTypes . push ( type ) ; const includeConditionalIndicator = ( printTypeFlags & PrintTypeFlags . OmitConditionalConstraint ) === 0 ; const getConditionalIndicator = ( subtype ) => { return subtype . condition !== undefined && includeConditionalIndicator ? '*' : '' ; } ; switch ( type . category ) { case TypeCategory . Unbound : { return 'Unbound' ; } case TypeCategory . Unknown : { return ( printTypeFlags & PrintTypeFlags . PrintUnknownWithAny ) !== 0 ? 'Any' : 'Unknown' ; } case TypeCategory . Module : { return `Module("${ type . moduleName }")` ; } case TypeCategory . Class : { if ( TypeBase . isInstance ( type ) ) { if ( type . literalValue !== undefined ) { return `Literal[${ printLiteralValue ( type ) }]` ; } return `${ printObjectTypeForClass ( type , printTypeFlags , returnTypeCallback , recursionTypes ) }${ getConditionalIndicator ( type ) }` ; } else { if ( type . literalValue !== undefined ) { return `Type[Literal[${ printLiteralValue ( type ) }]]${ getConditionalIndicator ( type ) }` ; } return `Type[${ printObjectTypeForClass ( type , printTypeFlags , returnTypeCallback , recursionTypes ) }]${ getConditionalIndicator ( type ) }` ; } } case TypeCategory . Function : { const parts = printFunctionParts ( type , printTypeFlags , returnTypeCallback , recursionTypes ) ; const paramSignature = `(${ parts [ 0 ] . join ( ', ' ) })` ; if ( FunctionType . isParamSpecValue ( type ) ) { return paramSignature ; } const fullSignature = `${ paramSignature } -> ${ parts [ 1 ] }` ; if ( parenthesizeCallable ) { return `(${ fullSignature })` ; } return fullSignature ; } case TypeCategory . OverloadedFunction : { const overloadedType = type ; const overloads = overloadedType . overloads . map ( ( overload ) => printType ( overload , printTypeFlags , returnTypeCallback , recursionTypes ) ) ; return `Overload[${ overloads . join ( ', ' ) }]` ; } case TypeCategory . Union : { const subtypeHandledSet = new Set < number > ( ) ; const subtypeStrings = new Set < string > ( ) ; const updatedPrintTypeFlags = printTypeFlags & PrintTypeFlags . PEP604 ? printTypeFlags | PrintTypeFlags . ParenthesizeCallable : printTypeFlags ; if ( ( printTypeFlags & PrintTypeFlags . ExpandTypeAlias ) === 0 && type . typeAliasSources ) { for ( const typeAliasSource of type . typeAliasSources ) { let matchedAllSubtypes = true ; let allSubtypesPreviouslyHandled = true ; const indicesCoveredByTypeAlias = new Set < number > ( ) ; for ( const sourceSubtype of typeAliasSource . subtypes ) { let unionSubtypeIndex = 0 ; let foundMatch = false ; for ( const unionSubtype of type . subtypes ) { if ( isTypeSame ( sourceSubtype , unionSubtype , undefined , true ) ) { if ( ! subtypeHandledSet . has ( unionSubtypeIndex ) ) { allSubtypesPreviouslyHandled = false ; } indicesCoveredByTypeAlias . add ( unionSubtypeIndex ) ; foundMatch = true ; break ; } unionSubtypeIndex ++ ; } if ( ! foundMatch ) { matchedAllSubtypes = false ; break ; } } if ( matchedAllSubtypes && ! allSubtypesPreviouslyHandled ) { subtypeStrings . add ( printType ( typeAliasSource , updatedPrintTypeFlags , returnTypeCallback , recursionTypes ) ) ; indicesCoveredByTypeAlias . forEach ( ( index ) => subtypeHandledSet . add ( index ) ) ; } } } const noneIndex = type . subtypes . findIndex ( ( subtype ) => isNoneInstance ( subtype ) ) ; if ( noneIndex >= 0 && ! subtypeHandledSet . has ( noneIndex ) ) { const typeWithoutNone = removeNoneFromUnion ( type ) ; if ( isNever ( typeWithoutNone ) ) { return 'None' ; } const optionalType = printType ( typeWithoutNone , updatedPrintTypeFlags , returnTypeCallback , recursionTypes ) ; if ( printTypeFlags & PrintTypeFlags . PEP604 ) { return optionalType + ' | None' ; } return 'Optional[' + optionalType + ']' ; } const literalObjectStrings = new Set < string > ( ) ; const literalClassStrings = new Set < string > ( ) ; doForEachSubtype ( type , ( subtype , index ) => { if ( ! subtypeHandledSet . has ( index ) ) { if ( isClassInstance ( subtype ) && subtype . literalValue !== undefined ) { literalObjectStrings . add ( printLiteralValue ( subtype ) ) ; } else if ( isInstantiableClass ( subtype ) && subtype . literalValue !== undefined ) { literalClassStrings . add ( printLiteralValue ( subtype ) ) ; } else { subtypeStrings . add ( printType ( subtype , updatedPrintTypeFlags , returnTypeCallback , recursionTypes ) ) ; } } } ) ; const dedupedSubtypeStrings = [ ] ; subtypeStrings . forEach ( ( s ) => dedupedSubtypeStrings . push ( s ) ) ; if ( literalObjectStrings . size > 0 ) { const literalStrings = [ ] ; literalObjectStrings . forEach ( ( s ) => literalStrings . push ( s ) ) ; dedupedSubtypeStrings . push ( `Literal[${ literalStrings . join ( ', ' ) }]` ) ; } if ( literalClassStrings . size > 0 ) { const literalStrings = [ ] ; literalClassStrings . forEach ( ( s ) => literalStrings . push ( s ) ) ; dedupedSubtypeStrings . push ( `Type[Literal[${ literalStrings . join ( ', ' ) }]]` ) ; } if ( dedupedSubtypeStrings . length === 1 ) { return dedupedSubtypeStrings [ 0 ] ; } if ( printTypeFlags & PrintTypeFlags . PEP604 ) { const unionString = dedupedSubtypeStrings . join ( ' | ' ) ; if ( parenthesizeUnion ) { return `(${ unionString })` ; } return unionString ; } return `Union[${ dedupedSubtypeStrings . join ( ', ' ) }]` ; } case TypeCategory . TypeVar : { if ( type . details . isSynthesized ) { if ( type . details . recursiveTypeAliasName ) { if ( ( printTypeFlags & PrintTypeFlags . ExpandTypeAlias ) !== 0 && type . details . boundType ) { return printType ( type . details . boundType , printTypeFlags , returnTypeCallback , recursionTypes ) ; } return type . details . recursiveTypeAliasName ; } if ( type . details . isSynthesizedSelf && type . details . boundType ) { let boundTypeString = printType ( type . details . boundType , printTypeFlags & ~ PrintTypeFlags . ExpandTypeAlias , returnTypeCallback , recursionTypes ) ; if ( ! isAnyOrUnknown ( type . details . boundType ) ) { boundTypeString = `Self@${ boundTypeString }` ; } if ( TypeBase . isInstantiable ( type ) ) { return `Type[${ boundTypeString }]` ; } return boundTypeString ; } return ( printTypeFlags & PrintTypeFlags . PrintUnknownWithAny ) !== 0 ? 'Any' : 'Unknown' ; } if ( type . details . isParamSpec ) { if ( type . paramSpecAccess ) { return `${ type . details . name }.${ type . paramSpecAccess }` ; } return `${ TypeVarType . getReadableName ( type ) }` ; } let typeVarName = TypeVarType . getReadableName ( type ) ; if ( type . isVariadicUnpacked ) { typeVarName = `*${ typeVarName }` ; } if ( TypeBase . isInstantiable ( type ) ) { return `Type[${ typeVarName }]` ; } return typeVarName ; } case TypeCategory . None : { return `${ TypeBase . isInstantiable ( type ) ? 'Type[None]' : 'None' }${ getConditionalIndicator ( type ) }` ; } case TypeCategory . Never : { return 'Never' ; } case TypeCategory . Any : { const anyType = type ; return anyType . isEllipsis ? '...' : 'Any' ; } } return '' ; } finally { recursionTypes . pop ( ) ; } } export function printLiteralValue ( type , quotation = "'" ) { const literalValue = type . literalValue ; if ( literalValue === undefined ) { return '' ; } let literalStr ; if ( typeof literalValue === 'string' ) { const prefix = type . details . name === 'bytes' ? 'b' : '' ; literalStr = JSON . stringify ( literalValue ) . toString ( ) ; if ( quotation !== '"' ) { literalStr = `'${ literalStr . substring ( 1 , literalStr . length - 1 ) . replace ( escapedDoubleQuoteRegEx , '"' ) . replace ( singleTickRegEx , "\\'" ) }'` ; } if ( prefix ) { literalStr = `${ prefix }${ literalStr }` ; } } else if ( typeof literalValue === 'boolean' ) { literalStr = literalValue ? 'True' : 'False' ; } else if ( literalValue instanceof EnumLiteral ) { literalStr = `${ literalValue . className }.${ literalValue . itemName }` ; } else { literalStr = literalValue . toString ( ) ; } return literalStr ; } export function printObjectTypeForClass ( type , printTypeFlags , returnTypeCallback , recursionTypes = [ ] ) { let objName = type . aliasName || type . details . name ; if ( ! ClassType . isPseudoGenericClass ( type ) ) { const typeParams = ClassType . getTypeParameters ( type ) ; const lastTypeParam = typeParams . length > 0 ? typeParams [ typeParams . length - 1 ] : undefined ; const isVariadic = lastTypeParam ? lastTypeParam . details . isVariadic : false ; const typeArgs = type . tupleTypeArguments || type . typeArguments ; if ( typeArgs ) { if ( typeArgs . length > 0 ) { const typeArgStrings = [ ] ; let isAllAny = true ; typeArgs . forEach ( ( typeArg , index ) => { const typeParam = index < typeParams . length ? typeParams [ index ] : undefined ; if ( typeParam && typeParam . details . isVariadic && isClassInstance ( typeArg ) && ClassType . isBuiltIn ( typeArg , 'tuple' ) && typeArg . tupleTypeArguments ) { if ( typeArg . tupleTypeArguments . length === 0 ) { if ( ! isAnyOrUnknown ( typeArg ) ) { isAllAny = false ; } typeArgStrings . push ( '()' ) ; } else { typeArgStrings . push ( ... typeArg . tupleTypeArguments . map ( ( typeArg ) => { if ( ! isAnyOrUnknown ( typeArg ) ) { isAllAny = false ; } return printType ( typeArg , printTypeFlags , returnTypeCallback , recursionTypes ) ; } ) ) ; } } else { if ( ! isAnyOrUnknown ( typeArg ) ) { isAllAny = false ; } typeArgStrings . push ( printType ( typeArg , printTypeFlags , returnTypeCallback , recursionTypes ) ) ; } } ) ; if ( ( printTypeFlags & PrintTypeFlags . OmitTypeArgumentsIfAny ) === 0 || ! isAllAny ) { objName += '[' + typeArgStrings . join ( ', ' ) + ']' ; } } else { if ( ClassType . isTupleClass ( type ) || isVariadic ) { objName += '[()]' ; } } } else { if ( typeParams . length > 0 ) { if ( ( printTypeFlags & PrintTypeFlags . OmitTypeArgumentsIfAny ) === 0 || typeParams . some ( ( typeParam ) => ! isAnyOrUnknown ( typeParam ) ) ) { objName += '[' + typeParams . map ( ( typeParam ) => { return printType ( typeParam , printTypeFlags , returnTypeCallback , recursionTypes ) ; } ) . join ( ', ' ) + ']' ; } } } } return objName ; } export function printFunctionParts ( type , printTypeFlags , returnTypeCallback , recursionTypes = [ ] ) { const paramTypeStrings = [ ] ; let sawDefinedName = false ; type . details . parameters . forEach ( ( param , index ) => { if ( index === type . details . parameters . length - 1 && param . category === ParameterCategory . VarArgList && isVariadicTypeVar ( param . type ) ) { const specializedParamType = FunctionType . getEffectiveParameterType ( type , index ) ; if ( isClassInstance ( specializedParamType ) && ClassType . isBuiltIn ( specializedParamType , 'tuple' ) && specializedParamType . tupleTypeArguments ) { specializedParamType . tupleTypeArguments . forEach ( ( paramType , paramIndex ) => { const paramString = printType ( paramType , printTypeFlags , returnTypeCallback , recursionTypes ) ; paramTypeStrings . push ( paramString ) ; } ) ; return ; } } let paramString = '' ; if ( param . category === ParameterCategory . VarArgList ) { paramString += '*' ; } else if ( param . category === ParameterCategory . VarArgDictionary ) { paramString += '**' ; } if ( param . name && ! param . isNameSynthesized ) { paramString += param . name ; sawDefinedName = true ; } let defaultValueAssignment = '=' ; let isParamSpecArgsKwargsParam = false ; if ( param . name ) { if ( param . hasDeclaredType || param . isTypeInferred ) { const paramType = FunctionType . getEffectiveParameterType ( type , index ) ; const paramTypeString = recursionTypes . length < maxTypeRecursionCount ? printType ( paramType , printTypeFlags , returnTypeCallback , recursionTypes ) : '' ; if ( ! param . isNameSynthesized ) { paramString += ': ' ; } paramString += paramTypeString ; if ( isParamSpec ( paramType ) ) { if ( param . category === ParameterCategory . VarArgList || param . category === ParameterCategory . VarArgDictionary ) { isParamSpecArgsKwargsParam = true ; } } defaultValueAssignment = ' = ' ; } else if ( ( printTypeFlags & PrintTypeFlags . OmitTypeArgumentsIfAny ) === 0 ) { if ( ! param . isNameSynthesized ) { paramString += ': ' ; } paramString += 'Unknown' ; defaultValueAssignment = ' = ' ; } } else if ( param . category === ParameterCategory . Simple ) { if ( sawDefinedName ) { paramString += '/' ; } else { return ; } } if ( param . hasDefault ) { if ( param . defaultValueExpression ) { paramString += defaultValueAssignment + ParseTreeUtils . printExpression ( param . defaultValueExpression ) ; } else { paramString += defaultValueAssignment + '...' ; } } if ( FunctionType . shouldSkipArgsKwargsCompatibilityCheck ( type ) && ! isParamSpecArgsKwargsParam ) { if ( param . category === ParameterCategory . VarArgList ) { paramString = '...' ; } else if ( param . category === ParameterCategory . VarArgDictionary ) { return ; } } paramTypeStrings . push ( paramString ) ; } ) ; if ( type . details . paramSpec ) { paramTypeStrings . push ( `**${ printType ( type . details . paramSpec , printTypeFlags , returnTypeCallback , recursionTypes ) }` ) ; } const returnType = returnTypeCallback ( type ) ; const returnTypeString = recursionTypes . length < maxTypeRecursionCount ? printType ( returnType , printTypeFlags | PrintTypeFlags . ParenthesizeUnion | PrintTypeFlags . ParenthesizeCallable , returnTypeCallback , recursionTypes ) : '' ; return [ paramTypeStrings , returnTypeString ] ; }