import { DEFAULT_GENERIC_PARAMETER_TYPE , DEFAULT_RESULT_CACHE , NEVER_TYPE } from "../constants" ; import { isSimpleTypeLiteral , isSimpleTypePrimitive , SimpleType , SimpleTypeFunctionParameter , SimpleTypeGenericArguments , SimpleTypeGenericParameter , SimpleTypeIntersection , SimpleTypeKind , SimpleTypeMemberNamed , SimpleTypeObject , SimpleTypeObjectTypeBase , SimpleTypeTuple } from "../simple-type" ; import { simpleTypeToString } from "../transform/simple-type-to-string" ; import { and , or } from "../utils/list-util" ; import { resolveType as resolveTypeUnsafe } from "../utils/resolve-type" ; import { extendTypeParameterMap , getTupleLengthType } from "../utils/simple-type-util" ; import { isAssignableToSimpleTypeKind } from "./is-assignable-to-simple-type-kind" ; import { SimpleTypeComparisonOptions } from "./simple-type-comparison-options" ; interface IsAssignableToSimpleTypeInternalOptions { config : SimpleTypeComparisonOptions ; cache : WeakMap < SimpleType , WeakMap < SimpleType , boolean > > ; insideType : Set < SimpleType > ; comparingTypes : Map < SimpleType , Set < SimpleType > > ; genericParameterMapA : Map < string , SimpleType > ; genericParameterMapB : Map < string , SimpleType > ; preventCaching : ( ) => void ; operations : { value : number ; } ; depth : number ; } param typeA param typeB param config export function isAssignableToSimpleType ( typeA , typeB , config ? ) { const userCache = config ?. cache ; config = { ... config , cache : undefined , strict : config ?. strict ?? true , strictFunctionTypes : config ?. strictFunctionTypes ?? config ?. strict ?? true , strictNullChecks : config ?. strictNullChecks ?? config ?. strict ?? true , maxDepth : config ?. maxDepth ?? 50 , maxOps : config ?. maxOps ?? 1000 } ; const cacheKey = `${ config . strict }:${ config . strictFunctionTypes }:${ config . strictNullChecks }` ; const cache = DEFAULT_RESULT_CACHE . get ( cacheKey ) || new WeakMap ( ) ; DEFAULT_RESULT_CACHE . set ( cacheKey , cache ) ; return isAssignableToSimpleTypeCached ( typeA , typeB , { config , operations : { value : 0 } , depth : 0 , cache : userCache || cache , insideType : new Set ( ) , comparingTypes : new Map ( ) , genericParameterMapA : new Map ( ) , genericParameterMapB : new Map ( ) , preventCaching : ( ) => { } } ) ; } function isAssignableToSimpleTypeCached ( typeA , typeB , options ) { let typeACache = options . cache . get ( typeA ) ! ; let preventCaching = false ; if ( typeACache ?. has ( typeB ) ) { if ( options . config . debug ) { logDebug ( options , "caching" , `Found cache when comparing: ${ simpleTypeToStringLazy ( typeA ) } (${ typeA . kind }) and ${ simpleTypeToStringLazy ( typeB ) } (${ typeB . kind }). Cache content: ${ typeACache . get ( typeB ) }` ) ; } return typeACache . get ( typeB ) ! ; } const result = isAssignableToSimpleTypeInternal ( typeA , typeB , { depth : options . depth , operations : options . operations , genericParameterMapA : options . genericParameterMapA , genericParameterMapB : options . genericParameterMapB , config : options . config , insideType : options . insideType , comparingTypes : options . comparingTypes , cache : options . cache , preventCaching : ( ) => { options . preventCaching ( ) ; preventCaching = true ; } } ) ; if ( ! preventCaching ) { if ( typeACache == null ) { typeACache = new WeakMap ( ) ; options . cache . set ( typeA , typeACache ) ; } typeACache . set ( typeB , result ) ; } return result ; } function isCacheableType ( simpleType , options ) { switch ( simpleType . kind ) { case "UNION" : case "INTERSECTION" : if ( options . genericParameterMapA . size !== 0 || options . genericParameterMapB . size !== 0 ) { return false ; } break ; } return ! ( "typeParameters" in simpleType ) && ! [ "GENERIC_ARGUMENTS" , "GENERIC_PARAMETER" , "PROMISE" , "LAZY" ] . includes ( simpleType . kind ) ; } function isAssignableToSimpleTypeInternal ( typeA , typeB , options ) { options . depth = options . depth + 1 ; options . operations . value ++ ; if ( options . config . debug === true ) { logDebugHeader ( typeA , typeB , options ) ; } if ( options . depth >= options . config . maxDepth ! || options . operations . value >= options . config . maxOps ! ) { options . preventCaching ( ) ; return true ; } if ( options . comparingTypes . has ( typeA ) ) { if ( options . comparingTypes . get ( typeA ) ! . has ( typeB ) ) { options . preventCaching ( ) ; if ( options . config . debug ) { logDebug ( options , "comparing types" , "Returns true because this relation is already being checking" ) ; } return true ; } } if ( options . insideType . has ( typeA ) || options . insideType . has ( typeB ) ) { if ( options . config . debug ) { logDebug ( options , "inside type" , `{${ typeA . kind }, ${ typeB . kind }} {typeA: ${ options . insideType . has ( typeA ) }} {typeB: ${ options . insideType . has ( typeB ) }} {insideTypeMap: ${ Array . from ( options . insideType . keys ( ) ) . map ( t => simpleTypeToStringLazy ( t ) ) . join ( ) }}` ) ; } options . preventCaching ( ) ; return true ; } if ( isCacheableType ( typeA , options ) && isCacheableType ( typeB , options ) ) { if ( typeA === typeB ) { if ( options . config . debug ) { logDebug ( options , "equal" , "The two types are equal!" , typeA . kind , typeB . kind ) ; } return true ; } } else { options . preventCaching ( ) ; } if ( options . config . isAssignable != null ) { const result = options . config . isAssignable ( typeA , typeB , options . config ) ; if ( result != null ) { return result ; } } if ( typeA . kind === "UNKNOWN" || typeA . kind === "ANY" ) { return true ; } if ( ! isSimpleTypePrimitive ( typeA ) && ! isSimpleTypePrimitive ( typeB ) ) { const comparingTypes = new Map ( options . comparingTypes ) ; if ( comparingTypes . has ( typeA ) ) { comparingTypes . get ( typeA ) ! . add ( typeB ) ; } else { comparingTypes . set ( typeA , new Set ( [ typeB ] ) ) ; } options . comparingTypes = comparingTypes ; } switch ( typeB . kind ) { case "UNION" : { const types = typeB . types . filter ( t => resolveType ( t , options . genericParameterMapB ) !== DEFAULT_GENERIC_PARAMETER_TYPE ) ; return and ( types , childTypeB => isAssignableToSimpleTypeCached ( typeA , childTypeB , options ) ) ; } case "INTERSECTION" : { if ( typeA . kind === "INTERSECTION" ) { break ; } const combined = reduceIntersectionIfPossible ( typeB , options . genericParameterMapB ) ; if ( combined . kind === "NEVER" ) { if ( options . config . debug ) { logDebug ( options , "intersection" , `Combining types in intersection is impossible. Comparing with 'never' instead.` ) ; } return isAssignableToSimpleTypeCached ( typeA , { kind : "NEVER" } , options ) ; } if ( options . config . debug ) { if ( combined !== typeB ) { logDebug ( options , "intersection" , `Types in intersection were combined into: ${ simpleTypeToStringLazy ( combined ) }` ) ; } } if ( combined . kind !== "INTERSECTION" ) { return isAssignableToSimpleTypeCached ( typeA , combined , options ) ; } return or ( combined . types , memberB => isAssignableToSimpleTypeCached ( typeA , memberB , options ) ) ; } case "ALIAS" : { return isAssignableToSimpleTypeCached ( typeA , typeB . target , options ) ; } case "GENERIC_ARGUMENTS" : { const updatedGenericParameterMapB = extendTypeParameterMap ( typeB , options . genericParameterMapB ) ; if ( options . config . debug ) { logDebug ( options , "generic args" , "Expanding with typeB args: " , Array . from ( updatedGenericParameterMapB . entries ( ) ) . map ( ( [ name , type ] ) => `${ name }=${ simpleTypeToStringLazy ( type ) }` ) . join ( "; " ) , "typeParameters" in typeB . target ? "" : "[No type parameters in target!]" ) ; } return isAssignableToSimpleTypeCached ( typeA , typeB . target , { ... options , genericParameterMapB : updatedGenericParameterMapB } ) ; } case "GENERIC_PARAMETER" : { const resolvedArgument = options . genericParameterMapB . get ( typeB . name ) ; const realTypeB = resolvedArgument || typeB . default || DEFAULT_GENERIC_PARAMETER_TYPE ; if ( options . config . debug ) { logDebug ( options , "generic" , `Resolving typeB for param ${ typeB . name } to:` , simpleTypeToStringLazy ( realTypeB ) , ", Default: " , simpleTypeToStringLazy ( typeB . default ) , ", In map: " , options . genericParameterMapB . has ( typeB . name ) , ", GenericParamMapB: " , Array . from ( options . genericParameterMapB . entries ( ) ) . map ( ( [ name , t ] ) => `${ name }=${ simpleTypeToStringLazy ( t ) }` ) . join ( "; " ) ) ; } return isAssignableToSimpleTypeCached ( typeA , realTypeB , options ) ; } } switch ( typeB . kind ) { case "ENUM_MEMBER" : { return isAssignableToSimpleTypeCached ( typeA , typeB . type , options ) ; } case "ENUM" : { return and ( typeB . types , childTypeB => isAssignableToSimpleTypeCached ( typeA , childTypeB , options ) ) ; } case "UNDEFINED" : case "NULL" : { if ( ! options . config . strictNullChecks ) { return typeA . kind !== "NEVER" ; } break ; } case "ANY" : { return typeA . kind !== "NEVER" ; } case "NEVER" : { return true ; } } switch ( typeA . kind ) { case "ALIAS" : { return isAssignableToSimpleTypeCached ( typeA . target , typeB , options ) ; } case "GENERIC_PARAMETER" : { const resolvedArgument = options . genericParameterMapA . get ( typeA . name ) ; const realTypeA = resolvedArgument || typeA . default || DEFAULT_GENERIC_PARAMETER_TYPE ; if ( options . config . debug ) { logDebug ( options , "generic" , `Resolving typeA for param ${ typeA . name } to:` , simpleTypeToStringLazy ( realTypeA ) , ", Default: " , simpleTypeToStringLazy ( typeA . default ) , ", In map: " , options . genericParameterMapA . has ( typeA . name ) , ", GenericParamMapA: " , Array . from ( options . genericParameterMapA . entries ( ) ) . map ( ( [ name , t ] ) => `${ name }=${ simpleTypeToStringLazy ( t ) }` ) . join ( "; " ) ) ; } return isAssignableToSimpleTypeCached ( realTypeA , typeB , options ) ; } case "GENERIC_ARGUMENTS" : { const updatedGenericParameterMapA = extendTypeParameterMap ( typeA , options . genericParameterMapA ) ; if ( options . config . debug ) { logDebug ( options , "generic args" , "Expanding with typeA args: " , Array . from ( updatedGenericParameterMapA . entries ( ) ) . map ( ( [ name , type ] ) => `${ name }=${ simpleTypeToStringLazy ( type ) }` ) . join ( "; " ) , "typeParameters" in typeA . target ? "" : "[No type parameters in target!]" ) ; } return isAssignableToSimpleTypeCached ( typeA . target , typeB , { ... options , genericParameterMapA : updatedGenericParameterMapA } ) ; } case "UNION" : { const types = typeA . types . filter ( t => resolveType ( t , options . genericParameterMapA ) !== DEFAULT_GENERIC_PARAMETER_TYPE || typeB === DEFAULT_GENERIC_PARAMETER_TYPE ) ; return or ( types , childTypeA => isAssignableToSimpleTypeCached ( childTypeA , typeB , options ) ) ; } case "INTERSECTION" : { const combined = reduceIntersectionIfPossible ( typeA , options . genericParameterMapA ) ; if ( combined . kind === "NEVER" ) { if ( options . config . debug ) { logDebug ( options , "intersection" , `Combining types in intersection is impossible. Comparing with 'never' instead.` ) ; } return isAssignableToSimpleTypeCached ( { kind : "NEVER" } , typeB , options ) ; } if ( options . config . debug ) { if ( combined !== typeA ) { logDebug ( options , "intersection" , `Types in intersection were combined into: ${ simpleTypeToStringLazy ( combined ) }` ) ; } } if ( combined . kind !== "INTERSECTION" ) { return isAssignableToSimpleTypeCached ( combined , typeB , options ) ; } return and ( combined . types , memberA => isAssignableToSimpleTypeCached ( memberA , typeB , options ) ) ; } } switch ( typeA . kind ) { case "NON_PRIMITIVE" : { if ( options . config . debug ) { logDebug ( options , "object" , `Checking if typeB is non-primitive [primitive=${ isSimpleTypePrimitive ( typeB ) }] [hasName=${ typeB . name != null }]` ) ; } if ( isSimpleTypePrimitive ( typeB ) ) { return typeB . name != null ; } return typeB . kind !== "UNKNOWN" ; } case "ARRAY" : { if ( typeB . kind === "ARRAY" ) { return isAssignableToSimpleTypeCached ( typeA . type , typeB . type , options ) ; } else if ( typeB . kind === "TUPLE" ) { return and ( typeB . members , memberB => isAssignableToSimpleTypeCached ( typeA . type , memberB . type , options ) ) ; } return false ; } case "ENUM" : { return or ( typeA . types , childTypeA => isAssignableToSimpleTypeCached ( childTypeA , typeB , options ) ) ; } case "NUMBER_LITERAL" : case "STRING_LITERAL" : case "BIG_INT_LITERAL" : case "BOOLEAN_LITERAL" : case "ES_SYMBOL_UNIQUE" : { return isSimpleTypeLiteral ( typeB ) ? typeA . value === typeB . value : false ; } case "ENUM_MEMBER" : { if ( resolveType ( typeA . type , options . genericParameterMapA ) . kind === "NUMBER_LITERAL" && [ "NUMBER" , "NUMBER_LITERAL" ] . includes ( typeB . kind ) ) { if ( typeB . name != null ) { return false ; } return true ; } return isAssignableToSimpleTypeCached ( typeA . type , typeB , options ) ; } case "STRING" : case "BOOLEAN" : case "NUMBER" : case "ES_SYMBOL" : case "BIG_INT" : { if ( typeB . name != null ) { return false ; } if ( isSimpleTypeLiteral ( typeB ) ) { return PRIMITIVE_TYPE_TO_LITERAL_MAP [ typeA . kind ] === typeB . kind ; } return typeA . kind === typeB . kind ; } case "UNDEFINED" : case "NULL" : { return typeA . kind === typeB . kind ; } case "VOID" : { return typeB . kind === "VOID" || typeB . kind === "UNDEFINED" ; } case "NEVER" : { return false ; } case "FUNCTION" : case "METHOD" : { if ( "call" in typeB && typeB . call != null ) { return isAssignableToSimpleTypeCached ( typeA , typeB . call , options ) ; } if ( typeB . kind !== "FUNCTION" && typeB . kind !== "METHOD" ) return false ; if ( typeB . parameters == null || typeB . returnType == null ) return typeA . parameters == null || typeA . returnType == null ; if ( typeA . parameters == null || typeA . returnType == null ) return true ; if ( options . config . debug ) { logDebug ( options , "function" , `Checking if return type of typeA is 'void'` ) ; } if ( ! isAssignableToSimpleTypeKind ( typeA . returnType , "VOID" ) ) { if ( options . config . debug ) { logDebug ( options , "function" , `Return type is not void. Checking return types` ) ; } if ( ! isAssignableToSimpleTypeCached ( typeA . returnType , typeB . returnType , options ) ) { return false ; } } const typeAThisParam = typeA . parameters . find ( arg => arg . name === "this" ) ; const typeBThisParam = typeB . parameters . find ( arg => arg . name === "this" ) ; if ( typeAThisParam != null && typeBThisParam != null ) { if ( options . config . debug ) { logDebug ( options , "function" , `Checking 'this' param` ) ; } if ( ! isAssignableToSimpleTypeCached ( typeAThisParam . type , typeBThisParam . type , options ) ) { return false ; } } const paramTypesA = typeAThisParam == null ? typeA . parameters : typeA . parameters . filter ( arg => arg !== typeAThisParam ) ; const paramTypesB = typeBThisParam == null ? typeB . parameters : typeB . parameters . filter ( arg => arg !== typeBThisParam ) ; if ( paramTypesB . length === 0 ) { return true ; } const requiredParamCountB = paramTypesB . reduce ( ( sum , param ) => ( param . optional || param . rest ? sum : sum + 1 ) , 0 ) ; if ( requiredParamCountB > paramTypesA . length ) { if ( options . config . debug ) { logDebug ( options , "function" , `typeB has more required params than typeA: ${ requiredParamCountB } > ${ paramTypesA . length }` ) ; } return false ; } let prevParamA = undefined ; let prevParamB = undefined ; for ( let i = 0 ; i < Math . max ( paramTypesA . length , paramTypesB . length ) ; i ++ ) { let paramA = paramTypesA [ i ] ; let paramB = paramTypesB [ i ] ; if ( options . config . debug ) { logDebug ( options , "function" , `${ i } ['${ paramA ?. name || "???" }' AND '${ paramB ?. name || "???" }'] Checking parameters ${ options . config . strictFunctionTypes ? "[contravariant]" : "[bivariant]" }: [${ paramA ?. type == null ? "???" : simpleTypeToStringLazy ( paramA . type ) }  AND  ${ paramB ?. type == null ? "???" : simpleTypeToStringLazy ( paramB . type ) }]` ) ; } if ( paramA == null && prevParamA ?. rest ) { if ( options . config . debug ) { logDebug ( options , "function" , `paramA is null and but last param in typeA is rest. Use that one.` ) ; } paramA = prevParamA ; } if ( paramB == null && prevParamB ?. rest ) { if ( options . config . debug ) { logDebug ( options , "function" , `paramB is null and but last param in typeB is rest. Use that one.` ) ; } paramB = prevParamB ; } prevParamA = paramA ; prevParamB = paramB ; if ( paramA == null ) { if ( paramB != null && ! paramB . optional && ! paramB . rest ) { if ( options . config . debug ) { logDebug ( options , "function" , `paramA is null and paramB is null, optional or has rest` ) ; } return false ; } if ( options . config . debug ) { logDebug ( options , "function" , `paramA is null and paramB it not null, but is optional or has rest` ) ; } continue ; } if ( paramB == null ) { if ( options . config . debug ) { logDebug ( options , "function" , `paramB is 'null' returning true` ) ; } return true ; } const resolvedTypeA = resolveType ( paramA . type , options . genericParameterMapA ) ; const resolvedTypeB = resolveType ( paramB . type , options . genericParameterMapB ) ; const paramAType = paramA . rest && resolvedTypeA . kind === "ARRAY" ? resolvedTypeA . type : paramA . type ; const paramBType = paramB . rest && resolvedTypeB . kind === "ARRAY" ? resolvedTypeB . type : paramB . type ; if ( paramA . rest ) { if ( options . config . debug ) { logDebug ( options , "function" , `paramA is 'rest' and has been resolved to '${ simpleTypeToStringLazy ( paramAType ) }'` ) ; } } if ( paramB . rest ) { if ( options . config . debug ) { logDebug ( options , "function" , `paramB is 'rest' and has been resolved to '${ simpleTypeToStringLazy ( paramBType ) }'` ) ; } } if ( ! options . config . strictFunctionTypes ) { if ( options . config . debug ) { logDebug ( options , "function" , `Checking covariant relationship` ) ; } if ( isAssignableToSimpleTypeCached ( paramAType , paramBType , options ) ) { continue ; } } const newOptions = { ... options , config : typeA . kind === "METHOD" || typeB . kind === "METHOD" ? { ... options . config , strictNullChecks : false , strictFunctionTypes : false } : options . config , cache : new WeakMap ( ) , genericParameterMapB : options . genericParameterMapA , genericParameterMapA : options . genericParameterMapB } ; if ( options . config . debug ) { logDebug ( options , "function" , `Checking contravariant relationship` ) ; } if ( ! isAssignableToSimpleTypeCached ( paramBType , paramAType , newOptions ) ) { return false ; } } return true ; } case "INTERFACE" : case "OBJECT" : case "CLASS" : { const typeAHasZeroMembers = isObjectEmpty ( typeA , { ignoreOptionalMembers : ( [ "UNKNOWN" , "NON_PRIMITIVE" ] as SimpleTypeKind [ ] ) . includes ( typeB . kind ) } ) ; if ( typeAHasZeroMembers && typeA . call == null && ( typeA . ctor == null || typeA . kind === "CLASS" ) ) { if ( options . config . debug ) { logDebug ( options , "object-type" , `typeA is the empty object '{}'` ) ; } return ! isAssignableToSimpleTypeKind ( typeB , [ "NULL" , "UNDEFINED" , "NEVER" , "VOID" , ... ( options . config . strictNullChecks ? [ "UNKNOWN" ] : [ ] ) ] as SimpleTypeKind [ ] , { matchAny : false } ) ; } switch ( typeB . kind ) { case "FUNCTION" : case "METHOD" : return typeA . call != null && isAssignableToSimpleTypeCached ( typeA . call , typeB , options ) ; case "INTERFACE" : case "OBJECT" : case "CLASS" : { const membersA = typeA . members || [ ] ; const membersB = typeB . members || [ ] ; options . insideType = new Set ( [ ... options . insideType , typeA , typeB ] ) ; let membersInCommon = 0 ; const requiredMembersInTypeAExistsInTypeB = and ( membersA , memberA => { const memberB = membersB . find ( memberB => memberA . name === memberB . name ) ; if ( memberB != null ) membersInCommon += 1 ; return memberB == null ? memberA . optional : memberA . optional || ! memberB . optional ; } ) ; if ( ! requiredMembersInTypeAExistsInTypeB ) { if ( options . config . debug ) { logDebug ( options , "object-type" , `Didn't find required members from typeA in typeB` ) ; } return false ; } if ( typeA . ctor != null && typeA . kind !== "CLASS" ) { if ( options . config . debug ) { logDebug ( options , "object-type" , `Checking if typeB.ctor is assignable to typeA.ctor` ) ; } if ( typeB . ctor != null && typeB . kind !== "CLASS" ) { if ( ! isAssignableToSimpleTypeCached ( typeA . ctor , typeB . ctor , options ) ) { return false ; } membersInCommon += 1 ; } else { if ( options . config . debug ) { logDebug ( options , "object-type" , `Expected typeB.ctor to have a ctor` ) ; } return false ; } } if ( typeA . call != null ) { if ( options . config . debug ) { logDebug ( options , "object-type" , `Checking if typeB.call is assignable to typeA.call` ) ; } if ( typeB . call != null ) { if ( ! isAssignableToSimpleTypeCached ( typeA . call , typeB . call , options ) ) { return false ; } membersInCommon += 1 ; } else { return false ; } } const typeBIsEmpty = membersB . length === 0 && typeB . call == null && ( ( typeB . kind !== "CLASS" && typeB . ctor == null ) || typeB . kind === "CLASS" ) ; if ( membersInCommon === 0 && ! typeBIsEmpty ) { if ( options . config . debug ) { logDebug ( options , "object-type" , `typeB has 0 members in common with typeA and there are more than 0 members in typeB` ) ; } return false ; } const membersInTypeBAreAssignableToMembersInTypeA = and ( membersB , memberB => { const memberA = membersA . find ( memberA => memberA . name === memberB . name ) ; if ( memberA == null ) { return true ; } if ( options . config . debug ) { logDebug ( options , "object-type" , `Checking member '${ memberA . name }' types` ) ; } return isAssignableToSimpleTypeCached ( memberA . type , memberB . type , options ) ; } ) ; if ( options . config . debug ) { if ( ! membersInTypeBAreAssignableToMembersInTypeA ) { logDebug ( options , "object-type" , `Not all members in typeB is assignable to corresponding members in typeA` ) ; } else { logDebug ( options , "object-type" , `All members were checked successfully` ) ; } } return membersInTypeBAreAssignableToMembersInTypeA ; } default : return false ; } } case "TUPLE" : { if ( typeB . kind !== "TUPLE" ) return false ; if ( ! isAssignableToSimpleTypeCached ( getTupleLengthType ( typeA ) , getTupleLengthType ( typeB ) , options ) ) { return false ; } if ( typeA . rest && typeB . members . length > typeA . members . length ) { return and ( typeB . members . slice ( typeA . members . length ) , ( memberB , i ) => { return isAssignableToSimpleTypeCached ( typeA . members [ typeA . members . length - 1 ] . type , memberB . type , options ) ; } ) ; } return and ( typeA . members , ( memberA , i ) => { const memberB = typeB . members [ i ] ; if ( memberB == null ) return memberA . optional ; return isAssignableToSimpleTypeCached ( memberA . type , memberB . type , options ) ; } ) ; } case "PROMISE" : { return typeB . kind === "PROMISE" && isAssignableToSimpleTypeCached ( typeA . type , typeB . type , options ) ; } case "DATE" : { return typeB . kind === "DATE" ; } } return true ; } function reduceIntersectionIfPossible ( simpleType , parameterMap ) { const typeKindMap = new Map < SimpleTypeKind , SimpleType [ ] > ( ) ; const primitiveSet = new Set < SimpleTypeKind > ( ) ; const primitiveLiteralSet = new Map < SimpleTypeKind , unknown > ( ) ; for ( const member of simpleType . types ) { const resolvedType = resolveType ( member , parameterMap ) ; typeKindMap . set ( resolvedType . kind , [ ... ( typeKindMap . get ( resolvedType . kind ) || [ ] ) , resolvedType ] ) ; switch ( resolvedType . kind ) { case "NEVER" : return NEVER_TYPE ; } if ( isSimpleTypePrimitive ( resolvedType ) ) { if ( isSimpleTypeLiteral ( resolvedType ) ) { if ( primitiveLiteralSet . has ( resolvedType . kind ) && primitiveLiteralSet . get ( resolvedType . kind ) !== resolvedType . value ) { return NEVER_TYPE ; } primitiveLiteralSet . set ( resolvedType . kind , resolvedType . value ) ; } else { primitiveSet . add ( resolvedType . kind ) ; if ( primitiveSet . size > 1 ) { return NEVER_TYPE ; } } } } if ( ( typeKindMap . get ( "TUPLE" ) ?. length || 0 ) > 1 ) { let len = undefined ; for ( const type of typeKindMap . get ( "TUPLE" ) as SimpleTypeTuple [ ] ) { if ( len != null && len !== type . members . length ) { return NEVER_TYPE ; } len = type . members . length ; } } if ( typeKindMap . size === 1 && ( typeKindMap . get ( "OBJECT" ) ?. length || 0 ) > 1 ) { const members = new Map < string , SimpleTypeMemberNamed > ( ) ; for ( const type of typeKindMap . get ( "OBJECT" ) as SimpleTypeObject [ ] ) { for ( const member of type . members || [ ] ) { if ( members . has ( member . name ) ) { const combinedMemberType = reduceIntersectionIfPossible ( { kind : "INTERSECTION" , types : [ members . get ( member . name ) ! . type , member . type ] } , parameterMap ) ; if ( combinedMemberType . kind === "NEVER" ) { return combinedMemberType ; } members . set ( member . name , { ... member , type : combinedMemberType } ) ; } else { members . set ( member . name , member ) ; } } } return { ... ( typeKindMap . get ( "OBJECT" ) ! [ 0 ] as SimpleTypeObject ) , members : Array . from ( members . values ( ) ) } ; } return simpleType ; } function isObjectEmpty ( simpleType , { ignoreOptionalMembers } ) { return simpleType . members == null || simpleType . members . length === 0 || ( ignoreOptionalMembers && ! simpleType . members . some ( m => ! m . optional ) ) || false ; } export function resolveType ( simpleType , parameterMap ) { return resolveTypeUnsafe ( simpleType , parameterMap ) ; } function logDebugHeader ( typeA , typeB , options ) { const silentConfig = { ... options . config , debug : false , maxOps : 20 , maxDepth : 20 } ; let result ; try { result = isAssignableToSimpleType ( typeA , typeB , silentConfig ) ; } catch ( e ) { result = e . message ; } const depthChars = "   " . repeat ( options . depth ) ; const firstLogPart = ` ${ depthChars }${ simpleTypeToStringLazy ( typeA ) } ${ colorText ( options , ">:" , "cyan" ) } ${ simpleTypeToStringLazy ( typeB ) }   [${ typeA . kind } === ${ typeB . kind }]` ; let text = `${ firstLogPart } ${ " " . repeat ( Math . max ( 2 , 120 - firstLogPart . length ) ) }${ colorText ( options , options . depth , "yellow" ) } ### (${ typeA . name || "???" } === ${ typeB . name || "???" }) [result=${ colorText ( options , result , result === true ? "green" : "red" ) }]` ; if ( options . depth >= 50 ) { if ( options . depth === 50 ) { text = `Nested comparisons reach 100. Skipping logging...` ; } else { return ; } } ( options . config . debugLog || console . log ) ( text ) ; } function logDebug ( options , title , ... args ) { const depthChars = "   " . repeat ( options . depth ) ; const text = `${ depthChars } [${ colorText ( options , title , "blue" ) }] ${ args . join ( " " ) }` ; ( options . config . debugLog || console . log ) ( colorText ( options , text , "gray" ) ) ; } function simpleTypeToStringLazy ( simpleType ) { if ( simpleType == null ) { return "???" ; } return simpleTypeToString ( simpleType ) ; } function colorText ( options , text , color ) { if ( options . config . debugLog != null ) { return `${ text }` ; } const RESET = "\x1b[0m" ; const COLOR = ( ( ) => { switch ( color ) { case "gray" : return "\x1b[2m\x1b[37m" ; case "red" : return "\x1b[31m" ; case "green" : return "\x1b[32m" ; case "yellow" : return "\x1b[33m" ; case "blue" : return "\x1b[34m" ; case "cyan" : return "\x1b[2m\x1b[36m" ; } } ) ( ) ; return `${ COLOR }${ text }${ RESET }` ; } const PRIMITIVE_TYPE_TO_LITERAL_MAP = { [ "STRING" ] : "STRING_LITERAL" , [ "NUMBER" ] : "NUMBER_LITERAL" , [ "BOOLEAN" ] : "BOOLEAN_LITERAL" , [ "BIG_INT" ] : "BIG_INT_LITERAL" , [ "ES_SYMBOL" ] : "ES_SYMBOL_UNIQUE" } as unknown as Record < SimpleTypeKind , SimpleTypeKind | undefined > ;