UNPKG

ts-simple-type

Version:

Relationship type checker functions for Typescript types.

2,202 lines 94.3 kB
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var tsModule = require('typescript');
require('util');

// Collect all values on place. This is a map so Typescript will complain if we forget any kind.
const SIMPLE_TYPE_MAP = {
    NUMBER_LITERAL: "primitive_literal",
    STRING_LITERAL: "primitive_literal",
    BIG_INT_LITERAL: "primitive_literal",
    BOOLEAN_LITERAL: "primitive_literal",
    ES_SYMBOL_UNIQUE: "primitive_literal",
    BIG_INT: "primitive",
    BOOLEAN: "primitive",
    NULL: "primitive",
    UNDEFINED: "primitive",
    VOID: "primitive",
    ES_SYMBOL: "primitive",
    NUMBER: "primitive",
    STRING: "primitive",
    NON_PRIMITIVE: undefined,
    ENUM_MEMBER: undefined,
    ALIAS: undefined,
    ANY: undefined,
    ARRAY: undefined,
    CLASS: undefined,
    DATE: undefined,
    ENUM: undefined,
    FUNCTION: undefined,
    GENERIC_ARGUMENTS: undefined,
    GENERIC_PARAMETER: undefined,
    INTERFACE: undefined,
    INTERSECTION: undefined,
    METHOD: undefined,
    NEVER: undefined,
    OBJECT: undefined,
    PROMISE: undefined,
    TUPLE: undefined,
    UNION: undefined,
    UNKNOWN: undefined
};
const LITERAL_TYPE_KINDS = Object.keys(SIMPLE_TYPE_MAP).filter(kind => SIMPLE_TYPE_MAP[kind] === "primitive_literal");
function isSimpleTypeLiteral(type) {
    return LITERAL_TYPE_KINDS.includes(type.kind);
}
const PRIMITIVE_TYPE_KINDS = [...LITERAL_TYPE_KINDS, ...Object.keys(SIMPLE_TYPE_MAP).filter(kind => SIMPLE_TYPE_MAP[kind] === "primitive")];
function isSimpleTypePrimitive(type) {
    return PRIMITIVE_TYPE_KINDS.includes(type.kind);
}
// All kinds
const SIMPLE_TYPE_KINDS = Object.keys(SIMPLE_TYPE_MAP);
function isSimpleType(type) {
    return typeof type === "object" && type != null && "kind" in type && Object.values(SIMPLE_TYPE_KINDS).find((key) => key === type.kind) != null;
}

let selectedTSModule = tsModule;
function setTypescriptModule(ts) {
    selectedTSModule = ts;
}
function getTypescriptModule() {
    return selectedTSModule;
}

function or(list, match) {
    return list.find((a, i) => match(a, i)) != null;
}
function and(list, match) {
    return list.find((a, i) => !match(a, i)) == null;
}

function isTypeChecker(obj) {
    return obj != null && typeof obj === "object" && "getSymbolAtLocation" in obj;
}
function isProgram(obj) {
    return obj != null && typeof obj === "object" && "getTypeChecker" in obj && "getCompilerOptions" in obj;
}
function isNode(obj) {
    return obj != null && typeof obj === "object" && "kind" in obj && "flags" in obj && "pos" in obj && "end" in obj;
}
function typeHasFlag(type, flag, op = "and") {
    return hasFlag(type.flags, flag, op);
}
function hasFlag(flags, flag, op = "and") {
    if (Array.isArray(flag)) {
        return (op === "and" ? and : or)(flag, f => hasFlag(flags, f));
    }
    return (flags & flag) !== 0;
}
function isBoolean(type, ts) {
    var _a;
    return typeHasFlag(type, ts.TypeFlags.BooleanLike) || ((_a = type.symbol) === null || _a === void 0 ? void 0 : _a.name) === "Boolean";
}
function isBooleanLiteral(type, ts) {
    return typeHasFlag(type, ts.TypeFlags.BooleanLiteral);
}
function isBigIntLiteral(type, ts) {
    return typeHasFlag(type, ts.TypeFlags.BigIntLiteral);
}
function isUniqueESSymbol(type, ts) {
    return typeHasFlag(type, ts.TypeFlags.UniqueESSymbol);
}
function isESSymbolLike(type, ts) {
    var _a;
    return typeHasFlag(type, ts.TypeFlags.ESSymbolLike) || ((_a = type.symbol) === null || _a === void 0 ? void 0 : _a.name) === "Symbol";
}
function isLiteral(type, ts) {
    return type.isLiteral() || isBooleanLiteral(type, ts) || isBigIntLiteral(type, ts) || isUniqueESSymbol(type, ts);
}
function isString(type, ts) {
    var _a;
    return typeHasFlag(type, ts.TypeFlags.StringLike) || ((_a = type.symbol) === null || _a === void 0 ? void 0 : _a.name) === "String";
}
function isNumber(type, ts) {
    var _a;
    return typeHasFlag(type, ts.TypeFlags.NumberLike) || ((_a = type.symbol) === null || _a === void 0 ? void 0 : _a.name) === "Number";
}
function isEnum(type, ts) {
    return typeHasFlag(type, ts.TypeFlags.EnumLike);
}
function isBigInt(type, ts) {
    var _a;
    return typeHasFlag(type, ts.TypeFlags.BigIntLike) || ((_a = type.symbol) === null || _a === void 0 ? void 0 : _a.name) === "BigInt";
}
function isObject(type, ts) {
    var _a;
    return typeHasFlag(type, ts.TypeFlags.Object) || ((_a = type.symbol) === null || _a === void 0 ? void 0 : _a.name) === "Object";
}
function isNonPrimitive(type, ts) {
    var _a;
    return typeHasFlag(type, ts.TypeFlags.NonPrimitive) || ((_a = type.symbol) === null || _a === void 0 ? void 0 : _a.name) === "object";
}
function isThisType(type, ts) {
    var _a, _b;
    const kind = (_b = (_a = type.getSymbol()) === null || _a === void 0 ? void 0 : _a.valueDeclaration) === null || _b === void 0 ? void 0 : _b.kind;
    if (kind == null) {
        return false;
    }
    return hasFlag(kind, ts.SyntaxKind.ThisKeyword);
}
function isUnknown(type, ts) {
    return typeHasFlag(type, ts.TypeFlags.Unknown);
}
function isNull(type, ts) {
    return typeHasFlag(type, ts.TypeFlags.Null);
}
function isUndefined(type, ts) {
    return typeHasFlag(type, ts.TypeFlags.Undefined);
}
function isVoid(type, ts) {
    return typeHasFlag(type, ts.TypeFlags.VoidLike);
}
function isNever(type, ts) {
    return typeHasFlag(type, ts.TypeFlags.Never);
}
function isObjectTypeReference(type, ts) {
    return hasFlag(type.objectFlags, ts.ObjectFlags.Reference);
}
function isSymbol(obj) {
    return "flags" in obj && "name" in obj && "getDeclarations" in obj;
}
function isMethod(type, ts) {
    if (!isObject(type, ts))
        return false;
    const symbol = type.getSymbol();
    if (symbol == null)
        return false;
    return hasFlag(symbol.flags, ts.SymbolFlags.Method);
}
function getDeclaration(symbol, ts) {
    const declarations = symbol.getDeclarations();
    if (declarations == null || declarations.length === 0)
        return symbol.valueDeclaration;
    return declarations[0];
}
function isArray(type, checker, ts) {
    if (!isObject(type, ts))
        return false;
    const symbol = type.getSymbol();
    if (symbol == null)
        return false;
    return getTypeArguments(type, checker, ts).length === 1 && ["ArrayLike", "ReadonlyArray", "ConcatArray", "Array"].includes(symbol.getName());
}
function isPromise(type, checker, ts) {
    if (!isObject(type, ts))
        return false;
    const symbol = type.getSymbol();
    if (symbol == null)
        return false;
    return getTypeArguments(type, checker, ts).length === 1 && ["PromiseLike", "Promise"].includes(symbol.getName());
}
function isDate(type, ts) {
    if (!isObject(type, ts))
        return false;
    const symbol = type.getSymbol();
    if (symbol == null)
        return false;
    return symbol.getName() === "Date";
}
function isTupleTypeReference(type, ts) {
    const target = getTargetType(type, ts);
    if (target == null)
        return false;
    return (target.objectFlags & ts.ObjectFlags.Tuple) !== 0;
}
function isFunction(type, ts) {
    if (!isObject(type, ts))
        return false;
    const symbol = type.getSymbol();
    if (symbol == null)
        return false;
    return (symbol.flags & ts.SymbolFlags.Function) !== 0 || symbol.escapedName === "Function" || (symbol.members != null && symbol.members.has("__call"));
}
function getTypeArguments(type, checker, ts) {
    if (isObject(type, ts)) {
        if (isObjectTypeReference(type, ts)) {
            if ("getTypeArguments" in checker) {
                return Array.from(checker.getTypeArguments(type) || []);
            }
            else {
                return Array.from(type.typeArguments || []);
            }
        }
    }
    return [];
}
function getTargetType(type, ts) {
    if (isObject(type, ts) && isObjectTypeReference(type, ts)) {
        return type.target;
    }
}
function getModifiersFromDeclaration(declaration, ts) {
    const tsModifiers = ts.getCombinedModifierFlags(declaration);
    const modifiers = [];
    const map = {
        [ts.ModifierFlags.Export]: "EXPORT",
        [ts.ModifierFlags.Ambient]: "AMBIENT",
        [ts.ModifierFlags.Public]: "PUBLIC",
        [ts.ModifierFlags.Private]: "PRIVATE",
        [ts.ModifierFlags.Protected]: "PROTECTED",
        [ts.ModifierFlags.Static]: "STATIC",
        [ts.ModifierFlags.Readonly]: "READONLY",
        [ts.ModifierFlags.Abstract]: "ABSTRACT",
        [ts.ModifierFlags.Async]: "ASYNC",
        [ts.ModifierFlags.Default]: "DEFAULT"
    };
    Object.entries(map).forEach(([tsModifier, modifierKind]) => {
        if ((tsModifiers & Number(tsModifier)) !== 0) {
            modifiers.push(modifierKind);
        }
    });
    return modifiers;
}
function isImplicitGeneric(type, checker, ts) {
    return isArray(type, checker, ts) || isTupleTypeReference(type, ts) || isPromise(type, checker, ts);
}
function isMethodSignature(type, ts) {
    const symbol = type.getSymbol();
    if (symbol == null)
        return false;
    if (!isObject(type, ts))
        return false;
    if (type.getCallSignatures().length === 0)
        return false;
    const decl = getDeclaration(symbol);
    if (decl == null)
        return false;
    return decl.kind === ts.SyntaxKind.MethodSignature;
}

