diff --git a/packages/app/src/module.ts b/packages/app/src/module.ts index da6ad89a1..530af5805 100644 --- a/packages/app/src/module.ts +++ b/packages/app/src/module.ts @@ -178,6 +178,9 @@ export class ConfigurationInvalidError extends CustomError { let moduleId = 0; +/** + * @reflection never + */ type PartialDeep = T extends string | number | bigint | boolean | null | undefined | symbol | Date ? T | undefined // Arrays, Sets and Maps and their readonly counterparts have their items made @@ -235,7 +238,7 @@ export type ListenerType = EventListener | ClassType; /** * The AppModule is the base class for all modules. * - * You can use `createModule` to create a new module class or extend from `AppModule` manually. + * You can use `createModule` to create a new module class or extend from `AppModule` manually. * * @example * ```typescript @@ -251,7 +254,6 @@ export type ListenerType = EventListener | ClassType; * this.name = 'myModule'; * } * } - * */ export class AppModule = any> extends InjectorModule> { public setupConfigs: ((module: AppModule, config: any) => void)[] = []; diff --git a/packages/injector/src/module.ts b/packages/injector/src/module.ts index 3a5c49641..f83b891b3 100644 --- a/packages/injector/src/module.ts +++ b/packages/injector/src/module.ts @@ -1,7 +1,19 @@ import { NormalizedProvider, ProviderWithScope, TagProvider, Token } from './provider.js'; import { arrayRemoveItem, ClassType, getClassName, isClass, isPlainObject, isPrototypeOfBase } from '@deepkit/core'; import { BuildContext, Injector, SetupProviderRegistry } from './injector.js'; -import { hasTypeInformation, isExtendable, isType, ReceiveType, reflect, ReflectionKind, reflectOrUndefined, resolveReceiveType, TypeClass, TypeObjectLiteral, visit } from '@deepkit/type'; +import { + hasTypeInformation, + isExtendable, + isType, + ReceiveType, + reflect, + ReflectionKind, + reflectOrUndefined, + resolveReceiveType, + TypeClass, + TypeObjectLiteral, + visit +} from '@deepkit/type'; export type ConfigureProvider = { [name in keyof T]: T[name] extends (...args: infer A) => any ? (...args: A) => ConfigureProvider : T[name] }; @@ -67,7 +79,9 @@ function lookupPreparedProviders(preparedProviders: PreparedProvider[], token: T if (token.kind === ReflectionKind.function && token.function && token.function === preparedProvider.token) last = preparedProvider; if (isType(preparedProvider.token) && isExtendable(preparedProvider.token, token)) last = preparedProvider; if (isClass(preparedProvider.token) && hasTypeInformation(preparedProvider.token) && isExtendable(reflect(preparedProvider.token), token)) last = preparedProvider; - } else if ('string' === typeof token || 'number' === typeof token || 'bigint' === typeof token || 'symbol' === typeof token && isType(preparedProvider.token)) { + } else if (('string' === typeof token || 'number' === typeof token || 'bigint' === typeof token || 'symbol' === typeof token) && isType(preparedProvider.token)) { + // note: important that we check for preparedProvider.token being isType, otherwise isExtendable uses typeInfer (which does not use cache). + // we have to call reflect(preparedProvider.token) otherwise, so that the cache is used. if (isExtendable(preparedProvider.token, { kind: ReflectionKind.literal, literal: token })) last = preparedProvider; } } diff --git a/packages/type/src/reflection/extends.ts b/packages/type/src/reflection/extends.ts index bd359f2ce..3a9ffe425 100644 --- a/packages/type/src/reflection/extends.ts +++ b/packages/type/src/reflection/extends.ts @@ -20,6 +20,7 @@ import { isTypeIncluded, ReflectionKind, resolveTypeMembers, + stringifyType, Type, TypeAny, TypeInfer, @@ -56,11 +57,25 @@ function hasStack(extendStack: StackEntry[], left: Type, right: Type): boolean { * * See https://www.typescriptlang.org/docs/handbook/type-compatibility.html#any-unknown-object-void-undefined-null-and-never-assignability * This algo follows strict mode. + * + * Warning: If you do not pass Type objects, typeInfer() is used which does not use cache (it is designed to be called withing type processor) */ export function isExtendable(leftValue: AssignableType, rightValue: AssignableType, extendStack: StackEntry[] = []): boolean { + const start = Date.now(); const right: Type = isType(rightValue) ? rightValue : typeInfer(rightValue); const left: Type = isType(leftValue) ? leftValue : typeInfer(leftValue); + const valid = _isExtendable(left, right, extendStack); + const took = Date.now() - start; + + if (took > 100) { + console.warn('isExtendable took very long', Date.now() - start, 'ms comparing', stringifyType(left), 'and', stringifyType(right)); + } + + return valid; +} + +export function _isExtendable(left: Type, right: Type, extendStack: StackEntry[] = []): boolean { if (hasStack(extendStack, left, right)) return true; try { @@ -79,12 +94,12 @@ export function isExtendable(leftValue: AssignableType, rightValue: AssignableTy } if (right.kind === ReflectionKind.any || right.kind === ReflectionKind.unknown) return true; - if (left.kind === ReflectionKind.promise && right.kind === ReflectionKind.promise) return isExtendable(left.type, right.type); + if (left.kind === ReflectionKind.promise && right.kind === ReflectionKind.promise) return _isExtendable(left.type, right.type); if (left.kind === ReflectionKind.promise && right.kind === ReflectionKind.object) return true; - if (left.kind === ReflectionKind.promise) return isExtendable(createPromiseObjectLiteral(left.type), right); - if (right.kind === ReflectionKind.promise) return isExtendable(left, createPromiseObjectLiteral(right.type)); + if (left.kind === ReflectionKind.promise) return _isExtendable(createPromiseObjectLiteral(left.type), right); + if (right.kind === ReflectionKind.promise) return _isExtendable(left, createPromiseObjectLiteral(right.type)); if (right.kind !== ReflectionKind.union) { if (left.kind === ReflectionKind.null) { @@ -157,7 +172,7 @@ export function isExtendable(leftValue: AssignableType, rightValue: AssignableTy if ('string' === typeof left.literal && right.kind === ReflectionKind.templateLiteral) { return extendTemplateLiteral(left, right); } - if (right.kind === ReflectionKind.union) return right.types.some(v => isExtendable(leftValue, v, extendStack)); + if (right.kind === ReflectionKind.union) return right.types.some(v => _isExtendable(left, v, extendStack)); return false; } @@ -183,7 +198,7 @@ export function isExtendable(leftValue: AssignableType, rightValue: AssignableTy if (right.kind === ReflectionKind.objectLiteral) { for (const type of resolveTypeMembers(right)) { if (type.kind === ReflectionKind.callSignature) { - if (isExtendable(left, type, extendStack)) return true; + if (_isExtendable(left, type, extendStack)) return true; } } @@ -191,7 +206,7 @@ export function isExtendable(leftValue: AssignableType, rightValue: AssignableTy } if (right.kind === ReflectionKind.function || right.kind === ReflectionKind.methodSignature || right.kind === ReflectionKind.method) { - const returnValid = isExtendable(left.return, right.return, extendStack); + const returnValid = _isExtendable(left.return, right.return, extendStack); if (!returnValid) return false; return isFunctionParameterExtendable(left, right, extendStack); @@ -201,12 +216,12 @@ export function isExtendable(leftValue: AssignableType, rightValue: AssignableTy } if ((left.kind === ReflectionKind.propertySignature || left.kind === ReflectionKind.property) && (right.kind === ReflectionKind.propertySignature || right.kind === ReflectionKind.property)) { - return isExtendable(left.type, right.type, extendStack); + return _isExtendable(left.type, right.type, extendStack); } if ((left.kind === ReflectionKind.class || left.kind === ReflectionKind.objectLiteral) && right.kind === ReflectionKind.function && right.name === 'new') { const leftConstructor = (left.types as Type[]).find(v => (v.kind === ReflectionKind.method && v.name === 'constructor') || (v.kind === ReflectionKind.methodSignature && v.name === 'new')); - const valid = isExtendable(right, leftConstructor || { kind: ReflectionKind.function, parameters: [], return: { kind: ReflectionKind.any } }, extendStack); + const valid = _isExtendable(right, leftConstructor || { kind: ReflectionKind.function, parameters: [], return: { kind: ReflectionKind.any } }, extendStack); return valid; } @@ -234,7 +249,7 @@ export function isExtendable(leftValue: AssignableType, rightValue: AssignableTy } } - return isExtendable(left, rightConstructor.return, extendStack); + return _isExtendable(left, rightConstructor.return, extendStack); } for (const member of right.types) { @@ -245,7 +260,7 @@ export function isExtendable(leftValue: AssignableType, rightValue: AssignableTy if (member.name === 'constructor') continue; const leftMember = (left.types as Type[]).find(v => isMember(v) && v.name === member.name); if (!leftMember) return false; - if (!isExtendable(leftMember, member, extendStack)) { + if (!_isExtendable(leftMember, member, extendStack)) { return false; } } @@ -263,7 +278,7 @@ export function isExtendable(leftValue: AssignableType, rightValue: AssignableTy if (left.kind === ReflectionKind.array && right.kind === ReflectionKind.array) { - return isExtendable(left.type, right.type, extendStack); + return _isExtendable(left.type, right.type, extendStack); } if (left.kind === ReflectionKind.tuple && right.kind === ReflectionKind.array) { @@ -273,7 +288,7 @@ export function isExtendable(leftValue: AssignableType, rightValue: AssignableTy const type = member.type.kind === ReflectionKind.rest ? member.type.type : member.type; if (isTypeIncluded(tupleUnion.types, type)) tupleUnion.types.push(type); } - return isExtendable(tupleUnion, right, extendStack); + return _isExtendable(tupleUnion, right, extendStack); } if (left.kind === ReflectionKind.array && right.kind === ReflectionKind.tuple) { @@ -282,7 +297,7 @@ export function isExtendable(leftValue: AssignableType, rightValue: AssignableTy for (const member of right.types) { let type = member.type.kind === ReflectionKind.rest ? member.type.type : member.type; if (member.optional) type = flatten({ kind: ReflectionKind.union, types: [{ kind: ReflectionKind.undefined }, type] }); - if (!isExtendable(left.type, type, extendStack)) return false; + if (!_isExtendable(left.type, type, extendStack)) return false; } return true; } @@ -292,7 +307,7 @@ export function isExtendable(leftValue: AssignableType, rightValue: AssignableTy const rightType = indexAccess(right, { kind: ReflectionKind.literal, literal: i }); const leftType = indexAccess(left, { kind: ReflectionKind.literal, literal: i }); if (rightType.kind === ReflectionKind.infer || leftType.kind === ReflectionKind.infer) continue; - const valid = isExtendable(leftType, rightType, extendStack); + const valid = _isExtendable(leftType, rightType, extendStack); if (!valid) return false; } inferFromTuple(left, right); @@ -300,9 +315,9 @@ export function isExtendable(leftValue: AssignableType, rightValue: AssignableTy return true; } - if (left && left.kind === ReflectionKind.union) return left.types.every(v => isExtendable(v, rightValue, extendStack)); + if (left && left.kind === ReflectionKind.union) return left.types.every(v => _isExtendable(v, right, extendStack)); - if (right.kind === ReflectionKind.union) return right.types.some(v => isExtendable(leftValue, v, extendStack)); + if (right.kind === ReflectionKind.union) return right.types.some(v => _isExtendable(left, v, extendStack)); return false; } finally { @@ -383,7 +398,7 @@ function isFunctionParameterExtendable(left: { parameters: TypeParameter[] }, ri //we have to change the position here since its type assignability is inversed to tuples rules // true for tuple: [a: string] extends [a: string, b: string] // false for function: (a: string) extends (a: string, b: string) - const valid = isExtendable(rightTuple, leftTuple, extendStack); + const valid = _isExtendable(rightTuple, leftTuple, extendStack); if (valid) { inferFromTuple(leftTuple, rightTuple); } diff --git a/packages/type/src/reflection/processor.ts b/packages/type/src/reflection/processor.ts index 29a0c9d57..8a68ee9ee 100644 --- a/packages/type/src/reflection/processor.ts +++ b/packages/type/src/reflection/processor.ts @@ -28,6 +28,7 @@ import { narrowOriginalLiteral, ReflectionKind, ReflectionVisibility, + stringifyType, Type, TypeBaseMember, TypeCallSignature, @@ -333,6 +334,17 @@ export class Processor { }; reflect(object: ClassType | Function | Packed | any, inputs: RuntimeStackEntry[] = [], options: ReflectOptions = {}): Type { + const start = Date.now(); + const result = this._reflect(object, inputs, options); + + const took = Date.now() - start; + if (took > 100) { + console.warn(`Type computation took very long ${took}ms for ${stringifyType(result)}`); + } + return result; + } + + _reflect(object: ClassType | Function | Packed | any, inputs: RuntimeStackEntry[] = [], options: ReflectOptions = {}): Type { const packed: Packed | undefined = isPack(object) ? object : object.__type; if (!packed) { if (isFunction(object) && object.length === 0) { @@ -1690,6 +1702,7 @@ export function typeInfer(value: any): Type { if (isArray(value.__type)) { //with emitted types: function or class //don't use resolveRuntimeType since we don't allow cache here + // console.log('typeInfer of', value.name); return Processor.get().reflect(value) as Type; }