Skip to content

Add fastpath to isRelatedTo for type references #37481

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

Merged
89 changes: 52 additions & 37 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
@@ -15541,11 +15541,21 @@ namespace ts {
* * Ternary.False if they are not related.
*/
function isRelatedTo(originalSource: Type, originalTarget: Type, reportErrors = false, headMessage?: DiagnosticMessage, intersectionState = IntersectionState.None): Ternary {
// Before normalization: if `source` is type an object type, and `target` is primitive,
// skip all the checks we don't need and just return `isSimpleTypeRelatedTo` result
if (originalSource.flags & TypeFlags.Object && originalTarget.flags & TypeFlags.Primitive) {
if (isSimpleTypeRelatedTo(originalSource, originalTarget, relation, reportErrors ? reportError : undefined)) {
return Ternary.True;
}
reportErrorResults(originalSource, originalTarget, Ternary.False, !!(getObjectFlags(originalSource) & ObjectFlags.JsxAttributes));
return Ternary.False;
}

// Normalize the source and target types: Turn fresh literal types into regular literal types,
// turn deferred type references into regular type references, simplify indexed access and
// conditional types, and resolve substitution types to either the substitution (on the source
// side) or the type variable (on the target side).
let source = getNormalizedType(originalSource, /*writing*/ false);
const source = getNormalizedType(originalSource, /*writing*/ false);
let target = getNormalizedType(originalTarget, /*writing*/ true);

if (source === target) return Ternary.True;
@@ -15679,6 +15689,7 @@ namespace ts {
}
}
}

// For certain combinations involving intersections and optional, excess, or mismatched properties we need
// an extra property check where the intersection is viewed as a single object. The following are motivating
// examples that all should be errors, but aren't without this extra property check:
@@ -15702,47 +15713,51 @@ namespace ts {
inPropertyCheck = false;
}

if (!result && reportErrors) {
source = originalSource.aliasSymbol ? originalSource : source;
target = originalTarget.aliasSymbol ? originalTarget : target;
let maybeSuppress = overrideNextErrorInfo > 0;
if (maybeSuppress) {
overrideNextErrorInfo--;
}
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) {
const currentError = errorInfo;
tryElaborateArrayLikeErrors(source, target, reportErrors);
if (errorInfo !== currentError) {
maybeSuppress = !!errorInfo;
reportErrorResults(source, target, result, isComparingJsxAttributes);
return result;

function reportErrorResults(source: Type, target: Type, result: Ternary, isComparingJsxAttributes: boolean) {
if (!result && reportErrors) {
source = originalSource.aliasSymbol ? originalSource : source;
target = originalTarget.aliasSymbol ? originalTarget : target;
let maybeSuppress = overrideNextErrorInfo > 0;
if (maybeSuppress) {
overrideNextErrorInfo--;
}
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) {
const currentError = errorInfo;
tryElaborateArrayLikeErrors(source, target, reportErrors);
if (errorInfo !== currentError) {
maybeSuppress = !!errorInfo;
}
}
}
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) {
tryElaborateErrorsForPrimitivesAndObjects(source, target);
}
else if (source.symbol && source.flags & TypeFlags.Object && globalObjectType === source) {
reportError(Diagnostics.The_Object_type_is_assignable_to_very_few_other_types_Did_you_mean_to_use_the_any_type_instead);
}
else if (isComparingJsxAttributes && target.flags & TypeFlags.Intersection) {
const targetTypes = (target as IntersectionType).types;
const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes, errorNode);
const intrinsicClassAttributes = getJsxType(JsxNames.IntrinsicClassAttributes, errorNode);
if (intrinsicAttributes !== errorType && intrinsicClassAttributes !== errorType &&
(contains(targetTypes, intrinsicAttributes) || contains(targetTypes, intrinsicClassAttributes))) {
// do not report top error
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) {
tryElaborateErrorsForPrimitivesAndObjects(source, target);
}
else if (source.symbol && source.flags & TypeFlags.Object && globalObjectType === source) {
reportError(Diagnostics.The_Object_type_is_assignable_to_very_few_other_types_Did_you_mean_to_use_the_any_type_instead);
}
else if (isComparingJsxAttributes && target.flags & TypeFlags.Intersection) {
const targetTypes = (target as IntersectionType).types;
const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes, errorNode);
const intrinsicClassAttributes = getJsxType(JsxNames.IntrinsicClassAttributes, errorNode);
if (intrinsicAttributes !== errorType && intrinsicClassAttributes !== errorType &&
(contains(targetTypes, intrinsicAttributes) || contains(targetTypes, intrinsicClassAttributes))) {
// do not report top error
return result;
}
}
else {
errorInfo = elaborateNeverIntersection(errorInfo, originalTarget);
}
if (!headMessage && maybeSuppress) {
lastSkippedInfo = [source, target];
// Used by, eg, missing property checking to replace the top-level message with a more informative one
return result;
}
reportRelationError(headMessage, source, target);
}
else {
errorInfo = elaborateNeverIntersection(errorInfo, originalTarget);
}
if (!headMessage && maybeSuppress) {
lastSkippedInfo = [source, target];
// Used by, eg, missing property checking to replace the top-level message with a more informative one
return result;
}
reportRelationError(headMessage, source, target);
}
return result;
}

