Skip to content
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