From b19c64d4c956981da1044efc392f12a2648ee517 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 11 Oct 2023 08:53:29 -0700 Subject: [PATCH 1/6] Optimize getNarrowedTypeWorker for literals --- src/compiler/checker.ts | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9ed6f52c38c71..8f5faaa6ae25f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16782,7 +16782,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // a subtype of just `A` or just `B`. When we encounter such a type parameter, we therefore check if the // type parameter is a subtype of a union of all the other types. if (source.flags & TypeFlags.TypeParameter && getBaseConstraintOrType(source).flags & TypeFlags.Union) { - if (isTypeRelatedTo(source, getUnionType(map(types, t => t === source ? neverType : t)), strictSubtypeRelation)) { + if (isTypeStrictSubtypeOf(source, getUnionType(map(types, t => t === source ? neverType : t)))) { orderedRemoveItemAt(types, i); } continue; @@ -16816,7 +16816,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } if ( - isTypeRelatedTo(source, target, strictSubtypeRelation) && ( + isTypeStrictSubtypeOf(source, target) && ( !(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) || !(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) || isTypeDerivedFrom(source, target) @@ -28042,7 +28042,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // the constituent based on its type facts. We use the strict subtype relation because it treats `object` // as a subtype of `{}`, and we need the type facts check because function types are subtypes of `object`, // but are classified as "function" according to `typeof`. - isTypeRelatedTo(t, impliedType, strictSubtypeRelation) ? hasTypeFacts(t, facts) ? t : neverType : + isTypeStrictSubtypeOf(t, impliedType) ? hasTypeFacts(t, facts) ? t : neverType : // We next check if the consituent is a supertype of the implied type. If so, we substitute the implied // type. This handles top types like `unknown` and `{}`, and supertypes like `{ toString(): string }`. isTypeSubtypeOf(impliedType, t) ? impliedType : @@ -28240,7 +28240,26 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { matching || type, checkDerived ? t => isTypeDerivedFrom(t, c) ? t : isTypeDerivedFrom(c, t) ? c : neverType : - t => isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : neverType, + t => { + // isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : neverType; + + if (t.flags & TypeFlags.Literal && c.flags & TypeFlags.Literal) { + if (t.flags & TypeFlags.BooleanLiteral && c.flags & TypeFlags.BooleanLiteral) { + return t === c ? t : neverType; + } + return (t as LiteralType).value === (c as LiteralType).value ? t : neverType; + } + + if (isTypeStrictSubtypeOf(t, c)) return t; + if (isTypeStrictSubtypeOf(c, t)) return c; + + if (t.flags & TypeFlags.Unit && c.flags & TypeFlags.Unit) return neverType; + + if (isTypeSubtypeOf(t, c)) return t; + if (isTypeSubtypeOf(c, t)) return c; + + return neverType; + }, ); // If no constituents are directly related, create intersections for any generic constituents that // are related by constraint. @@ -28257,6 +28276,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getIntersectionType([type, candidate]); } + // function strictSubtypeImpliesSubtype(source: Type, target: Type) { + // const s = source.flags; + // const t = target.flags; + // if ( + // !(t & TypeFlags.Unknown && !(s & TypeFlags.Any)) + // || !(s & TypeFlags.Object && t & TypeFlags.NonPrimitive && !(isEmptyAnonymousObjectType(source) && !(getObjectFlags(source) & ObjectFlags.FreshLiteral))) + // ) return false; + + // return true; + // } + function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { if (hasMatchingArgument(callExpression, reference)) { const signature = assumeTrue || !isCallChain(callExpression) ? getEffectsSignature(callExpression) : undefined; From 54d0bc780a35ee61c874e07625ac30384c396c06 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 11 Oct 2023 09:48:40 -0700 Subject: [PATCH 2/6] revert unrelated --- src/compiler/checker.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8f5faaa6ae25f..38042f097069b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16782,7 +16782,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // a subtype of just `A` or just `B`. When we encounter such a type parameter, we therefore check if the // type parameter is a subtype of a union of all the other types. if (source.flags & TypeFlags.TypeParameter && getBaseConstraintOrType(source).flags & TypeFlags.Union) { - if (isTypeStrictSubtypeOf(source, getUnionType(map(types, t => t === source ? neverType : t)))) { + if (isTypeRelatedTo(source, getUnionType(map(types, t => t === source ? neverType : t)), strictSubtypeRelation)) { orderedRemoveItemAt(types, i); } continue; @@ -16816,7 +16816,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } if ( - isTypeStrictSubtypeOf(source, target) && ( + isTypeRelatedTo(source, target, strictSubtypeRelation) && ( !(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) || !(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) || isTypeDerivedFrom(source, target) @@ -28042,7 +28042,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // the constituent based on its type facts. We use the strict subtype relation because it treats `object` // as a subtype of `{}`, and we need the type facts check because function types are subtypes of `object`, // but are classified as "function" according to `typeof`. - isTypeStrictSubtypeOf(t, impliedType) ? hasTypeFacts(t, facts) ? t : neverType : + isTypeRelatedTo(t, impliedType, strictSubtypeRelation) ? hasTypeFacts(t, facts) ? t : neverType : // We next check if the consituent is a supertype of the implied type. If so, we substitute the implied // type. This handles top types like `unknown` and `{}`, and supertypes like `{ toString(): string }`. isTypeSubtypeOf(impliedType, t) ? impliedType : @@ -28276,17 +28276,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getIntersectionType([type, candidate]); } - // function strictSubtypeImpliesSubtype(source: Type, target: Type) { - // const s = source.flags; - // const t = target.flags; - // if ( - // !(t & TypeFlags.Unknown && !(s & TypeFlags.Any)) - // || !(s & TypeFlags.Object && t & TypeFlags.NonPrimitive && !(isEmptyAnonymousObjectType(source) && !(getObjectFlags(source) & ObjectFlags.FreshLiteral))) - // ) return false; - - // return true; - // } - function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { if (hasMatchingArgument(callExpression, reference)) { const signature = assumeTrue || !isCallChain(callExpression) ? getEffectsSignature(callExpression) : undefined; From 629de0d7eaea147eac27c07d72c5a0aa3e7a63bf Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 11 Oct 2023 19:37:15 -0700 Subject: [PATCH 3/6] Fixup --- src/compiler/checker.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 38042f097069b..2a2c1b4d32ca0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -28241,10 +28241,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkDerived ? t => isTypeDerivedFrom(t, c) ? t : isTypeDerivedFrom(c, t) ? c : neverType : t => { - // isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : neverType; + // WAS: isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : neverType; - if (t.flags & TypeFlags.Literal && c.flags & TypeFlags.Literal) { - if (t.flags & TypeFlags.BooleanLiteral && c.flags & TypeFlags.BooleanLiteral) { + // Avoid going through relations for comparison between literals of the same types; + // if they're not equal then they're not related and we can skip doing a bunch of work. + if (t.flags & TypeFlags.Literal && (t.flags & TypeFlags.Literal) === (c.flags & TypeFlags.Literal)) { + if (t.flags & TypeFlags.BooleanLiteral) { return t === c ? t : neverType; } return (t as LiteralType).value === (c as LiteralType).value ? t : neverType; @@ -28253,6 +28255,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isTypeStrictSubtypeOf(t, c)) return t; if (isTypeStrictSubtypeOf(c, t)) return c; + // Strict subtyping is equivalent to subtyping for these types (and likely others). if (t.flags & TypeFlags.Unit && c.flags & TypeFlags.Unit) return neverType; if (isTypeSubtypeOf(t, c)) return t; From 53f60d1914c202f34d6e81bff8a80de4bf513efb Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 12 Oct 2023 13:59:25 -0700 Subject: [PATCH 4/6] Fix bools, leave todo --- src/compiler/checker.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2a2c1b4d32ca0..45eacd4dca33d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -28247,7 +28247,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // if they're not equal then they're not related and we can skip doing a bunch of work. if (t.flags & TypeFlags.Literal && (t.flags & TypeFlags.Literal) === (c.flags & TypeFlags.Literal)) { if (t.flags & TypeFlags.BooleanLiteral) { - return t === c ? t : neverType; + const tIsTrue = t === trueType || t === regularTrueType; + const cIsTrue = c === trueType || c === regularTrueType; + return tIsTrue === cIsTrue ? t : neverType; } return (t as LiteralType).value === (c as LiteralType).value ? t : neverType; } @@ -28256,6 +28258,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isTypeStrictSubtypeOf(c, t)) return c; // Strict subtyping is equivalent to subtyping for these types (and likely others). + // TODO(jakebailey): what other things can we quickly check here? if (t.flags & TypeFlags.Unit && c.flags & TypeFlags.Unit) return neverType; if (isTypeSubtypeOf(t, c)) return t; From e68e177567be96e79b42ee3aef66eaff561bd9f4 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:17:33 -0700 Subject: [PATCH 5/6] Try out new thing for all relations --- src/compiler/checker.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 45eacd4dca33d..c464034f8f2ab 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20719,6 +20719,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (source === target) { return true; } + // TODO(jakebailey): just trying this out; passes all tests and is interesting + if ( + source.flags & TypeFlags.Literal + && (source.flags & TypeFlags.Literal) === (target.flags & TypeFlags.Literal) + && !(source.flags & TypeFlags.EnumLiteral && target.flags & TypeFlags.EnumLiteral) + ) { + if (source.flags & TypeFlags.BooleanLiteral) { + return source === target; + } + return (source as LiteralType).value === (target as LiteralType).value; + } + if (relation !== identityRelation) { if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || isSimpleTypeRelatedTo(source, target, relation)) { return true; @@ -28245,7 +28257,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Avoid going through relations for comparison between literals of the same types; // if they're not equal then they're not related and we can skip doing a bunch of work. - if (t.flags & TypeFlags.Literal && (t.flags & TypeFlags.Literal) === (c.flags & TypeFlags.Literal)) { + if ( + t.flags & TypeFlags.Literal + && (t.flags & TypeFlags.Literal) === (c.flags & TypeFlags.Literal) + && !(t.flags & TypeFlags.EnumLiteral && c.flags & TypeFlags.EnumLiteral) + ) { if (t.flags & TypeFlags.BooleanLiteral) { const tIsTrue = t === trueType || t === regularTrueType; const cIsTrue = c === trueType || c === regularTrueType; From 247724d51169e02e5f9f2c37bd5eb8d6dff4d151 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:58:16 -0700 Subject: [PATCH 6/6] Revert change --- src/compiler/checker.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c464034f8f2ab..08a461ba4bac8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20719,17 +20719,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (source === target) { return true; } - // TODO(jakebailey): just trying this out; passes all tests and is interesting - if ( - source.flags & TypeFlags.Literal - && (source.flags & TypeFlags.Literal) === (target.flags & TypeFlags.Literal) - && !(source.flags & TypeFlags.EnumLiteral && target.flags & TypeFlags.EnumLiteral) - ) { - if (source.flags & TypeFlags.BooleanLiteral) { - return source === target; - } - return (source as LiteralType).value === (target as LiteralType).value; - } if (relation !== identityRelation) { if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || isSimpleTypeRelatedTo(source, target, relation)) {