const DEFAULT_TYPE_CACHE = new WeakMap();
const DEFAULT_RESULT_CACHE = new Map();
const DEFAULT_GENERIC_PARAMETER_TYPE = { kind: "UNKNOWN" };
const NEVER_TYPE = { kind: "NEVER" };

function resolveType(simpleType, parameterMap = new Map()) {
    switch (simpleType.kind) {
        case "GENERIC_PARAMETER": {
            const resolvedArgument = parameterMap === null || parameterMap === void 0 ? void 0 : parameterMap.get(simpleType.name);
            return resolveType(resolvedArgument || /*simpleType.default ||*/ DEFAULT_GENERIC_PARAMETER_TYPE, parameterMap);
        }
        case "GENERIC_ARGUMENTS": {
            const updatedGenericParameterMap = extendTypeParameterMap(simpleType, parameterMap);
            return resolveType(simpleType.target, updatedGenericParameterMap);
        }
        default:
            return simpleType;
    }
}

/**
 * Returns a type that represents the length of the Tuple type
 * Read more here: https://github.com/microsoft/TypeScript/pull/24897
 * @param tuple
 */
function getTupleLengthType(tuple) {
    // When the tuple has rest argument, return "number"
    if (tuple.rest) {
        return {
            kind: "NUMBER"
        };
    }
    // Else return an intersection of number literals that represents all possible lengths
    const minLength = tuple.members.filter(member => !member.optional).length;
    if (minLength === tuple.members.length) {
        return {
            kind: "NUMBER_LITERAL",
            value: minLength
        };
    }
    return {
        kind: "UNION",
        types: new Array(tuple.members.length - minLength + 1).fill(0).map((_, i) => ({
            kind: "NUMBER_LITERAL",
            value: minLength + i
        }))
    };
}
function simplifySimpleTypes(types) {
    let newTypes = [...types];
    const NULLABLE_TYPE_KINDS = ["UNDEFINED", "NULL"];
    // Only include one instance of primitives and literals
    newTypes = newTypes.filter((type, i) => {
        // Only include one of each literal with specific value
        if (isSimpleTypeLiteral(type)) {
            return !newTypes.slice(0, i).some(newType => newType.kind === type.kind && newType.value === type.value);
        }
        if (PRIMITIVE_TYPE_KINDS.includes(type.kind) || NULLABLE_TYPE_KINDS.includes(type.kind)) {
            // Remove this type from the array if there is already a primitive in the array
            return !newTypes.slice(0, i).some(t => t.kind === type.kind);
        }
        return true;
    });
    // Simplify boolean literals
    const booleanLiteralTypes = newTypes.filter((t) => t.kind === "BOOLEAN_LITERAL");
    if (booleanLiteralTypes.find(t => t.value === true) != null && booleanLiteralTypes.find(t => t.value === false) != null) {
        newTypes = [...newTypes.filter(type => type.kind !== "BOOLEAN_LITERAL"), { kind: "BOOLEAN" }];
    }
    // Reorder "NULL" and "UNDEFINED" to be last
    const nullableTypes = newTypes.filter((t) => NULLABLE_TYPE_KINDS.includes(t.kind));
    if (nullableTypes.length > 0) {
        newTypes = [
            ...newTypes.filter(t => !NULLABLE_TYPE_KINDS.includes(t.kind)),
            ...nullableTypes.sort((t1, t2) => (t1.kind === "NULL" ? (t2.kind === "UNDEFINED" ? -1 : 0) : t2.kind === "NULL" ? 1 : 0))
        ];
    }
    return newTypes;
}
function extendTypeParameterMap(genericType, existingMap) {
    const target = resolveType(genericType.target, existingMap);
    if ("typeParameters" in target) {
        const parameterEntries = (target.typeParameters || []).map((parameter, i) => {
            const typeArg = genericType.typeArguments[i];
            const resolvedTypeArg = typeArg == null ? /*parameter.default || */ DEFAULT_GENERIC_PARAMETER_TYPE : resolveType(typeArg, existingMap);
            //return [parameter.name, genericType.typeArguments[i] || parameter.default || { kind: "ANY" }] as [string, SimpleType];
            return [parameter.name, resolvedTypeArg];
        });
        const allParameterEntries = [...existingMap.entries(), ...parameterEntries];
        return new Map(allParameterEntries);
    }
    return existingMap;
}

