diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d627a55ce8b5b..4e2495aceae18 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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 { diff --git a/tests/baselines/reference/recursiveArrayNotCircular.js b/tests/baselines/reference/recursiveArrayNotCircular.js new file mode 100644 index 0000000000000..50f612bfcbf9c --- /dev/null +++ b/tests/baselines/reference/recursiveArrayNotCircular.js @@ -0,0 +1,66 @@ +//// [recursiveArrayNotCircular.ts] +type Action = P extends void ? { type : T } : { type: T, payload: P } + +enum ActionType { + Foo, + Bar, + Baz, + Batch +} + +type ReducerAction = + | Action + | Action + | Action + | Action + +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); + } +} diff --git a/tests/baselines/reference/recursiveArrayNotCircular.symbols b/tests/baselines/reference/recursiveArrayNotCircular.symbols new file mode 100644 index 0000000000000..83a1610e9f440 --- /dev/null +++ b/tests/baselines/reference/recursiveArrayNotCircular.symbols @@ -0,0 +1,126 @@ +=== tests/cases/compiler/recursiveArrayNotCircular.ts === +type Action = 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 +>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 +>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 +>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 +>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)) + } +} diff --git a/tests/baselines/reference/recursiveArrayNotCircular.types b/tests/baselines/reference/recursiveArrayNotCircular.types new file mode 100644 index 0000000000000..1d0c76523af42 --- /dev/null +++ b/tests/baselines/reference/recursiveArrayNotCircular.types @@ -0,0 +1,114 @@ +=== tests/cases/compiler/recursiveArrayNotCircular.ts === +type Action = P extends void ? { type : T } : { type: T, payload: P } +>Action : Action +>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 : any + + | Action +>ActionType : any + + | Action +>ActionType : any + + | Action +>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 : (callbackfn: (value: ReducerAction, index: number, array: ReducerAction[]) => U, thisArg?: any) => U[] +>action.payload : ReducerAction[] +>action : { type: ActionType.Batch; payload: ReducerAction[]; } +>payload : ReducerAction[] +>map : (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 + } +} diff --git a/tests/cases/compiler/recursiveArrayNotCircular.ts b/tests/cases/compiler/recursiveArrayNotCircular.ts new file mode 100644 index 0000000000000..3bf2b419f3ae0 --- /dev/null +++ b/tests/cases/compiler/recursiveArrayNotCircular.ts @@ -0,0 +1,36 @@ +type Action = P extends void ? { type : T } : { type: T, payload: P } + +enum ActionType { + Foo, + Bar, + Baz, + Batch +} + +type ReducerAction = + | Action + | Action + | Action + | Action + +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); + } +} \ No newline at end of file