diff --git a/packages/type-compiler/src/compiler.ts b/packages/type-compiler/src/compiler.ts
index 34c42f713..eaa6e505a 100644
--- a/packages/type-compiler/src/compiler.ts
+++ b/packages/type-compiler/src/compiler.ts
@@ -2096,9 +2096,10 @@ export class ReflectionTransformer implements CustomTransformer {
program.pushOp(ReflectionOp.array);
return;
} else if (name === 'Function') {
- program.pushOp(ReflectionOp.frame);
- program.pushOp(ReflectionOp.any);
- program.pushOp(ReflectionOp.function, program.pushStack(''));
+ program.pushFrame();
+ const index = program.pushStack(this.f.createArrowFunction(undefined, undefined, [], undefined, undefined, this.f.createIdentifier('Function')));
+ program.pushOp(ReflectionOp.functionReference, index);
+ program.popFrameImplicit();
return;
} else if (name === 'Set') {
if (type.typeArguments && type.typeArguments[0]) {
diff --git a/packages/type-compiler/tests/transpile.spec.ts b/packages/type-compiler/tests/transpile.spec.ts
index 817a228eb..44501722c 100644
--- a/packages/type-compiler/tests/transpile.spec.ts
+++ b/packages/type-compiler/tests/transpile.spec.ts
@@ -462,6 +462,15 @@ test('keep "use x" at top', () => {
expect(res.app.startsWith('"use client";')).toBe(true);
});
+test('Function', () => {
+ const res = transpile({
+ 'app': `
+ type a = Function;
+ `
+ });
+ expect(res.app).toContain(`[() => Function, `);
+});
+
test('inline type definitions should compile', () => {
const res = transpile({
'app': `
diff --git a/packages/type/src/reflection/extends.ts b/packages/type/src/reflection/extends.ts
index 18264a3a1..b0818cfd5 100644
--- a/packages/type/src/reflection/extends.ts
+++ b/packages/type/src/reflection/extends.ts
@@ -11,7 +11,8 @@
import {
addType,
emptyObject,
- flatten, getTypeJitContainer,
+ flatten,
+ getTypeJitContainer,
indexAccess,
isMember,
isOptional,
@@ -23,17 +24,20 @@ import {
stringifyType,
Type,
TypeAny,
+ TypeCallSignature,
+ TypeFunction,
TypeInfer,
TypeLiteral,
TypeMethod,
TypeMethodSignature,
TypeNumber,
TypeObjectLiteral,
- TypeParameter, TypePromise,
+ TypeParameter,
+ TypePromise,
TypeString,
TypeTemplateLiteral,
TypeTuple,
- TypeUnion
+ TypeUnion,
} from './type.js';
import { isPrototypeOfBase } from '@deepkit/core';
import { typeInfer } from './processor.js';
@@ -75,6 +79,25 @@ export function isExtendable(leftValue: AssignableType, rightValue: AssignableTy
return valid;
}
+function isFunctionLike(type: Type) {
+ return type.kind === ReflectionKind.function || type.kind === ReflectionKind.method || type.kind === ReflectionKind.callSignature
+ || type.kind === ReflectionKind.methodSignature || type.kind === ReflectionKind.objectLiteral
+ || ((type.kind === ReflectionKind.property || type.kind === ReflectionKind.propertySignature) && type.type.kind === ReflectionKind.function);
+}
+
+function getFunctionLikeType(type: Type): TypeMethod | TypeMethodSignature | TypeFunction | TypeCallSignature | undefined {
+ if (type.kind === ReflectionKind.function || type.kind === ReflectionKind.method || type.kind === ReflectionKind.methodSignature) return type;
+ if (type.kind === ReflectionKind.objectLiteral) {
+ for (const member of resolveTypeMembers(type)) {
+ if (member.kind === ReflectionKind.callSignature) return member;
+ }
+ }
+ if (type.kind === ReflectionKind.property || type.kind === ReflectionKind.propertySignature) {
+ return type.type.kind === ReflectionKind.function ? getFunctionLikeType(type.type) : undefined;
+ }
+ return;
+}
+
export function _isExtendable(left: Type, right: Type, extendStack: StackEntry[] = []): boolean {
if (hasStack(extendStack, left, right)) return true;
@@ -194,29 +217,18 @@ export function _isExtendable(left: Type, right: Type, extendStack: StackEntry[]
}
}
- if (left.kind === ReflectionKind.function && right.kind === ReflectionKind.function && left.function && left.function === right.function) return true;
+ if (isFunctionLike(left) && isFunctionLike(right)) {
+ const leftType = getFunctionLikeType(left);
+ const rightType = getFunctionLikeType(right);
+ if (leftType && rightType) {
+ if (rightType.kind === ReflectionKind.function && rightType.function === Function) return true;
+ if (leftType.kind === ReflectionKind.function && rightType.kind === ReflectionKind.function && leftType.function && leftType.function === rightType.function) return true;
- if ((left.kind === ReflectionKind.function || left.kind === ReflectionKind.method || left.kind === ReflectionKind.methodSignature) &&
- (right.kind === ReflectionKind.function || right.kind === ReflectionKind.method || right.kind === ReflectionKind.methodSignature || right.kind === ReflectionKind.objectLiteral)
- ) {
- if (right.kind === ReflectionKind.objectLiteral) {
- for (const type of resolveTypeMembers(right)) {
- if (type.kind === ReflectionKind.callSignature) {
- if (_isExtendable(left, type, extendStack)) return true;
- }
- }
-
- return false;
- }
-
- if (right.kind === ReflectionKind.function || right.kind === ReflectionKind.methodSignature || right.kind === ReflectionKind.method) {
- const returnValid = _isExtendable(left.return, right.return, extendStack);
+ const returnValid = _isExtendable(leftType.return, rightType.return, extendStack);
if (!returnValid) return false;
- return isFunctionParameterExtendable(left, right, extendStack);
+ return isFunctionParameterExtendable(leftType, rightType, extendStack);
}
-
- return false;
}
if ((left.kind === ReflectionKind.propertySignature || left.kind === ReflectionKind.property) && (right.kind === ReflectionKind.propertySignature || right.kind === ReflectionKind.property)) {
diff --git a/packages/type/src/reflection/processor.ts b/packages/type/src/reflection/processor.ts
index 908610450..9bb1bef53 100644
--- a/packages/type/src/reflection/processor.ts
+++ b/packages/type/src/reflection/processor.ts
@@ -1865,6 +1865,10 @@ function applyPropertyDecorator(type: Type, data: TData) {
}
}
+function collapseFunctionToMethod(member: TypePropertySignature | TypeMethodSignature): member is TypePropertySignature & { type: TypeMethodSignature } {
+ return member.kind === ReflectionKind.propertySignature && member.type.kind === ReflectionKind.function && member.type.function !== Function;
+}
+
function pushObjectLiteralTypes(
type: TypeObjectLiteral,
types: (TypeIndexSignature | TypePropertySignature | TypeMethodSignature | TypeObjectLiteral | TypeCallSignature)[],
@@ -1904,7 +1908,7 @@ function pushObjectLiteralTypes(
//note: is it possible to overwrite an index signature?
type.types.push(member);
} else if (member.kind === ReflectionKind.propertySignature || member.kind === ReflectionKind.methodSignature) {
- const toAdd = member.kind === ReflectionKind.propertySignature && member.type.kind === ReflectionKind.function ? {
+ const toAdd = collapseFunctionToMethod(member) ? {
kind: ReflectionKind.methodSignature,
name: member.name,
optional: member.optional,
diff --git a/packages/type/tests/integration4.spec.ts b/packages/type/tests/integration4.spec.ts
index f8eb70413..099a3b001 100644
--- a/packages/type/tests/integration4.spec.ts
+++ b/packages/type/tests/integration4.spec.ts
@@ -12,6 +12,7 @@ import { expect, test } from '@jest/globals';
import { assertType, AutoIncrement, Group, groupAnnotation, PrimaryKey, ReflectionKind } from '../src/reflection/type.js';
import { typeOf } from '../src/reflection/reflection.js';
import { cast } from '../src/serializer-facade.js';
+import { equalType } from './utils.js';
test('group from enum', () => {
enum Groups {
@@ -143,3 +144,16 @@ test('union loosely', () => {
expect(cast({ id: 2 })).toEqual({ id: 2 });
expect(cast({ id: '3' })).toEqual({ id: 3 });
});
+
+test('function conditions', () => {
+ type t1 = (() => any) extends Function ? true : false;
+ type t2 = ((a: string) => void) extends Function ? true : false;
+ type t3 = { a(a: string): void } extends { a: Function } ? true : false;
+ type t4 = { a(a: string): void } extends { a(): void } ? true : false;
+ console.log(typeOf());
+ console.log(typeOf<{ a: Function } >());
+ equalType();
+ equalType();
+ equalType();
+ equalType();
+});
diff --git a/packages/type/tests/processor.spec.ts b/packages/type/tests/processor.spec.ts
index 8c5eb3add..c8043020d 100644
--- a/packages/type/tests/processor.spec.ts
+++ b/packages/type/tests/processor.spec.ts
@@ -103,6 +103,11 @@ test('extends fn', () => {
{ kind: ReflectionKind.function, return: { kind: ReflectionKind.literal, literal: true }, parameters: [] },
{ kind: ReflectionKind.function, return: { kind: ReflectionKind.boolean }, parameters: [] }
)).toBe(true);
+
+ expect(isExtendable(
+ { kind: ReflectionKind.function, return: { kind: ReflectionKind.literal, literal: true }, parameters: [] },
+ { kind: ReflectionKind.function, function: Function, return: { kind: ReflectionKind.unknown }, parameters: [] }
+ )).toBe(true);
});
test('arg', () => {
diff --git a/packages/type/tests/standard-functions.spec.ts b/packages/type/tests/standard-functions.spec.ts
index facfc6d86..5a38ffdf4 100644
--- a/packages/type/tests/standard-functions.spec.ts
+++ b/packages/type/tests/standard-functions.spec.ts
@@ -8,19 +8,8 @@
* You should have received a copy of the MIT License along with this program.
*/
-import { test, expect } from '@jest/globals';
-import { ReceiveType, removeTypeName, resolveReceiveType, typeOf } from '../src/reflection/reflection.js';
-import { expectEqualType } from './utils.js';
-import { stringifyResolvedType, stringifyType } from '../src/reflection/type.js';
-import { createPromiseObjectLiteral } from '../src/reflection/extends.js';
-import { serializeType } from '../src/type-serialization.js';
-
-function equalType(a?: ReceiveType, b?: ReceiveType) {
- const aType = removeTypeName(resolveReceiveType(a));
- const bType = removeTypeName(resolveReceiveType(b));
- expect(stringifyResolvedType(aType)).toBe(stringifyResolvedType(bType));
- expectEqualType(aType, bType as any);
-}
+import { test } from '@jest/globals';
+import { equalType } from './utils.js';
test('Exclude', () => {
equalType, 'a' | 'c'>();
diff --git a/packages/type/tests/typeguard.spec.ts b/packages/type/tests/typeguard.spec.ts
index 83fb0541c..130edcedf 100644
--- a/packages/type/tests/typeguard.spec.ts
+++ b/packages/type/tests/typeguard.spec.ts
@@ -229,6 +229,15 @@ test('object literal', () => {
expect(is<{ a: string, b: number }>({ a: 'a', b: 'asd' })).toEqual(false);
});
+test('function', () => {
+ expect(is<(a: string) => void>((a: string): void => undefined)).toEqual(true);
+ expect(is<(a: string) => void>((a: string): string => 'asd')).toEqual(false);
+ expect(is<(a: string) => void>((a: string): number => 2)).toEqual(false);
+ expect(is<(a: string) => void>((a: string): any => 2)).toEqual(true);
+ expect(is<(a: string) => void>((a: any): number => 2)).toEqual(false);
+ expect(is((a: any): number => 2)).toEqual(true);
+});
+
test('class', () => {
class A {
a!: string;
diff --git a/packages/type/tests/utils.ts b/packages/type/tests/utils.ts
index c15969cb0..584796f87 100644
--- a/packages/type/tests/utils.ts
+++ b/packages/type/tests/utils.ts
@@ -1,5 +1,12 @@
-import { getTypeJitContainer, ParentLessType, ReflectionKind, Type } from '../src/reflection/type.js';
+import {
+ getTypeJitContainer,
+ ParentLessType,
+ ReflectionKind,
+ stringifyResolvedType,
+ Type,
+} from '../src/reflection/type.js';
import { Processor, RuntimeStackEntry } from '../src/reflection/processor.js';
+import { ReceiveType, removeTypeName, resolveReceiveType } from '../src/reflection/reflection.js';
import { expect } from '@jest/globals';
import { ReflectionOp } from '@deepkit/type-spec';
import { isArray, isObject } from '@deepkit/core';
@@ -18,6 +25,7 @@ function reflectionName(kind: ReflectionKind): string {
}
let visitStackId: number = 0;
+
export function visitWithParent(type: Type, visitor: (type: Type, path: string, parent?: Type) => false | void, onCircular?: () => void, stack: number = visitStackId++, path: string = '', parent?: Type): void {
const jit = getTypeJitContainer(type);
if (jit.visitId === visitStackId) {
@@ -73,7 +81,7 @@ export function visitWithParent(type: Type, visitor: (type: Type, path: string,
export function expectType(
pack: ReflectionOp[] | { ops: ReflectionOp[], stack: RuntimeStackEntry[], inputs?: RuntimeStackEntry[] },
- expectObject: E | number | string | boolean
+ expectObject: E | number | string | boolean,
): void {
const type = Processor.get().run(isArray(pack) ? pack : pack.ops, isArray(pack) ? [] : pack.stack, isArray(pack) ? [] : pack.inputs);
@@ -88,10 +96,22 @@ export function expectType(
}
}
+export function equalType(a?: ReceiveType, b?: ReceiveType) {
+ const aType = removeTypeName(resolveReceiveType(a));
+ const bType = removeTypeName(resolveReceiveType(b));
+ expect(stringifyResolvedType(aType)).toBe(stringifyResolvedType(bType));
+ expectEqualType(aType, bType as any);
+}
+
/**
* Types can not be compared via toEqual since they contain circular references (.parent) and other stuff can not be easily assigned.
*/
-export function expectEqualType(actual: any, expected: any, options: { noTypeNames?: true, noOrigin?: true, excludes?: string[], stack?: any[] } = {}, path: string = ''): void {
+export function expectEqualType(actual: any, expected: any, options: {
+ noTypeNames?: true,
+ noOrigin?: true,
+ excludes?: string[],
+ stack?: any[]
+} = {}, path: string = ''): void {
if (!options.stack) options.stack = [];
if (options.stack.includes(expected)) {