function toSimpleType(type, checker, options = {}) {
    if (isSimpleType(type)) {
        return type;
    }
    checker = checker;
    if (isNode(type)) {
        // "type" is a "Node", convert it to a "Type" and continue.
        return toSimpleType(checker.getTypeAtLocation(type), checker);
    }
    return toSimpleTypeCached(type, {
        checker,
        eager: options.eager,
        cache: options.cache || DEFAULT_TYPE_CACHE,
        ts: getTypescriptModule()
    });
}
function toSimpleTypeCached(type, options) {
    if (options.cache.has(type)) {
        return options.cache.get(type);
    }
    // This function will resolve the type and assign the content to "target".
    // This way we can cache "target" before calling "toSimpleTypeInternal" recursively
    const resolveType = (target) => {
        // Construct the simple type recursively
        //const simpleTypeOverwrite = options.cache.has(type) ? options.cache.get(type)! : toSimpleTypeInternal(type, options);
        const simpleTypeOverwrite = toSimpleTypeInternal(type, options);
        // Strip undefined keys to make the output cleaner
        Object.entries(simpleTypeOverwrite).forEach(([k, v]) => {
            if (v == null)
                delete simpleTypeOverwrite[k];
        });
        // Transfer properties on the simpleType to the placeholder
        // This makes it possible to keep on using the reference "placeholder".
        Object.assign(target, simpleTypeOverwrite);
    };
    if (options.eager === true) {
        // Make and cache placeholder
        const placeholder = {};
        options.cache.set(type, placeholder);
        // Resolve type into placeholder
        resolveType(placeholder);
        Object.freeze(placeholder);
        return placeholder;
    }
    else {
        const placeholder = {};
        // A function that only resolves the type once
        let didResolve = false;
        const ensureResolved = () => {
            if (!didResolve) {
                resolveType(placeholder);
                didResolve = true;
            }
        };
        // Use "toStringTag" as a hook into resolving the type.
        // If we don't have this hook, console.log would always print "{}" because the type hasn't been resolved
        Object.defineProperty(placeholder, Symbol.toStringTag, {
            get() {
                resolveType(placeholder);
                // Don't return any tag. Only use this function as a hook for calling "resolveType"
                return undefined;
            }
        });
        // Return a proxy with the purpose of resolving the type lazy
        const proxy = new Proxy(placeholder, {
            ownKeys(target) {
                ensureResolved();
                return [...Object.getOwnPropertyNames(target), ...Object.getOwnPropertySymbols(target)];
            },
            has(target, p) {
                // Always return true if we test for "kind", but don't resolve the type
                // This way "isSimpleType" (which checks for "kind") will succeed without resolving the type
                if (p === "kind") {
                    return true;
                }
                ensureResolved();
                return p in target;
            },
            getOwnPropertyDescriptor(target, p) {
                ensureResolved();
                return Object.getOwnPropertyDescriptor(target, p);
            },
            get: (target, p) => {
                ensureResolved();
                return target[p];
            },
            set: (target, p) => {
                throw new TypeError(`Cannot assign to read only property '${p}'`);
            }
        });
        options.cache.set(type, proxy);
        return proxy;
    }
}
/**
 * Tries to lift a potential generic type and wrap the result in a "GENERIC_ARGUMENTS" simple type and/or "ALIAS" type.
 * Returns the "simpleType" otherwise.
 * @param simpleType
 * @param type
 * @param options
 */
