From c53ff42dc1bfacb7d770d5bf5d0a26d1ba2d5143 Mon Sep 17 00:00:00 2001 From: Trevor Dixon Date: Thu, 30 Jun 2022 15:26:49 +0200 Subject: [PATCH] Include type predicate information for functions and use that information when comparing. --- src/is-assignable/is-assignable-to-simple-type.ts | 11 +++++++++++ src/simple-type.ts | 8 ++++++++ src/transform/to-simple-type.ts | 10 +++++++++- test/type-combinations.spec.ts | 9 ++++++++- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/is-assignable/is-assignable-to-simple-type.ts b/src/is-assignable/is-assignable-to-simple-type.ts index 61f9bfb..717243c 100644 --- a/src/is-assignable/is-assignable-to-simple-type.ts +++ b/src/is-assignable/is-assignable-to-simple-type.ts @@ -567,6 +567,17 @@ function isAssignableToSimpleTypeInternal(typeA: SimpleType, typeB: SimpleType, } } + // If typeA has a type predicate, make sure typeB's matches + if (typeA.typePredicate) { + if (!typeB.typePredicate) return false; + if (!isAssignableToSimpleTypeCached(typeA.typePredicate.type, typeB.typePredicate.type, options)) { + return false; + } + if (typeA.typePredicate.parameterIndex !== typeB.typePredicate.parameterIndex) { + return false; + } + } + // Test "this" types const typeAThisParam = typeA.parameters.find(arg => arg.name === "this"); const typeBThisParam = typeB.parameters.find(arg => arg.name === "this"); diff --git a/src/simple-type.ts b/src/simple-type.ts index 435aeb9..dc07aeb 100644 --- a/src/simple-type.ts +++ b/src/simple-type.ts @@ -206,11 +206,18 @@ export interface SimpleTypeFunctionParameter { readonly initializer: boolean; } +export interface SimpleTypeTypePredicate { + readonly paramaterName: string; + readonly paramaterIndex: number; + readonly type: SimpleType; +} + export interface SimpleTypeFunction extends SimpleTypeBase { readonly kind: "FUNCTION"; readonly parameters?: SimpleTypeFunctionParameter[]; readonly typeParameters?: SimpleTypeGenericParameter[]; readonly returnType?: SimpleType; + readonly typePredicate?: SimpleTypeTypePredicate; } export interface SimpleTypeMethod extends SimpleTypeBase { @@ -218,6 +225,7 @@ export interface SimpleTypeMethod extends SimpleTypeBase { readonly parameters: SimpleTypeFunctionParameter[]; readonly typeParameters?: SimpleTypeGenericParameter[]; readonly returnType: SimpleType; + readonly typePredicate?: SimpleTypeTypePredicate; } // ############################## diff --git a/src/transform/to-simple-type.ts b/src/transform/to-simple-type.ts index 7a7d02e..e5f42bd 100644 --- a/src/transform/to-simple-type.ts +++ b/src/transform/to-simple-type.ts @@ -614,7 +614,15 @@ function getSimpleFunctionFromSignatureDeclaration( const typeParameters = getTypeParameters(signatureDeclaration, options); - return { name, kind, returnType, parameters, typeParameters } as SimpleTypeFunction | SimpleTypeMethod; + const tsTypePredicate = checker.getTypePredicateOfSignature(signature); + const typePredicateType = tsTypePredicate && toSimpleTypeCached(tsTypePredicate.type, options); + const typePredicate = typePredicateType && { + parameterName: tsTypePredicate.parameterName, + parameterIndex: tsTypePredicate.parameterIndex, + type: typePredicateType + }; + + return { name, kind, returnType, parameters, typeParameters, typePredicate } as SimpleTypeFunction | SimpleTypeMethod; } function getRealSymbolName(symbol: ESSymbol, ts: typeof tsModule): string | undefined { diff --git a/test/type-combinations.spec.ts b/test/type-combinations.spec.ts index bc133aa..b712487 100644 --- a/test/type-combinations.spec.ts +++ b/test/type-combinations.spec.ts @@ -150,7 +150,14 @@ export const FUNCTION_TYPES: TypescriptType[] = [ `((a: number, b?: string) => string)`, `((a: number, b: string) => string)`, `(): string | null`, - `(): string | boolean | null | {a: string}` + `(): string | boolean | null | {a: string}`, + `(a: unknown) => boolean`, + `(a: string|number) => a is string`, + `(b: string|number) => b is number`, + `(a: unknown) => a is string|number` + // TODO: the following line exercises type predicate parameterIndex compatability, + // but fails the test for assignability with one of FUNCTION_REST_TYPES. + // `(a: string|number, b: string|number) => b is number` ]; export const FUNCTION_THIS_TYPES: TypescriptType[] = [`(this: string, a: number) => any`, `(this: number, a: number) => any`, `(this: number) => any`];