Skip to content

[experiment] Sort intersections #52891

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 35 additions & 37 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17132,7 +17132,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function insertType(types: Type[], type: Type): boolean {
const index = binarySearch(types, type, getTypeId, compareValues);
// TODO(jakebailey): we already have insertSorted and should use it here (adding the perf optimization?)
const len = types.length;
// Perf optimization (measure?); check the last element first as we often insert in order.
const index = len && type.id > types[len - 1].id ? ~len : binarySearch(types, type, getTypeId, compareValues);
if (index < 0) {
types.splice(~index, 0, type);
return true;
Expand All @@ -17152,11 +17155,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) includes |= TypeFlags.IncludesNonWideningType;
}
else {
const len = typeSet.length;
const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues);
if (index < 0) {
typeSet.splice(~index, 0, type);
}
insertType(typeSet, type);
}
}
return includes;
Expand Down Expand Up @@ -17527,15 +17526,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return links.resolvedType;
}

function addTypeToIntersection(typeSet: Map<string, Type>, includes: TypeFlags, type: Type) {
function addTypeToIntersection(typeSet: Type[], includes: TypeFlags, type: Type) {
const flags = type.flags;
if (flags & TypeFlags.Intersection) {
return addTypesToIntersection(typeSet, includes, (type as IntersectionType).types);
}
if (isEmptyAnonymousObjectType(type)) {
if (!(includes & TypeFlags.IncludesEmptyObject)) {
includes |= TypeFlags.IncludesEmptyObject;
typeSet.set(type.id.toString(), type);
insertType(typeSet, type);
}
}
else {
Expand All @@ -17547,13 +17546,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
includes |= TypeFlags.IncludesMissingType;
type = undefinedType;
}
if (!typeSet.has(type.id.toString())) {
const inserted = insertType(typeSet, type);
if (inserted) {
if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) {
// We have seen two distinct unit types which means we should reduce to an
// empty intersection. Adding TypeFlags.NonPrimitive causes that to happen.
includes |= TypeFlags.NonPrimitive;
}
typeSet.set(type.id.toString(), type);
}
}
includes |= flags & TypeFlags.IncludesMask;
Expand All @@ -17563,7 +17562,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

// Add the given types to the given type set. Order is preserved, freshness is removed from literal
// types, duplicates are removed, and nested types of the given kind are flattened into the set.
function addTypesToIntersection(typeSet: Map<string, Type>, includes: TypeFlags, types: readonly Type[]) {
function addTypesToIntersection(typeSet: Type[], includes: TypeFlags, types: readonly Type[]) {
for (const type of types) {
includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type));
}
Expand Down Expand Up @@ -17702,9 +17701,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// 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.
function getIntersectionType(types: readonly Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], noSupertypeReduction?: boolean): Type {
const typeMembershipMap = new Map<string, Type>();
const includes = addTypesToIntersection(typeMembershipMap, 0 as TypeFlags, types);
const typeSet: Type[] = arrayFrom(typeMembershipMap.values());
const typeSet: Type[] = [];
const includes = addTypesToIntersection(typeSet, 0 as TypeFlags, types);
let objectFlags = ObjectFlags.None;
// An intersection type is considered empty if it contains
// the type never, or
Expand Down Expand Up @@ -22142,29 +22140,29 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
const targetTypes = target.types;
if (target.flags & TypeFlags.UnionOrIntersection && containsType(targetTypes, source)) {
return Ternary.True;
}
if (
relation !== comparableRelation && getObjectFlags(target) & ObjectFlags.PrimitiveUnion && !(source.flags & TypeFlags.EnumLiteral) && (
source.flags & (TypeFlags.StringLiteral | TypeFlags.BooleanLiteral | TypeFlags.BigIntLiteral) ||
(relation === subtypeRelation || relation === strictSubtypeRelation) && source.flags & TypeFlags.NumberLiteral
)
) {
// When relating a literal type to a union of primitive types, we know the relation is false unless
// the union contains the base primitive type or the literal type in one of its fresh/regular forms.
// We exclude numeric literals for non-subtype relations because numeric literals are assignable to
// numeric enum literals with the same value. Similarly, we exclude enum literal types because
// identically named enum types are related (see isEnumTypeRelatedTo). We exclude the comparable
// relation in entirety because it needs to be checked in both directions.
const alternateForm = source === (source as StringLiteralType).regularType ? (source as StringLiteralType).freshType : (source as StringLiteralType).regularType;
const primitive = source.flags & TypeFlags.StringLiteral ? stringType :
source.flags & TypeFlags.NumberLiteral ? numberType :
source.flags & TypeFlags.BigIntLiteral ? bigintType :
undefined;
return primitive && containsType(targetTypes, primitive) || alternateForm && containsType(targetTypes, alternateForm) ? Ternary.True : Ternary.False;
}
if (target.flags & TypeFlags.Union) {
if (containsType(targetTypes, source)) {
return Ternary.True;
}
if (
relation !== comparableRelation && getObjectFlags(target) & ObjectFlags.PrimitiveUnion && !(source.flags & TypeFlags.EnumLiteral) && (
source.flags & (TypeFlags.StringLiteral | TypeFlags.BooleanLiteral | TypeFlags.BigIntLiteral) ||
(relation === subtypeRelation || relation === strictSubtypeRelation) && source.flags & TypeFlags.NumberLiteral
)
) {
// When relating a literal type to a union of primitive types, we know the relation is false unless
// the union contains the base primitive type or the literal type in one of its fresh/regular forms.
// We exclude numeric literals for non-subtype relations because numeric literals are assignable to
// numeric enum literals with the same value. Similarly, we exclude enum literal types because
// identically named enum types are related (see isEnumTypeRelatedTo). We exclude the comparable
// relation in entirety because it needs to be checked in both directions.
const alternateForm = source === (source as StringLiteralType).regularType ? (source as StringLiteralType).freshType : (source as StringLiteralType).regularType;
const primitive = source.flags & TypeFlags.StringLiteral ? stringType :
source.flags & TypeFlags.NumberLiteral ? numberType :
source.flags & TypeFlags.BigIntLiteral ? bigintType :
undefined;
return primitive && containsType(targetTypes, primitive) || alternateForm && containsType(targetTypes, alternateForm) ? Ternary.True : Ternary.False;
}
const match = getMatchingUnionConstituentForType(target as UnionType, source);
if (match) {
const related = isRelatedTo(source, match, RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState);
Expand Down Expand Up @@ -22204,7 +22202,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
const sourceTypes = source.types;
if (source.flags & TypeFlags.Union && containsType(sourceTypes, target)) {
if (containsType(sourceTypes, target)) {
return Ternary.True;
}
const len = sourceTypes.length;
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/accessorsOverrideProperty8.types
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const Base = classWithProperties({
class MyClass extends Base {
>MyClass : MyClass
> : ^^^^^^^
>Base : Base & Properties<{ readonly x: "boolean"; y: "string"; }>
>Base : Properties<{ readonly x: "boolean"; y: "string"; }> & Base
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

get x() {
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/accessorsOverrideProperty9.types
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(
}

return MixedClass;
>MixedClass : ((abstract new (...args: any[]) => MixedClass) & { prototype: ApiItemContainerMixin<any>.MixedClass; }) & TBaseClass
>MixedClass : TBaseClass & ((abstract new (...args: any[]) => MixedClass) & { prototype: ApiItemContainerMixin<any>.MixedClass; })
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ export function Configurable<T extends Constructor<{}>>(base: T): T {
> : ^

return class extends base {
>class extends base { constructor(...args: any[]) { super(...args); } } : { new (...args: any[]): (Anonymous class); prototype: Configurable<any>.(Anonymous class); } & T
> : ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>class extends base { constructor(...args: any[]) { super(...args); } } : T & { new (...args: any[]): (Anonymous class); prototype: Configurable<any>.(Anonymous class); }
> : ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>base : {}
> : ^^

Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/amdLikeInputDeclarationEmit.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ define("lib/ExtendedClass", ["deps/BaseClass"],

//// [ExtendedClass.d.ts]
export = ExtendedClass;
declare const ExtendedClass: new () => {
declare const ExtendedClass: new () => import("deps/BaseClass") & {
f: () => "something";
} & import("deps/BaseClass");
};
10 changes: 5 additions & 5 deletions tests/baselines/reference/amdLikeInputDeclarationEmit.types
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ define("lib/ExtendedClass", ["deps/BaseClass"],
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

const ExtendedClass = BaseClass.extends({
>ExtendedClass : new () => { f: () => "something"; } & import("deps/BaseClass")
>ExtendedClass : new () => import("deps/BaseClass") & { f: () => "something"; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>BaseClass.extends({ f: function() { return "something"; } }) : new () => { f: () => "something"; } & import("deps/BaseClass")
>BaseClass.extends({ f: function() { return "something"; } }) : new () => import("deps/BaseClass") & { f: () => "something"; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>BaseClass.extends : <A>(a: A) => new () => A & import("deps/BaseClass")
>BaseClass.extends : <A>(a: A) => new () => import("deps/BaseClass") & A
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>BaseClass : typeof import("deps/BaseClass")
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>extends : <A>(a: A) => new () => A & import("deps/BaseClass")
>extends : <A>(a: A) => new () => import("deps/BaseClass") & A
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>{ f: function() { return "something"; } } : { f: () => "something"; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -95,7 +95,7 @@ define("lib/ExtendedClass", ["deps/BaseClass"],
> : ^^
>exports : any
> : ^^^
>ExtendedClass : new () => { f: () => "something"; } & import("deps/BaseClass")
>ExtendedClass : new () => import("deps/BaseClass") & { f: () => "something"; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

return module.exports;
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/anonClassDeclarationEmitIsAnon.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,11 @@ export declare function wrapClass(param: any): {
};
};
export type Constructor<T = {}> = new (...args: any[]) => T;
export declare function Timestamped<TBase extends Constructor>(Base: TBase): {
export declare function Timestamped<TBase extends Constructor>(Base: TBase): TBase & {
new (...args: any[]): {
timestamp: number;
};
} & TBase;
};
//// [index.d.ts]
declare const _default: {
new (): {
Expand All @@ -137,11 +137,11 @@ export default _default;
export declare class User {
name: string;
}
declare const TimestampedUser_base: {
declare const TimestampedUser_base: typeof User & {
new (...args: any[]): {
timestamp: number;
};
} & typeof User;
};
export declare class TimestampedUser extends TimestampedUser_base {
constructor();
}
12 changes: 6 additions & 6 deletions tests/baselines/reference/anonClassDeclarationEmitIsAnon.types
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ export type Constructor<T = {}> = new (...args: any[]) => T;
> : ^^^^^

export function Timestamped<TBase extends Constructor>(Base: TBase) {
>Timestamped : <TBase extends Constructor<{}>>(Base: TBase) => { new (...args: any[]): (Anonymous class); prototype: Timestamped<any>.(Anonymous class); } & TBase
>Timestamped : <TBase extends Constructor<{}>>(Base: TBase) => TBase & { new (...args: any[]): (Anonymous class); prototype: Timestamped<any>.(Anonymous class); }
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>Base : TBase
> : ^^^^^

return class extends Base {
>class extends Base { timestamp = Date.now(); } : { new (...args: any[]): (Anonymous class); prototype: Timestamped<any>.(Anonymous class); } & TBase
>class extends Base { timestamp = Date.now(); } : TBase & { new (...args: any[]): (Anonymous class); prototype: Timestamped<any>.(Anonymous class); }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>Base : {}
> : ^^
Expand All @@ -59,7 +59,7 @@ export function Timestamped<TBase extends Constructor>(Base: TBase) {
import { wrapClass, Timestamped } from "./wrapClass";
>wrapClass : (param: any) => typeof Wrapped
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>Timestamped : <TBase extends import("wrapClass").Constructor<{}>>(Base: TBase) => { new (...args: any[]): (Anonymous class); prototype: Timestamped<any>.(Anonymous class); } & TBase
>Timestamped : <TBase extends import("wrapClass").Constructor<{}>>(Base: TBase) => TBase & { new (...args: any[]): (Anonymous class); prototype: Timestamped<any>.(Anonymous class); }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

export default wrapClass(0);
Expand All @@ -86,9 +86,9 @@ export class User {
export class TimestampedUser extends Timestamped(User) {
>TimestampedUser : TimestampedUser
> : ^^^^^^^^^^^^^^^
>Timestamped(User) : Timestamped<typeof User>.(Anonymous class) & User
>Timestamped(User) : User & Timestamped<typeof User>.(Anonymous class)
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>Timestamped : <TBase extends import("wrapClass").Constructor<{}>>(Base: TBase) => { new (...args: any[]): (Anonymous class); prototype: Timestamped<any>.(Anonymous class); } & TBase
>Timestamped : <TBase extends import("wrapClass").Constructor<{}>>(Base: TBase) => TBase & { new (...args: any[]): (Anonymous class); prototype: Timestamped<any>.(Anonymous class); }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>User : typeof User
> : ^^^^^^^^^^^
Expand All @@ -97,7 +97,7 @@ export class TimestampedUser extends Timestamped(User) {
super();
>super() : void
> : ^^^^
>super : { new (...args: any[]): Timestamped<typeof User>.(Anonymous class); prototype: Timestamped<any>.(Anonymous class); } & typeof User
>super : typeof User & { new (...args: any[]): Timestamped<typeof User>.(Anonymous class); prototype: Timestamped<any>.(Anonymous class); }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}
}
Loading