function liftGenericType(type, options) {
    // Check for alias reference
    if (type.aliasSymbol != null) {
        const aliasDeclaration = getDeclaration(type.aliasSymbol, options.ts);
        const typeParameters = getTypeParameters(aliasDeclaration, options);
        return {
            target: type,
            generic: target => {
                // Lift the simple type to an ALIAS type.
                const aliasType = {
                    kind: "ALIAS",
                    name: type.aliasSymbol.getName() || "",
                    target,
                    typeParameters
                };
                // Lift the alias type if it uses generic arguments.
                if (type.aliasTypeArguments != null) {
                    const typeArguments = Array.from(type.aliasTypeArguments || []).map(t => toSimpleTypeCached(t, options));
                    return {
                        kind: "GENERIC_ARGUMENTS",
                        target: aliasType,
                        typeArguments
                    };
                }
                return target;
            }
        };
    }
    // Check if the type is a generic interface/class reference and lift it.
    else if (isObject(type, options.ts) && isObjectTypeReference(type, options.ts) && type.typeArguments != null && type.typeArguments.length > 0) {
        // Special case for array, tuple and promise, they are generic in themselves
        if (isImplicitGeneric(type, options.checker, options.ts)) {
            return undefined;
        }
        return {
            target: type.target,
            generic: target => {
                const typeArguments = Array.from(type.typeArguments || []).map(t => toSimpleTypeCached(t, options));
                return {
                    kind: "GENERIC_ARGUMENTS",
                    target,
                    typeArguments
                };
            }
        };
    }
    return undefined;
}
function toSimpleTypeInternal(type, options) {
    const { checker, ts } = options;
    const symbol = type.getSymbol();
    const name = symbol != null ? getRealSymbolName(symbol, ts) : undefined;
    let simpleType;
    const generic = liftGenericType(type, options);
    if (generic != null) {
        type = generic.target;
    }
    if (isLiteral(type, ts)) {
        const literalSimpleType = primitiveLiteralToSimpleType(type, checker, ts);
        if (literalSimpleType != null) {
            // Enum members
            if (symbol != null && symbol.flags & ts.SymbolFlags.EnumMember) {
                const parentSymbol = symbol.parent;
                if (parentSymbol != null) {
                    return {
                        name: name || "",
                        fullName: `${parentSymbol.name}.${name}`,
                        kind: "ENUM_MEMBER",
                        type: literalSimpleType
                    };
                }
            }
            // Literals types
            return literalSimpleType;
        }
    }
    // Primitive types
    else if (isString(type, ts)) {
        simpleType = { kind: "STRING", name };
    }
    else if (isNumber(type, ts)) {
        simpleType = { kind: "NUMBER", name };
    }
    else if (isBoolean(type, ts)) {
        simpleType = { kind: "BOOLEAN", name };
    }
    else if (isBigInt(type, ts)) {
        simpleType = { kind: "BIG_INT", name };
    }
    else if (isESSymbolLike(type, ts)) {
        simpleType = { kind: "ES_SYMBOL", name };
    }
    else if (isUndefined(type, ts)) {
        simpleType = { kind: "UNDEFINED", name };
    }
    else if (isNull(type, ts)) {
        simpleType = { kind: "NULL", name };
    }
    else if (isUnknown(type, ts)) {
        simpleType = { kind: "UNKNOWN", name };
    }
    else if (isVoid(type, ts)) {
        simpleType = { kind: "VOID", name };
    }
    else if (isNever(type, ts)) {
        simpleType = { kind: "NEVER", name };
    }
    // Enum
    else if (isEnum(type, ts) && type.isUnion()) {
        simpleType = {
            name: name || "",
            kind: "ENUM",
            types: type.types.map(t => toSimpleTypeCached(t, options))
        };
    }
    // Promise
    else if (isPromise(type, checker, ts)) {
        simpleType = {
            kind: "PROMISE",
            name,
            type: toSimpleTypeCached(getTypeArguments(type, checker, ts)[0], options)
        };
    }
    // Unions and intersections
    else if (type.isUnion()) {
        simpleType = {
            kind: "UNION",
            types: simplifySimpleTypes(type.types.map(t => toSimpleTypeCached(t, options))),
            name
        };
    }
    else if (type.isIntersection()) {
        simpleType = {
            kind: "INTERSECTION",
            types: simplifySimpleTypes(type.types.map(t => toSimpleTypeCached(t, options))),
            name
        };
    }
    // Date
    else if (isDate(type, ts)) {
        simpleType = {
            kind: "DATE",
            name
        };
    }
    // Array
    else if (isArray(type, checker, ts)) {
        simpleType = {
            kind: "ARRAY",
            type: toSimpleTypeCached(getTypeArguments(type, checker, ts)[0], options),
            name
        };
    }
    else if (isTupleTypeReference(type, ts)) {
        const types = getTypeArguments(type, checker, ts);
        const minLength = type.target.minLength;
        simpleType = {
            kind: "TUPLE",
            rest: type.target.hasRestElement || false,
            members: types.map((childType, i) => {
                return {
                    optional: i >= minLength,
                    type: toSimpleTypeCached(childType, options)
                };
            }),
            name
        };
    }
    // Method signatures
    else if (isMethodSignature(type, ts)) {
        const callSignatures = type.getCallSignatures();
        simpleType = getSimpleFunctionFromCallSignatures(callSignatures, options);
    }
    // Class
    else if (type.isClass() && symbol != null) {
        const classDecl = getDeclaration(symbol);
        if (classDecl != null && ts.isClassDeclaration(classDecl)) {
            const ctor = (() => {
                const ctorSymbol = symbol != null && symbol.members != null ? symbol.members.get("__constructor") : undefined;
                if (ctorSymbol != null && symbol != null) {
                    const ctorDecl = ctorSymbol.declarations.length > 0 ? ctorSymbol.declarations[0] : ctorSymbol.valueDeclaration;
                    if (ctorDecl != null && ts.isConstructorDeclaration(ctorDecl)) {
                        return getSimpleFunctionFromSignatureDeclaration(ctorDecl, options);
                    }
                }
            })();
            const call = getSimpleFunctionFromCallSignatures(type.getCallSignatures(), options);
            const members = checker
                .getPropertiesOfType(type)
                .map(symbol => {
                const declaration = getDeclaration(symbol);
                // Some instance properties may have an undefined declaration.
                // Since we can't do too much without a declaration, filtering
                // these out seems like the best strategy for the moment.
                //
                // See https://github.com/runem/web-component-analyzer/issues/60 for
                // more info.
                if (declaration == null)
                    return null;
                return {
                    name: symbol.name,
                    optional: (symbol.flags & ts.SymbolFlags.Optional) !== 0,
                    modifiers: getModifiersFromDeclaration(declaration, ts),
                    type: toSimpleTypeCached(checker.getTypeAtLocation(declaration), options)
                };
            })
                .filter((member) => member != null);
            const typeParameters = getTypeParameters(getDeclaration(symbol), options);
            simpleType = {
                kind: "CLASS",
                name,
                call,
                ctor,
                typeParameters,
                members
            };
        }
    }
    // Interface
    else if ((type.isClassOrInterface() || isObject(type, ts)) && !((symbol === null || symbol === void 0 ? void 0 : symbol.name) === "Function")) {
        // Handle the empty object
        if (isObject(type, ts) && (symbol === null || symbol === void 0 ? void 0 : symbol.name) === "Object") {
            return {
                kind: "OBJECT"
            };
        }
        const members = type.getProperties().map(symbol => {
            const declaration = getDeclaration(symbol);
            return {
                name: symbol.name,
                optional: (symbol.flags & ts.SymbolFlags.Optional) !== 0,
                modifiers: declaration != null ? getModifiersFromDeclaration(declaration, ts) : [],
                type: toSimpleTypeCached(checker.getTypeAtLocation(symbol.valueDeclaration), options)
            };
        });
        const ctor = getSimpleFunctionFromCallSignatures(type.getConstructSignatures(), options);
        const call = getSimpleFunctionFromCallSignatures(type.getCallSignatures(), options);
        const typeParameters = (type.isClassOrInterface() && type.typeParameters != null ? type.typeParameters.map(t => toSimpleTypeCached(t, options)) : undefined) ||
            (symbol != null ? getTypeParameters(getDeclaration(symbol), options) : undefined);
        let indexType = {};
        if (type.getStringIndexType()) {
            indexType["STRING"] = toSimpleTypeCached(type.getStringIndexType(), options);
        }
        if (type.getNumberIndexType()) {
            indexType["NUMBER"] = toSimpleTypeCached(type.getNumberIndexType(), options);
        }
        if (Object.keys(indexType).length === 0) {
            indexType = undefined;
        }
        // Simplify: if there is only a single "call" signature and nothing else, just return the call signature
        /*if (call != null && members.length === 0 && ctor == null && indexType == null) {
            return { ...call, name, typeParameters };
        }*/
        simpleType = {
            kind: type.isClassOrInterface() ? "INTERFACE" : "OBJECT",
            typeParameters,
            ctor,
            members,
            name,
            indexType,
            call
        };
    }
    // Handle "object" type
    else if (isNonPrimitive(type, ts)) {
        return {
            kind: "NON_PRIMITIVE"
        };
    }
    // Function
    else if (symbol != null && (isFunction(type, ts) || isMethod(type, ts))) {
        simpleType = getSimpleFunctionFromCallSignatures(type.getCallSignatures(), options, name);
        if (simpleType == null) {
            simpleType = {
                kind: "FUNCTION",
                name
            };
        }
    }
    // Type Parameter
    else if (type.isTypeParameter() && symbol != null) {
        // This type
        if (isThisType(type, ts) && symbol.valueDeclaration != null) {
            return toSimpleTypeCached(checker.getTypeAtLocation(symbol.valueDeclaration), options);
        }
        const defaultType = type.getDefault();
        const defaultSimpleType = defaultType != null ? toSimpleTypeCached(defaultType, options) : undefined;
        simpleType = {
            kind: "GENERIC_PARAMETER",
            name: symbol.getName(),
            default: defaultSimpleType
        };
    }
    // If no type was found, return "ANY"
    if (simpleType == null) {
        simpleType = {
            kind: "ANY",
            name
        };
    }
    // Lift generic types and aliases if possible
    if (generic != null) {
        return generic.generic(simpleType);
    }
    return simpleType;
}
function primitiveLiteralToSimpleType(type, checker, ts) {
    if (type.isNumberLiteral()) {
        return {
            kind: "NUMBER_LITERAL",
            value: type.value
        };
    }
    else if (type.isStringLiteral()) {
        return {
            kind: "STRING_LITERAL",
            value: type.value
        };
    }
    else if (isBooleanLiteral(type, ts)) {
        // See https://github.com/Microsoft/TypeScript/issues/22269 for more information
        return {
            kind: "BOOLEAN_LITERAL",
            value: checker.typeToString(type) === "true"
        };
    }
    else if (isBigIntLiteral(type, ts)) {
        return {
            kind: "BIG_INT_LITERAL",
            /* global BigInt */
            value: BigInt(`${type.value.negative ? "-" : ""}${type.value.base10Value}`)
        };
    }
    else if (isUniqueESSymbol(type, ts)) {
        return {
            kind: "ES_SYMBOL_UNIQUE",
            value: String(type.escapedName) || Math.floor(Math.random() * 100000000).toString()
        };
    }
}
function getSimpleFunctionFromCallSignatures(signatures, options, fallbackName) {
    if (signatures.length === 0) {
        return undefined;
    }
    const signature = signatures[signatures.length - 1];
    const signatureDeclaration = signature.getDeclaration();
    return getSimpleFunctionFromSignatureDeclaration(signatureDeclaration, options, fallbackName);
}
function getSimpleFunctionFromSignatureDeclaration(signatureDeclaration, options, fallbackName) {
    const { checker } = options;
    const symbol = checker.getSymbolAtLocation(signatureDeclaration);
    const parameters = signatureDeclaration.parameters.map(parameterDecl => {
        const argType = checker.getTypeAtLocation(parameterDecl);
        return {
            name: parameterDecl.name.getText() || fallbackName,
            optional: parameterDecl.questionToken != null,
            type: toSimpleTypeCached(argType, options),
            rest: parameterDecl.dotDotDotToken != null,
            initializer: parameterDecl.initializer != null
        };
    });
    const name = symbol != null ? symbol.getName() : undefined;
    const type = checker.getTypeAtLocation(signatureDeclaration);
    const kind = isMethod(type, options.ts) ? "METHOD" : "FUNCTION";
    const signature = checker.getSignatureFromDeclaration(signatureDeclaration);
    const returnType = signature == null ? undefined : toSimpleTypeCached(checker.getReturnTypeOfSignature(signature), options);
    const typeParameters = getTypeParameters(signatureDeclaration, options);
    return { name, kind, returnType, parameters, typeParameters };
}
function getRealSymbolName(symbol, ts) {
    const name = symbol.getName();
    if (name != null && [ts.InternalSymbolName.Type, ts.InternalSymbolName.Object, ts.InternalSymbolName.Function].includes(name)) {
        return undefined;
    }
    return name;
}
function getTypeParameters(obj, options) {
    if (obj == null)
        return undefined;
    if (isSymbol(obj)) {
        const decl = getDeclaration(obj, options.ts);
        return getTypeParameters(decl, options);
    }
    if (options.ts.isClassDeclaration(obj) ||
        options.ts.isFunctionDeclaration(obj) ||
        options.ts.isFunctionTypeNode(obj) ||
        options.ts.isTypeAliasDeclaration(obj) ||
        options.ts.isMethodDeclaration(obj) ||
        options.ts.isMethodSignature(obj)) {
        return obj.typeParameters == null
            ? undefined
            : Array.from(obj.typeParameters)
                .map(td => options.checker.getTypeAtLocation(td))
                .map(t => toSimpleTypeCached(t, options));
    }
    return undefined;
}

function validateType(type, callback) {
    return validateTypeInternal(type, callback, new Map());
}
function validateTypeInternal(type, callback, parameterMap) {
    const res = callback(type);
    if (res != null) {
        return res;
    }
    switch (type.kind) {
        case "ENUM":
        case "UNION": {
            return or(type.types, childType => validateTypeInternal(childType, callback, parameterMap));
        }
        case "ALIAS": {
            return validateTypeInternal(type.target, callback, parameterMap);
        }
        case "INTERSECTION": {
            return and(type.types, childType => validateTypeInternal(childType, callback, parameterMap));
        }
        case "GENERIC_PARAMETER": {
            const resolvedArgument = parameterMap === null || parameterMap === void 0 ? void 0 : parameterMap.get(type.name);
            return validateTypeInternal(resolvedArgument || DEFAULT_GENERIC_PARAMETER_TYPE, callback, parameterMap);
        }
        case "GENERIC_ARGUMENTS": {
            const updatedGenericParameterMap = extendTypeParameterMap(type, parameterMap);
            return validateTypeInternal(type.target, callback, updatedGenericParameterMap);
        }
    }
    return false;
}