function isIdenticalTo(source: Type, target: Type): Ternary {
66 changes: 66 additions & 0 deletions tests/baselines/reference/recursiveArrayNotCircular.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//// [recursiveArrayNotCircular.ts]
type Action<T, P> = P extends void ? { type : T } : { type: T, payload: P }

enum ActionType {
Foo,
Bar,
Baz,
Batch
}

type ReducerAction =
| Action<ActionType.Bar, number>
| Action<ActionType.Baz, boolean>
| Action<ActionType.Foo, string>
| Action<ActionType.Batch, ReducerAction[]>

function assertNever(a: never): never {
throw new Error("Unreachable!");
}

function reducer(action: ReducerAction): void {
switch(action.type) {
case ActionType.Bar:
const x: number = action.payload;
break;
case ActionType.Baz:
const y: boolean = action.payload;
break;
case ActionType.Foo:
const z: string = action.payload;
break;
case ActionType.Batch:
action.payload.map(reducer);
break;
default: return assertNever(action);
}
}

//// [recursiveArrayNotCircular.js]
var ActionType;
(function (ActionType) {
ActionType[ActionType["Foo"] = 0] = "Foo";
ActionType[ActionType["Bar"] = 1] = "Bar";
ActionType[ActionType["Baz"] = 2] = "Baz";
ActionType[ActionType["Batch"] = 3] = "Batch";
})(ActionType || (ActionType = {}));
function assertNever(a) {
throw new Error("Unreachable!");
}
function reducer(action) {
switch (action.type) {
case ActionType.Bar:
var x = action.payload;
break;
case ActionType.Baz:
var y = action.payload;
break;
case ActionType.Foo:
var z = action.payload;
break;
case ActionType.Batch:
action.payload.map(reducer);
break;
default: return assertNever(action);
}
}
126 changes: 126 additions & 0 deletions tests/baselines/reference/recursiveArrayNotCircular.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
=== tests/cases/compiler/recursiveArrayNotCircular.ts ===
type Action<T, P> = P extends void ? { type : T } : { type: T, payload: P }
>Action : Symbol(Action, Decl(recursiveArrayNotCircular.ts, 0, 0))
>T : Symbol(T, Decl(recursiveArrayNotCircular.ts, 0, 12))
>P : Symbol(P, Decl(recursiveArrayNotCircular.ts, 0, 14))
>P : Symbol(P, Decl(recursiveArrayNotCircular.ts, 0, 14))
>type : Symbol(type, Decl(recursiveArrayNotCircular.ts, 0, 38))
>T : Symbol(T, Decl(recursiveArrayNotCircular.ts, 0, 12))
>type : Symbol(type, Decl(recursiveArrayNotCircular.ts, 0, 53))
>T : Symbol(T, Decl(recursiveArrayNotCircular.ts, 0, 12))
>payload : Symbol(payload, Decl(recursiveArrayNotCircular.ts, 0, 62))
>P : Symbol(P, Decl(recursiveArrayNotCircular.ts, 0, 14))

enum ActionType {
>ActionType : Symbol(ActionType, Decl(recursiveArrayNotCircular.ts, 0, 75))

Foo,
>Foo : Symbol(ActionType.Foo, Decl(recursiveArrayNotCircular.ts, 2, 17))

Bar,
>Bar : Symbol(ActionType.Bar, Decl(recursiveArrayNotCircular.ts, 3, 8))

Baz,
>Baz : Symbol(ActionType.Baz, Decl(recursiveArrayNotCircular.ts, 4, 8))

Batch
>Batch : Symbol(ActionType.Batch, Decl(recursiveArrayNotCircular.ts, 5, 8))
}

type ReducerAction =
>ReducerAction : Symbol(ReducerAction, Decl(recursiveArrayNotCircular.ts, 7, 1))

| Action<ActionType.Bar, number>
>Action : Symbol(Action, Decl(recursiveArrayNotCircular.ts, 0, 0))
>ActionType : Symbol(ActionType, Decl(recursiveArrayNotCircular.ts, 0, 75))
>Bar : Symbol(ActionType.Bar, Decl(recursiveArrayNotCircular.ts, 3, 8))

| Action<ActionType.Baz, boolean>
>Action : Symbol(Action, Decl(recursiveArrayNotCircular.ts, 0, 0))
>ActionType : Symbol(ActionType, Decl(recursiveArrayNotCircular.ts, 0, 75))
>Baz : Symbol(ActionType.Baz, Decl(recursiveArrayNotCircular.ts, 4, 8))

| Action<ActionType.Foo, string>
>Action : Symbol(Action, Decl(recursiveArrayNotCircular.ts, 0, 0))
>ActionType : Symbol(ActionType, Decl(recursiveArrayNotCircular.ts, 0, 75))
>Foo : Symbol(ActionType.Foo, Decl(recursiveArrayNotCircular.ts, 2, 17))

| Action<ActionType.Batch, ReducerAction[]>
>Action : Symbol(Action, Decl(recursiveArrayNotCircular.ts, 0, 0))
>ActionType : Symbol(ActionType, Decl(recursiveArrayNotCircular.ts, 0, 75))
>Batch : Symbol(ActionType.Batch, Decl(recursiveArrayNotCircular.ts, 5, 8))
>ReducerAction : Symbol(ReducerAction, Decl(recursiveArrayNotCircular.ts, 7, 1))

function assertNever(a: never): never {
>assertNever : Symbol(assertNever, Decl(recursiveArrayNotCircular.ts, 13, 45))
>a : Symbol(a, Decl(recursiveArrayNotCircular.ts, 15, 21))

throw new Error("Unreachable!");
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}

function reducer(action: ReducerAction): void {
>reducer : Symbol(reducer, Decl(recursiveArrayNotCircular.ts, 17, 1))
>action : Symbol(action, Decl(recursiveArrayNotCircular.ts, 19, 17))
>ReducerAction : Symbol(ReducerAction, Decl(recursiveArrayNotCircular.ts, 7, 1))

switch(action.type) {
>action.type : Symbol(type, Decl(recursiveArrayNotCircular.ts, 0, 53), Decl(recursiveArrayNotCircular.ts, 0, 53), Decl(recursiveArrayNotCircular.ts, 0, 53), Decl(recursiveArrayNotCircular.ts, 0, 53), Decl(recursiveArrayNotCircular.ts, 0, 53))
>action : Symbol(action, Decl(recursiveArrayNotCircular.ts, 19, 17))
>type : Symbol(type, Decl(recursiveArrayNotCircular.ts, 0, 53), Decl(recursiveArrayNotCircular.ts, 0, 53), Decl(recursiveArrayNotCircular.ts, 0, 53), Decl(recursiveArrayNotCircular.ts, 0, 53), Decl(recursiveArrayNotCircular.ts, 0, 53))

case ActionType.Bar:
>ActionType.Bar : Symbol(ActionType.Bar, Decl(recursiveArrayNotCircular.ts, 3, 8))
>ActionType : Symbol(ActionType, Decl(recursiveArrayNotCircular.ts, 0, 75))
>Bar : Symbol(ActionType.Bar, Decl(recursiveArrayNotCircular.ts, 3, 8))

const x: number = action.payload;
>x : Symbol(x, Decl(recursiveArrayNotCircular.ts, 22, 17))
>action.payload : Symbol(payload, Decl(recursiveArrayNotCircular.ts, 0, 62))
>action : Symbol(action, Decl(recursiveArrayNotCircular.ts, 19, 17))
>payload : Symbol(payload, Decl(recursiveArrayNotCircular.ts, 0, 62))

break;
case ActionType.Baz:
>ActionType.Baz : Symbol(ActionType.Baz, Decl(recursiveArrayNotCircular.ts, 4, 8))
>ActionType : Symbol(ActionType, Decl(recursiveArrayNotCircular.ts, 0, 75))
>Baz : Symbol(ActionType.Baz, Decl(recursiveArrayNotCircular.ts, 4, 8))

const y: boolean = action.payload;
>y : Symbol(y, Decl(recursiveArrayNotCircular.ts, 25, 17))
>action.payload : Symbol(payload, Decl(recursiveArrayNotCircular.ts, 0, 62), Decl(recursiveArrayNotCircular.ts, 0, 62))
>action : Symbol(action, Decl(recursiveArrayNotCircular.ts, 19, 17))
>payload : Symbol(payload, Decl(recursiveArrayNotCircular.ts, 0, 62), Decl(recursiveArrayNotCircular.ts, 0, 62))

break;
case ActionType.Foo:
>ActionType.Foo : Symbol(ActionType.Foo, Decl(recursiveArrayNotCircular.ts, 2, 17))
>ActionType : Symbol(ActionType, Decl(recursiveArrayNotCircular.ts, 0, 75))
>Foo : Symbol(ActionType.Foo, Decl(recursiveArrayNotCircular.ts, 2, 17))

const z: string = action.payload;
>z : Symbol(z, Decl(recursiveArrayNotCircular.ts, 28, 17))
>action.payload : Symbol(payload, Decl(recursiveArrayNotCircular.ts, 0, 62))
>action : Symbol(action, Decl(recursiveArrayNotCircular.ts, 19, 17))
>payload : Symbol(payload, Decl(recursiveArrayNotCircular.ts, 0, 62))

break;
case ActionType.Batch:
>ActionType.Batch : Symbol(ActionType.Batch, Decl(recursiveArrayNotCircular.ts, 5, 8))
>ActionType : Symbol(ActionType, Decl(recursiveArrayNotCircular.ts, 0, 75))
>Batch : Symbol(ActionType.Batch, Decl(recursiveArrayNotCircular.ts, 5, 8))

action.payload.map(reducer);
>action.payload.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>action.payload : Symbol(payload, Decl(recursiveArrayNotCircular.ts, 0, 62))
>action : Symbol(action, Decl(recursiveArrayNotCircular.ts, 19, 17))
>payload : Symbol(payload, Decl(recursiveArrayNotCircular.ts, 0, 62))
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>reducer : Symbol(reducer, Decl(recursiveArrayNotCircular.ts, 17, 1))

break;
default: return assertNever(action);
>assertNever : Symbol(assertNever, Decl(recursiveArrayNotCircular.ts, 13, 45))
>action : Symbol(action, Decl(recursiveArrayNotCircular.ts, 19, 17))
}
}
114 changes: 114 additions & 0 deletions tests/baselines/reference/recursiveArrayNotCircular.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
=== tests/cases/compiler/recursiveArrayNotCircular.ts ===
type Action<T, P> = P extends void ? { type : T } : { type: T, payload: P }
>Action : Action<T, P>
>type : T
>type : T
>payload : P

enum ActionType {
>ActionType : ActionType

Foo,
>Foo : ActionType.Foo

Bar,
>Bar : ActionType.Bar

Baz,
>Baz : ActionType.Baz

Batch
>Batch : ActionType.Batch
}

type ReducerAction =
>ReducerAction : ReducerAction

| Action<ActionType.Bar, number>
>ActionType : any

| Action<ActionType.Baz, boolean>
>ActionType : any

| Action<ActionType.Foo, string>
>ActionType : any

| Action<ActionType.Batch, ReducerAction[]>
>ActionType : any

function assertNever(a: never): never {
>assertNever : (a: never) => never
>a : never

throw new Error("Unreachable!");
>new Error("Unreachable!") : Error
>Error : ErrorConstructor
>"Unreachable!" : "Unreachable!"
}

function reducer(action: ReducerAction): void {
>reducer : (action: ReducerAction) => void
>action : ReducerAction

switch(action.type) {
>action.type : ActionType
>action : ReducerAction
>type : ActionType

case ActionType.Bar:
>ActionType.Bar : ActionType.Bar
>ActionType : typeof ActionType
>Bar : ActionType.Bar

const x: number = action.payload;
>x : number
>action.payload : number
>action : { type: ActionType.Bar; payload: number; }
>payload : number

break;
case ActionType.Baz:
>ActionType.Baz : ActionType.Baz
>ActionType : typeof ActionType
>Baz : ActionType.Baz

const y: boolean = action.payload;
>y : boolean
>action.payload : boolean
>action : { type: ActionType.Baz; payload: false; } | { type: ActionType.Baz; payload: true; }
>payload : boolean

break;
case ActionType.Foo:
>ActionType.Foo : ActionType.Foo
>ActionType : typeof ActionType
>Foo : ActionType.Foo

const z: string = action.payload;
>z : string
>action.payload : string
>action : { type: ActionType.Foo; payload: string; }
>payload : string

break;
case ActionType.Batch:
>ActionType.Batch : ActionType.Batch
>ActionType : typeof ActionType
>Batch : ActionType.Batch

action.payload.map(reducer);
>action.payload.map(reducer) : void[]
>action.payload.map : <U>(callbackfn: (value: ReducerAction, index: number, array: ReducerAction[]) => U, thisArg?: any) => U[]
>action.payload : ReducerAction[]
>action : { type: ActionType.Batch; payload: ReducerAction[]; }
>payload : ReducerAction[]
>map : <U>(callbackfn: (value: ReducerAction, index: number, array: ReducerAction[]) => U, thisArg?: any) => U[]
>reducer : (action: ReducerAction) => void

break;
default: return assertNever(action);
>assertNever(action) : never
>assertNever : (a: never) => never
>action : never
}
}
36 changes: 36 additions & 0 deletions tests/cases/compiler/recursiveArrayNotCircular.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
type Action<T, P> = P extends void ? { type : T } : { type: T, payload: P }

enum ActionType {
Foo,
Bar,
Baz,
Batch
}

type ReducerAction =
| Action<ActionType.Bar, number>
| Action<ActionType.Baz, boolean>
| Action<ActionType.Foo, string>
| Action<ActionType.Batch, ReducerAction[]>

function assertNever(a: never): never {
throw new Error("Unreachable!");
}

function reducer(action: ReducerAction): void {
switch(action.type) {
case ActionType.Bar:
const x: number = action.payload;
break;
case ActionType.Baz:
const y: boolean = action.payload;
break;
case ActionType.Foo:
const z: string = action.payload;
break;
case ActionType.Batch:
action.payload.map(reducer);
break;
default: return assertNever(action);
}
}