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
Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand All @@ -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 {
Expand Down
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))
}
}
Loading