function isAssignableToSimpleTypeKind(type, kind, optionsOrChecker, options = {}) {
    const checker = isTypeChecker(optionsOrChecker) ? optionsOrChecker : undefined;
    options = (isTypeChecker(optionsOrChecker) || optionsOrChecker == null ? options : optionsOrChecker) || {};
    if (!isSimpleType(type)) {
        return isAssignableToSimpleTypeKind(toSimpleType(type, checker), kind, options);
    }
    return validateType(type, simpleType => {
        if (Array.isArray(kind) && or(kind, itemKind => simpleType.kind === itemKind)) {
            return true;
        }
        if (simpleType.kind === kind) {
            return true;
        }
        switch (simpleType.kind) {
            // Make sure that an object without members are treated as ANY
            case "OBJECT": {
                if (simpleType.members == null || simpleType.members.length === 0) {
                    return isAssignableToSimpleTypeKind({ kind: "ANY" }, kind, options);
                }
                break;
            }
            case "ANY": {
                return options.matchAny || false;
            }
            case "ENUM_MEMBER": {
                return isAssignableToSimpleTypeKind(simpleType.type, kind, options);
            }
        }
    });
}

function isAssignableToPrimitiveType(type, checkerOrOptions) {
    const checker = isTypeChecker(checkerOrOptions) ? checkerOrOptions : undefined;
    return isAssignableToSimpleTypeKind(type, PRIMITIVE_TYPE_KINDS, checker, { matchAny: true });
}

/**
 * Converts a simple type to a string.
 * @param type Simple Type
 */
function simpleTypeToString(type) {
    return simpleTypeToStringInternal(type, new Set());
}
function simpleTypeToStringInternal(type, visitTypeSet) {
    if (!isSimpleTypePrimitive(type)) {
        if (visitTypeSet.has(type)) {
            return "";
        }
        visitTypeSet = new Set([...visitTypeSet, type]);
    }
    switch (type.kind) {
        case "BOOLEAN_LITERAL":
            return String(type.value);
        case "NUMBER_LITERAL":
            return String(type.value);
        case "STRING_LITERAL":
            return `"${type.value}"`;
        case "BIG_INT_LITERAL":
            return `${type.value}n`;
        case "ES_SYMBOL":
            return `Symbol()`;
        case "ES_SYMBOL_UNIQUE":
            return `Symbol(${type.name})`;
        case "STRING":
            return "string";
        case "BOOLEAN":
            return "boolean";
        case "NUMBER":
            return "number";
        case "BIG_INT":
            return "bigint";
        case "UNDEFINED":
            return "undefined";
        case "NULL":
            return "null";
        case "ANY":
            return "any";
        case "UNKNOWN":
            return "unknown";
        case "VOID":
            return "void";
        case "NEVER":
            return "never";
        case "FUNCTION":
        case "METHOD": {
            if (type.kind === "FUNCTION" && type.name != null)
                return type.name;
            const argText = functionArgTypesToString(type.parameters || [], visitTypeSet);
            return `${type.typeParameters != null ? `<${type.typeParameters.map(tp => tp.name).join(",")}>` : ""}(${argText})${type.returnType != null ? ` => ${simpleTypeToStringInternal(type.returnType, visitTypeSet)}` : ""}`;
        }
        case "ARRAY": {
            const hasMultipleTypes = ["UNION", "INTERSECTION"].includes(type.type.kind);
            let memberType = simpleTypeToStringInternal(type.type, visitTypeSet);
            if (type.name != null && ["ArrayLike", "ReadonlyArray"].includes(type.name))
                return `${type.name}<${memberType}>`;
            if (hasMultipleTypes && type.type.name == null)
                memberType = `(${memberType})`;
            return `${memberType}[]`;
        }
        case "UNION": {
            if (type.name != null)
                return type.name;
            return truncateAndJoinList(type.types.map(t => simpleTypeToStringInternal(t, visitTypeSet)), " | ", { maxContentLength: 200 });
        }
        case "ENUM":
            return type.name;
        case "ENUM_MEMBER":
            return type.fullName;
        case "INTERSECTION":
            if (type.name != null)
                return type.name;
            return truncateAndJoinList(type.types.map(t => simpleTypeToStringInternal(t, visitTypeSet)), " & ", { maxContentLength: 200 });
        case "INTERFACE":
            if (type.name != null)
                return type.name;
        // this fallthrough is intentional
        case "OBJECT": {
            if (type.members == null || type.members.length === 0) {
                if (type.call == null && type.ctor == null) {
                    return "{}";
                }
                if (type.call != null && type.ctor == null) {
                    return simpleTypeToStringInternal(type.call, visitTypeSet);
                }
            }
            const entries = (type.members || []).map(member => {
                // this check needs to change in the future
                if (member.type.kind === "FUNCTION" || member.type.kind === "METHOD") {
                    const result = simpleTypeToStringInternal(member.type, visitTypeSet);
                    return `${member.name}${result.replace(" => ", ": ")}`;
                }
                return `${member.name}: ${simpleTypeToStringInternal(member.type, visitTypeSet)}`;
            });
            if (type.ctor != null) {
                entries.push(`new${simpleTypeToStringInternal(type.ctor, visitTypeSet)}`);
            }
            if (type.call != null) {
                entries.push(simpleTypeToStringInternal(type.call, visitTypeSet));
            }
            return `{ ${entries.join("; ")}${entries.length > 0 ? ";" : ""} }`;
        }
        case "TUPLE":
            return `[${type.members.map(member => `${simpleTypeToStringInternal(member.type, visitTypeSet)}${member.optional ? "?" : ""}`).join(", ")}]`;
        case "GENERIC_ARGUMENTS": {
            const { target, typeArguments } = type;
            return typeArguments.length === 0 ? target.name || "" : `${target.name}<${typeArguments.map(t => simpleTypeToStringInternal(t, visitTypeSet)).join(", ")}>`;
        }
        case "PROMISE":
            return `${type.name || "Promise"}<${simpleTypeToStringInternal(type.type, visitTypeSet)}>`;
        case "DATE":
            return "Date";
        default:
            return type.name || "";
    }
}
function truncateAndJoinList(items, combine, { maxLength, maxContentLength }) {
    const text = items.join(combine);
    // Truncate if too long
    let slice = 0;
    if (maxContentLength != null && text.length > maxContentLength) {
        let curLength = 0;
        for (const item of items) {
            curLength += item.length;
            slice++;
            if (curLength > maxContentLength) {
                break;
            }
        }
    }
    else if (maxLength != null && items.length > maxLength) {
        slice = maxLength;
    }
    if (slice !== 0) {
        return [...items.slice(0, slice), `... ${items.length - slice} more ...`].join(combine);
    }
    return text;
}
function functionArgTypesToString(argTypes, visitTypeSet) {
    return argTypes
        .map(arg => {
        return `${arg.rest ? "..." : ""}${arg.name}${arg.optional ? "?" : ""}: ${simpleTypeToStringInternal(arg.type, visitTypeSet)}`;
    })
        .join(", ");
}

/**
 * Returns if typeB is assignable to typeA.
 * @param typeA Type A
 * @param typeB Type B
 * @param config
 */
function isAssignableToSimpleType(typeA, typeB, config) {
    var _a, _b, _c, _d, _e, _f, _g;
    const userCache = config === null || config === void 0 ? void 0 : config.cache;
    config = {
        ...config,
        cache: undefined,
        strict: (_a = config === null || config === void 0 ? void 0 : config.strict) !== null && _a !== void 0 ? _a : true,
        strictFunctionTypes: (_c = (_b = config === null || config === void 0 ? void 0 : config.strictFunctionTypes) !== null && _b !== void 0 ? _b : config === null || config === void 0 ? void 0 : config.strict) !== null && _c !== void 0 ? _c : true,
        strictNullChecks: (_e = (_d = config === null || config === void 0 ? void 0 : config.strictNullChecks) !== null && _d !== void 0 ? _d : config === null || config === void 0 ? void 0 : config.strict) !== null && _e !== void 0 ? _e : true,
        maxDepth: (_f = config === null || config === void 0 ? void 0 : config.maxDepth) !== null && _f !== void 0 ? _f : 50,
        maxOps: (_g = config === null || config === void 0 ? void 0 : config.maxOps) !== null && _g !== void 0 ? _g : 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 === null || typeACache === void 0 ? void 0 : 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);
    }
    // Call "isAssignableToSimpleTypeInternal" with a mutated options object
    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 (options.config.debug) {
            logDebug(
                options,
                "caching",
                `Setting cache for comparison between ${simpleTypeToStringLazy(typeA)} (${typeA.kind}) and ${simpleTypeToStringLazy(typeB)} (${typeB.kind}). Result: ${result}`
            );
        }*/
        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) {
    // It's assumed that the "options" parameter is already an unique reference that is safe to mutate.
    // Mutate depth and "operations"
    options.depth = options.depth + 1;
    options.operations.value++;
    // Handle debugging nested calls to isAssignable
    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;
    }
    // When comparing types S and T, the relationship in question is assumed to be true
    //   for every directly or indirectly nested occurrence of the same S and the same T
    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;
        }
    }
    // We might need a better way of handling refs, but these check are good for now
    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;
    }
    // Handle two types being equal
    // Types are not necessarily equal if they have typeParams because we still need to check the actual generic arguments
    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();
    }
    // Make it possible to overwrite default behavior by running user defined logic for comparing types
    if (options.config.isAssignable != null) {
        const result = options.config.isAssignable(typeA, typeB, options.config);
        if (result != null) {
            //options.preventCaching();
            return result;
        }
    }
    // Any and unknown. Everything is assignable to "ANY" and "UNKNOWN"
    if (typeA.kind === "UNKNOWN" || typeA.kind === "ANY") {
        return true;
    }
    // Mutate options and add this comparison to "comparingTypes".
    // Only do this if one of the types is not a primitive to save memory.
    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;
    }
    // #####################
    // Expand typeB
    // #####################
    switch (typeB.kind) {
        // [typeB] (expand)
        case "UNION": {
            // Some types seems to absorb other types when type checking a union (eg. 'unknown').
            // Usually typescript will absorb those types for us, but not when working with generic parameters.
            // The following line needs to be improved.
            const types = typeB.types.filter(t => resolveType$1(t, options.genericParameterMapB) !== DEFAULT_GENERIC_PARAMETER_TYPE);
            return and(types, childTypeB => isAssignableToSimpleTypeCached(typeA, childTypeB, options));
        }
        // [typeB] (expand)
        case "INTERSECTION": {
            // If we compare an intersection against an intersection, we need to compare from typeA and not typeB
            // Example: [string, number] & [string] === [string, number] & [string]
            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);
            }
            // An intersection type I is assignable to a type T if any type in I is assignable to T.
            return or(combined.types, memberB => isAssignableToSimpleTypeCached(typeA, memberB, options));
        }
        // [typeB] (expand)
        case "ALIAS": {
            return isAssignableToSimpleTypeCached(typeA, typeB.target, options);
        }
        // [typeB] (expand)
        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
            });
        }
        // [typeB] (expand)
        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);
        }
    }
    // #####################
    // Compare typeB
    // #####################
    switch (typeB.kind) {
        // [typeB] (compare)
        case "ENUM_MEMBER": {
            return isAssignableToSimpleTypeCached(typeA, typeB.type, options);
        }
        // [typeB] (compare)
        case "ENUM": {
            return and(typeB.types, childTypeB => isAssignableToSimpleTypeCached(typeA, childTypeB, options));
        }
        // [typeB] (compare)
        case "UNDEFINED":
        case "NULL": {
            // When strict null checks are turned off, "undefined" and "null" are in the domain of every type but never
            if (!options.config.strictNullChecks) {
                return typeA.kind !== "NEVER";
            }
            break;
        }
        // [typeB] (compare)
        case "ANY": {
            // "any" can be assigned to anything but "never"
            return typeA.kind !== "NEVER";
        }
        // [typeB] (compare)
        case "NEVER": {
            // "never" can be assigned to anything
            return true;
        }
    }
    // #####################
    // Expand typeA
    // #####################
    switch (typeA.kind) {
        // [typeA] (expand)
        case "ALIAS": {
            return isAssignableToSimpleTypeCached(typeA.target, typeB, options);
        }
        // [typeA] (expand)
        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);
        }
        // [typeA] (expand)
        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
            });
        }
        // [typeA] (expand)
        case "UNION": {
            // Some types seems to absorb other types when type checking a union (eg. 'unknown').
            // Usually typescript will absorb those types for us, but not when working with generic parameters.
            // The following line needs to be improved.
            const types = typeA.types.filter(t => resolveType$1(t, options.genericParameterMapA) !== DEFAULT_GENERIC_PARAMETER_TYPE || typeB === DEFAULT_GENERIC_PARAMETER_TYPE);
            return or(types, childTypeA => isAssignableToSimpleTypeCached(childTypeA, typeB, options));
        }
        // [typeA] (expand)
        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);
            }
            // A type T is assignable to an intersection type I if T is assignable to each type in I.
            return and(combined.types, memberA => isAssignableToSimpleTypeCached(memberA, typeB, options));
        }
    }
    // #####################
    // Compare typeA
    // #####################
    switch (typeA.kind) {
        // [typeA] (compare)
        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";
        }
        // [typeA] (compare)
        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;
        }
        // [typeA] (compare)
        case "ENUM": {
            return or(typeA.types, childTypeA => isAssignableToSimpleTypeCached(childTypeA, typeB, options));
        }
        // [typeA] (compare)
        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;
        }
        // [typeA] (compare)
        case "ENUM_MEMBER": {
            // You can always assign a "number" | "number literal" to a "number literal" enum member type.
            if (resolveType$1(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);
        }
        // [typeA] (compare)
        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;
        }
        // [typeA] (compare)
        case "UNDEFINED":
        case "NULL": {
            return typeA.kind === typeB.kind;
        }
        // [typeA] (compare)
        case "VOID": {
            return typeB.kind === "VOID" || typeB.kind === "UNDEFINED";
        }
        // [typeA] (compare)
        case "NEVER": {
            return false;
        }
        // [typeA] (compare)
        // https://www.typescriptlang.org/docs/handbook/type-compatibility.html#comparing-two-functions
        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;
            // Any return type is assignable to void
            if (options.config.debug) {
                logDebug(options, "function", `Checking if return type of typeA is 'void'`);
            }
            if (!isAssignableToSimpleTypeKind(typeA.returnType, "VOID")) {
                //if (!isAssignableToSimpleTypeInternal(typeA.returnType, { kind: "VOID" }, options)) {
                if (options.config.debug) {
                    logDebug(options, "function", `Return type is not void. Checking return types`);
                }
                if (!isAssignableToSimpleTypeCached(typeA.returnType, typeB.returnType, options)) {
                    return false;
                }
            }
            // Test "this" types
            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;
                }
            }
            // Get all "non-this" params
            const paramTypesA = typeAThisParam == null ? typeA.parameters : typeA.parameters.filter(arg => arg !== typeAThisParam);
            const paramTypesB = typeBThisParam == null ? typeB.parameters : typeB.parameters.filter(arg => arg !== typeBThisParam);
            // A function with 0 params can be assigned to any other function
            if (paramTypesB.length === 0) {
                return true;
            }
            // A function with more required params than typeA isn't assignable
            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;
            // Compare the types of each param
            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 === null || paramA === void 0 ? void 0 : paramA.name) || "???"}' AND '${(paramB === null || paramB === void 0 ? void 0 : paramB.name) || "???"}'] Checking parameters ${options.config.strictFunctionTypes ? "[contravariant]" : "[bivariant]"}: [${(paramA === null || paramA === void 0 ? void 0 : paramA.type) == null ? "???" : simpleTypeToStringLazy(paramA.type)}  AND  ${(paramB === null || paramB === void 0 ? void 0 : paramB.type) == null ? "???" : simpleTypeToStringLazy(paramB.type)}]`);
                }
                // Try to find the last param in typeA. If it's a rest param, continue with that one
                if (paramA == null && (prevParamA === null || prevParamA === void 0 ? void 0 : 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;
                }
                // Try to find the last param in typeB. If it's a rest param, continue with that one
                if (paramB == null && (prevParamB === null || prevParamB === void 0 ? void 0 : 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 is not present, check if paramB is optional or not present as well
                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 isn't present, check if paramA is optional
                if (paramB == null) {
                    if (options.config.debug) {
                        logDebug(options, "function", `paramB is 'null' returning true`);
                    }
                    return true;
                }
                // Check if we are comparing a spread against a non-spread
                const resolvedTypeA = resolveType$1(paramA.type, options.genericParameterMapA);
                const resolvedTypeB = resolveType$1(paramB.type, options.genericParameterMapB);
                // Unpack the array of rest parameters if possible
                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)}'`);
                    }
                }
                // Check if the param types are assignable
                // Function parameter type checking is bivariant (when strictFunctionTypes is off) and contravariant (when strictFunctionTypes is on)
                if (!options.config.strictFunctionTypes) {
                    if (options.config.debug) {
                        logDebug(options, "function", `Checking covariant relationship`);
                    }
                    // Strict is off, therefore start by checking the covariant.
                    // The contravariant relationship will be checked afterwards resulting in bivariant behavior
                    if (isAssignableToSimpleTypeCached(paramAType, paramBType, options)) {
                        // Continue to next parameter
                        continue;
                    }
                }
                // There is something strange going on where it seems checking two methods is less strict and checking two functions.
                // I haven't found any documentation for this behavior, but it seems to be the case.
                /* Examples (with strictFunctionTypes):
                        // -----------------------------
                        interface I1 {
                            test(b: string | null): void;
                        }
                        interface I2 {
                            test(a: string): void;
                        }

                        // This will not fail
                        const thisWillNotFail: I1 = {} as I2;

                        // -----------------------------
                        interface I3 {
                            test: (b: string | null) => void;
                        }

                        interface I4 {
                            test: (a: string) => void;
                        }

                        // This will fail with:
                        //    Types of parameters 'a' and 'b' are incompatible.
                        //      Type 'string | null' is not assignable to type 'string'.
                        //         Type 'null' is not assignable to type 'string'
                        const thisWillFail: I3 = {} as I4;
                    */
                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`);
                }
                // Contravariant
                if (!isAssignableToSimpleTypeCached(paramBType, paramAType, newOptions)) {
                    return false;
                }
            }
            return true;
        }
        // [typeA] (compare)
        case "INTERFACE":
        case "OBJECT":
        case "CLASS": {
            // If there are no members check that "typeB" is not assignable to a set of incompatible type kinds
            // This is to check the empty object {} and Object
            const typeAHasZeroMembers = isObjectEmpty(typeA, {
                ignoreOptionalMembers: ["UNKNOWN", "NON_PRIMITIVE"].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"] : [])], {
                    matchAny: false
                });
            }
            switch (typeB.kind) {
                case "FUNCTION":
                case "METHOD":
                    return typeA.call != null && isAssignableToSimpleTypeCached(typeA.call, typeB, options);
                case "INTERFACE":
                case "OBJECT":
                case "CLASS": {
                    // Test both callable types
                    const membersA = typeA.members || [];
                    const membersB = typeB.members || [];
                    options.insideType = new Set([...options.insideType, typeA, typeB]);
                    // Check how many properties typeB has in common with typeA.
                    let membersInCommon = 0;
                    // Make sure that every required prop in typeA is present in typeB
                    const requiredMembersInTypeAExistsInTypeB = and(membersA, memberA => {
                        //if (memberA.optional) return true;
                        const memberB = membersB.find(memberB => memberA.name === memberB.name);
                        if (memberB != null)
                            membersInCommon += 1;
                        return memberB == null
                            ? // If corresponding "memberB" couldn't be found, return true if "memberA" is optional
                                memberA.optional
                            : // If corresponding "memberB" was found, return true if "memberA" is optional or "memberB" is not 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;
                    }
                    // Check if construct signatures are assignable (if any)
                    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;
                        }
                    }
                    // Check if call signatures are assignable (if any)
                    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;
                        }
                    }
                    // They are not assignable if typeB has 0 members in common with typeA, and there are more than 0 members in typeB.
                    // The ctor of classes are not counted towards if typeB is empty
                    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;
                    }
                    // Ensure that every member in typeB is assignable to corresponding members in typeA
                    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;
            }
        }
        // [typeA] (compare)
        case "TUPLE": {
            if (typeB.kind !== "TUPLE")
                return false;
            // Compare the length of each tuple, but compare the length type instead of the actual length
            // We compare the length type because Typescript compares the type of the "length" member of tuples
            if (!isAssignableToSimpleTypeCached(getTupleLengthType(typeA), getTupleLengthType(typeB), options)) {
                return false;
            }
            // Compare if typeB elements are assignable to typeA's rest element
            // Example: [string, ...boolean[]] === [any, true, 123]
            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);
                });
            }
            // Compare that every type of typeB is assignable to corresponding members in typeA
            return and(typeA.members, (memberA, i) => {
                const memberB = typeB.members[i];
                if (memberB == null)
                    return memberA.optional;
                return isAssignableToSimpleTypeCached(memberA.type, memberB.type, options);
            });
        }
        // [typeA] (compare)
        case "PROMISE": {
            return typeB.kind === "PROMISE" && isAssignableToSimpleTypeCached(typeA.type, typeB.type, options);
        }
        // [typeA] (compare)
        case "DATE": {
            return typeB.kind === "DATE";
        }
    }
    // If we some how end up here (we shouldn't), return "true" as a safe fallback
    // @ts-ignore
    return true;
}
function reduceIntersectionIfPossible(simpleType, parameterMap) {
    // DOCUMENTATION FROM TYPESCRIPT SOURCE CODE (getIntersectionType)
    // We normalize combinations of intersection and union types based on the distributive property of the '&'
    // operator. Specifically, because X & (A | B) is equivalent to X & A | X & B, we can transform intersection
    // types with union type constituents into equivalent union types with intersection type constituents and
    // effectively ensure that union types are always at the top level in type representations.
    //
    // We do not perform structural deduplication on intersection types. Intersection types are created only by the &
    // type operator and we can't reduce those because we want to support recursive intersection types. For example,
    // a type alias of the form "type List<T> = T & { next: List<T> }" cannot be reduced during its declaration.
    // Also, unlike union types, the order of the constituent types is preserved in order that overload resolution
    // for intersections of types with signatures can be deterministic.
    var _a, _b;
    // An intersection type is considered empty if it contains
    // the type never, or
    // more than one unit type or,
    // an object type and a nullable type (null or undefined), or
    // a string-like type and a type known to be non-string-like, or
    // a number-like type and a type known to be non-number-like, or
    // a symbol-like type and a type known to be non-symbol-like, or
    // a void-like type and a type known to be non-void-like, or
    // a non-primitive type and a type known to be primitive.
    const typeKindMap = new Map();
    const primitiveSet = new Set();
    const primitiveLiteralSet = new Map();
    for (const member of simpleType.types) {
        const resolvedType = resolveType$1(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 ((((_a = typeKindMap.get("TUPLE")) === null || _a === void 0 ? void 0 : _a.length) || 0) > 1) {
        let len = undefined;
        for (const type of typeKindMap.get("TUPLE")) {
            if (len != null && len !== type.members.length) {
                return NEVER_TYPE;
            }
            len = type.members.length;
        }
    }
    if (typeKindMap.size === 1 && (((_b = typeKindMap.get("OBJECT")) === null || _b === void 0 ? void 0 : _b.length) || 0) > 1) {
        const members = new Map();
        for (const type of typeKindMap.get("OBJECT")) {
            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], 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;
}
function resolveType$1(simpleType, parameterMap) {
    return resolveType(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) {
        // Too deep
        if (options.depth === 50) {
            text = `Nested comparisons reach 100. Skipping logging...`;
        }
        else {
            return;
        }
    }
    // eslint-disable-next-line no-console
    (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(" ")}`;
    // eslint-disable-next-line no-console
    (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"
};
/*const LITERAL_TYPE_TO_PRIMITIVE_TYPE_MAP = ({
    ["STRING_LITERAL"]: "STRING",
    ["NUMBER_LITERAL"]: "NUMBER",
    ["BOOLEAN_LITERAL"]: "BOOLEAN",
    ["BIG_INT_LITERAL"]: "BIG_INT",
    ["ES_SYMBOL_UNIQUE"]: "ES_SYMBOL"
} as unknown) as Record<SimpleTypeKind, SimpleTypeKind | undefined>;*/

function isAssignableToType(typeA, typeB, checkerOrOptions, options) {
    if (typeA === typeB)
        return true;
    // Get the correct TypeChecker
    const checker = isTypeChecker(checkerOrOptions) ? checkerOrOptions : isProgram(checkerOrOptions) ? checkerOrOptions.getTypeChecker() : undefined;
    // Get the correct options. Potentially merge user given options with program options.
    options = {
        ...(checkerOrOptions == null ? {} : isProgram(checkerOrOptions) ? checkerOrOptions.getCompilerOptions() : isTypeChecker(checkerOrOptions) ? {} : checkerOrOptions),
        ...(options || {})
    };
    // Check if the types are nodes (in which case we need to get the type of the node)
    typeA = isNode(typeA) ? checker.getTypeAtLocation(typeA) : typeA;
    typeB = isNode(typeB) ? checker.getTypeAtLocation(typeB) : typeB;
    // Use native "isTypeAssignableTo" if both types are native TS-types and "isTypeAssignableTo" is exposed on TypeChecker
    if (!isSimpleType(typeA) && !isSimpleType(typeB) && checker != null && checker.isTypeAssignableTo != null) {
        return checker.isTypeAssignableTo(typeB, typeA);
    }
    // Convert the TS types to SimpleTypes
    const simpleTypeA = isSimpleType(typeA) ? typeA : toSimpleType(typeA, checker);
    const simpleTypeB = isSimpleType(typeB) ? typeB : toSimpleType(typeB, checker);
    return isAssignableToSimpleType(simpleTypeA, simpleTypeB, options);
}

function isAssignableToValue(type, value, checker) {
    const typeB = convertValueToSimpleType(value, { visitValueSet: new Set(), widening: false });
    return isAssignableToType(type, typeB, checker, { strict: true });
}
function convertValueToSimpleType(value, { visitValueSet, widening }) {
    if (visitValueSet.has(value)) {
        return { kind: "ANY" };
    }
    if (value === undefined) {
        return {
            kind: "UNDEFINED"
        };
    }
    else if (value === null) {
        return {
            kind: "NULL"
        };
    }
    else if (typeof value === "string") {
        if (widening) {
            return { kind: "STRING" };
        }
        return {
            kind: "STRING_LITERAL",
            value
        };
    }
    else if (typeof value === "number") {
        if (widening) {
            return { kind: "NUMBER" };
        }
        return {
            kind: "NUMBER_LITERAL",
            value
        };
    }
    else if (typeof value === "boolean") {
        if (widening) {
            return { kind: "BOOLEAN" };
        }
        return {
            kind: "BOOLEAN_LITERAL",
            value
        };
    }
    else if (typeof value === "symbol") {
        if (widening) {
            return { kind: "ES_SYMBOL" };
        }
        return {
            kind: "ES_SYMBOL_UNIQUE",
            value: Math.random().toString()
        };
    }
    else if (Array.isArray(value)) {
        visitValueSet.add(value);
        const firstElement = value[0];
        if (firstElement != null) {
            return { kind: "ARRAY", type: convertValueToSimpleType(firstElement, { visitValueSet, widening: true }) };
        }
        return {
            kind: "ARRAY",
            type: { kind: "ANY" }
        };
    }
    else if (value instanceof Promise) {
        return {
            kind: "PROMISE",
            type: { kind: "ANY" }
        };
    }
    else if (value instanceof Date) {
        return {
            kind: "DATE"
        };
    }
    else if (typeof value === "object" && value != null) {
        visitValueSet.add(value);
        const members = Object.entries(value).map(([key, value]) => ({
            name: key,
            type: convertValueToSimpleType(value, { visitValueSet, widening })
        }));
        return {
            kind: "OBJECT",
            members
        };
    }
    return { kind: "ANY" };
}

function typeToString(type, checker) {
    if (isSimpleType(type)) {
        return simpleTypeToString(type);
    }
    // Use the typescript checker to return a string for a type
    return checker.typeToString(type);
}

const TYPE_REF_PREFIX = "__REF__";
function isTypeRef(value) {
    return typeof value === "string" && value.startsWith(TYPE_REF_PREFIX);
}
/**
 * Deserialize a serialized type into a SimpleType
 * @param serializedSimpleType
 */
function deserializeSimpleType(serializedSimpleType) {
    const { typeMap } = serializedSimpleType;
    // Make a map to lookup ids to get a shared SimpleType
    const deserializedTypeMap = new Map();
    // Add an empty object for each type in the reference map.
    // These object will be filled out afterwards.
    // This is useful because it allows us to easily shared references.
    for (const typeId of Object.keys(typeMap)) {
        deserializedTypeMap.set(Number(typeId), {});
    }
    // Loop through all types and deserialize them
    for (const [typeId, serializedType] of Object.entries(typeMap)) {
        const deserializedType = convertObject(serializedType, obj => {
            // Find and replace with a corresponding type in the typeMap when encountering a typeRef
            if (isTypeRef(obj)) {
                const typeId = Number(obj.replace(TYPE_REF_PREFIX, ""));
                return deserializedTypeMap.get(typeId);
            }
        });
        // Merge the content of "deserialized type" into the reference
        Object.assign(deserializedTypeMap.get(Number(typeId)), deserializedType);
    }
    // Return the main deserialized type
    return deserializedTypeMap.get(serializedSimpleType.type);
}
/**
 * Serialize a SimpleType
 * @param simpleType
 */
function serializeSimpleType(simpleType) {
    // Assign an "id" to each serialized type
    const typeMap = {};
    // Make it possible to lookup an id based on a SimpleType
    const typeMapReverse = new WeakMap();
    // Keep track of current id
    let id = 0;
    const mainTypeId = serializeTypeInternal(simpleType, {
        assignIdToType: type => {
            if (typeMapReverse.has(type)) {
                return typeMapReverse.get(type);
            }
            const assignedId = id++;
            typeMapReverse.set(type, assignedId);
            return assignedId;
        },
        getIdFromType: type => {
            return typeMapReverse.get(type);
        },
        emitType: (id, simpleTypeWithRef) => {
            typeMap[id] = simpleTypeWithRef;
            return id++;
        }
    });
    return {
        type: mainTypeId,
        typeMap
    };
}
function serializeTypeInternal(simpleType, { emitType, getIdFromType, assignIdToType }) {
    // If this SimpleType already has been assigned an ID, we don't need to serialize it again
    const existingId = getIdFromType(simpleType);
    if (existingId != null) {
        return existingId;
    }
    const id = assignIdToType(simpleType);
    const serializedType = convertObject({ ...simpleType }, obj => {
        // Replace with id whenever encountering a SimpleType
        if (isSimpleType(obj)) {
            // Convert the SimpleType recursively
            const id = serializeTypeInternal(obj, { emitType, getIdFromType, assignIdToType });
            return `${TYPE_REF_PREFIX}${id}`;
        }
    });
    // Emit this serialized type to the type map
    emitType(id, serializedType);
    return id;
}
function convertObject(input, convert) {
    let outer = true;
    function convertObjectInner(obj) {
        if (Array.isArray(obj)) {
            return obj.map(o => convertObjectInner(o));
        }
        if (!outer) {
            const convertedObj = convert(obj);
            if (convertedObj != null) {
                return convertedObj;
            }
        }
        outer = false;
        if (typeof obj === "object" && obj != null) {
            const newObj = {};
            for (const [key, value] of Object.entries(obj)) {
                newObj[key] = convertObjectInner(value);
            }
            return newObj;
        }
        return obj;
    }
    return convertObjectInner(input);
}

exports.LITERAL_TYPE_KINDS = LITERAL_TYPE_KINDS;
exports.PRIMITIVE_TYPE_KINDS = PRIMITIVE_TYPE_KINDS;
exports.SIMPLE_TYPE_KINDS = SIMPLE_TYPE_KINDS;
exports.deserializeSimpleType = deserializeSimpleType;
exports.getTypescriptModule = getTypescriptModule;
exports.isAssignableToPrimitiveType = isAssignableToPrimitiveType;
exports.isAssignableToSimpleTypeKind = isAssignableToSimpleTypeKind;
exports.isAssignableToType = isAssignableToType;
exports.isAssignableToValue = isAssignableToValue;
exports.isSimpleType = isSimpleType;
exports.isSimpleTypeLiteral = isSimpleTypeLiteral;
exports.isSimpleTypePrimitive = isSimpleTypePrimitive;
exports.serializeSimpleType = serializeSimpleType;
exports.setTypescriptModule = setTypescriptModule;
exports.toSimpleType = toSimpleType;
exports.typeToString = typeToString;
exports.validateType = validateType;