Allow weak type detection for intersection sources (#26668)
Previously, intersections were only allowed as targets, but this was just an artifact of the original implementation, which operated inside the structural part of isRelatedTo. Removing this restriction catches subtle bugs in React user code, where a function named `create` returns a mapped type whose types are all branded numbers. The display of these properties, for some original type `T`, is not `number & { __ }` but the much-less-obvious `RegisteredStyle<T>`.
This commit is contained in:
parent
bdb7c351a2
commit
0043ba16b1
|
@ -11178,7 +11178,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
if (relation !== comparableRelation &&
|
||||
!(source.flags & TypeFlags.UnionOrIntersection) &&
|
||||
!(source.flags & TypeFlags.Union) &&
|
||||
!(target.flags & TypeFlags.Union) &&
|
||||
!isIntersectionConstituent &&
|
||||
source !== globalObjectType &&
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
tests/cases/conformance/types/intersection/intersectionAsWeakTypeSource.ts(8,7): error TS2559: Type 'XY' has no properties in common with type 'Z'.
|
||||
tests/cases/conformance/types/intersection/intersectionAsWeakTypeSource.ts(18,7): error TS2322: Type 'Brand<{ view: number; styleMedia: string; }>' is not assignable to type 'ViewStyle'.
|
||||
Property 'view' is missing in type 'Number & { __brand: { view: number; styleMedia: string; }; }'.
|
||||
|
||||
|
||||
==== tests/cases/conformance/types/intersection/intersectionAsWeakTypeSource.ts (2 errors) ====
|
||||
interface X { x: string }
|
||||
interface Y { y: number }
|
||||
interface Z { z?: boolean }
|
||||
|
||||
type XY = X & Y;
|
||||
const xy: XY = {x: 'x', y: 10};
|
||||
|
||||
const z1: Z = xy; // error, {xy} doesn't overlap with {z}
|
||||
~~
|
||||
!!! error TS2559: Type 'XY' has no properties in common with type 'Z'.
|
||||
|
||||
|
||||
interface ViewStyle {
|
||||
view: number
|
||||
styleMedia: string
|
||||
}
|
||||
type Brand<T> = number & { __brand: T }
|
||||
declare function create<T extends { [s: string]: ViewStyle }>(styles: T): { [P in keyof T]: Brand<T[P]> };
|
||||
const wrapped = create({ first: { view: 0, styleMedia: "???" } });
|
||||
const vs: ViewStyle = wrapped.first // error, first is a branded number
|
||||
~~
|
||||
!!! error TS2322: Type 'Brand<{ view: number; styleMedia: string; }>' is not assignable to type 'ViewStyle'.
|
||||
!!! error TS2322: Property 'view' is missing in type 'Number & { __brand: { view: number; styleMedia: string; }; }'.
|
||||
|
26
tests/baselines/reference/intersectionAsWeakTypeSource.js
Normal file
26
tests/baselines/reference/intersectionAsWeakTypeSource.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
//// [intersectionAsWeakTypeSource.ts]
|
||||
interface X { x: string }
|
||||
interface Y { y: number }
|
||||
interface Z { z?: boolean }
|
||||
|
||||
type XY = X & Y;
|
||||
const xy: XY = {x: 'x', y: 10};
|
||||
|
||||
const z1: Z = xy; // error, {xy} doesn't overlap with {z}
|
||||
|
||||
|
||||
interface ViewStyle {
|
||||
view: number
|
||||
styleMedia: string
|
||||
}
|
||||
type Brand<T> = number & { __brand: T }
|
||||
declare function create<T extends { [s: string]: ViewStyle }>(styles: T): { [P in keyof T]: Brand<T[P]> };
|
||||
const wrapped = create({ first: { view: 0, styleMedia: "???" } });
|
||||
const vs: ViewStyle = wrapped.first // error, first is a branded number
|
||||
|
||||
|
||||
//// [intersectionAsWeakTypeSource.js]
|
||||
var xy = { x: 'x', y: 10 };
|
||||
var z1 = xy; // error, {xy} doesn't overlap with {z}
|
||||
var wrapped = create({ first: { view: 0, styleMedia: "???" } });
|
||||
var vs = wrapped.first; // error, first is a branded number
|
|
@ -0,0 +1,72 @@
|
|||
=== tests/cases/conformance/types/intersection/intersectionAsWeakTypeSource.ts ===
|
||||
interface X { x: string }
|
||||
>X : Symbol(X, Decl(intersectionAsWeakTypeSource.ts, 0, 0))
|
||||
>x : Symbol(X.x, Decl(intersectionAsWeakTypeSource.ts, 0, 13))
|
||||
|
||||
interface Y { y: number }
|
||||
>Y : Symbol(Y, Decl(intersectionAsWeakTypeSource.ts, 0, 25))
|
||||
>y : Symbol(Y.y, Decl(intersectionAsWeakTypeSource.ts, 1, 13))
|
||||
|
||||
interface Z { z?: boolean }
|
||||
>Z : Symbol(Z, Decl(intersectionAsWeakTypeSource.ts, 1, 25))
|
||||
>z : Symbol(Z.z, Decl(intersectionAsWeakTypeSource.ts, 2, 13))
|
||||
|
||||
type XY = X & Y;
|
||||
>XY : Symbol(XY, Decl(intersectionAsWeakTypeSource.ts, 2, 27))
|
||||
>X : Symbol(X, Decl(intersectionAsWeakTypeSource.ts, 0, 0))
|
||||
>Y : Symbol(Y, Decl(intersectionAsWeakTypeSource.ts, 0, 25))
|
||||
|
||||
const xy: XY = {x: 'x', y: 10};
|
||||
>xy : Symbol(xy, Decl(intersectionAsWeakTypeSource.ts, 5, 5))
|
||||
>XY : Symbol(XY, Decl(intersectionAsWeakTypeSource.ts, 2, 27))
|
||||
>x : Symbol(x, Decl(intersectionAsWeakTypeSource.ts, 5, 16))
|
||||
>y : Symbol(y, Decl(intersectionAsWeakTypeSource.ts, 5, 23))
|
||||
|
||||
const z1: Z = xy; // error, {xy} doesn't overlap with {z}
|
||||
>z1 : Symbol(z1, Decl(intersectionAsWeakTypeSource.ts, 7, 5))
|
||||
>Z : Symbol(Z, Decl(intersectionAsWeakTypeSource.ts, 1, 25))
|
||||
>xy : Symbol(xy, Decl(intersectionAsWeakTypeSource.ts, 5, 5))
|
||||
|
||||
|
||||
interface ViewStyle {
|
||||
>ViewStyle : Symbol(ViewStyle, Decl(intersectionAsWeakTypeSource.ts, 7, 17))
|
||||
|
||||
view: number
|
||||
>view : Symbol(ViewStyle.view, Decl(intersectionAsWeakTypeSource.ts, 10, 21))
|
||||
|
||||
styleMedia: string
|
||||
>styleMedia : Symbol(ViewStyle.styleMedia, Decl(intersectionAsWeakTypeSource.ts, 11, 16))
|
||||
}
|
||||
type Brand<T> = number & { __brand: T }
|
||||
>Brand : Symbol(Brand, Decl(intersectionAsWeakTypeSource.ts, 13, 1))
|
||||
>T : Symbol(T, Decl(intersectionAsWeakTypeSource.ts, 14, 11))
|
||||
>__brand : Symbol(__brand, Decl(intersectionAsWeakTypeSource.ts, 14, 26))
|
||||
>T : Symbol(T, Decl(intersectionAsWeakTypeSource.ts, 14, 11))
|
||||
|
||||
declare function create<T extends { [s: string]: ViewStyle }>(styles: T): { [P in keyof T]: Brand<T[P]> };
|
||||
>create : Symbol(create, Decl(intersectionAsWeakTypeSource.ts, 14, 39))
|
||||
>T : Symbol(T, Decl(intersectionAsWeakTypeSource.ts, 15, 24))
|
||||
>s : Symbol(s, Decl(intersectionAsWeakTypeSource.ts, 15, 37))
|
||||
>ViewStyle : Symbol(ViewStyle, Decl(intersectionAsWeakTypeSource.ts, 7, 17))
|
||||
>styles : Symbol(styles, Decl(intersectionAsWeakTypeSource.ts, 15, 62))
|
||||
>T : Symbol(T, Decl(intersectionAsWeakTypeSource.ts, 15, 24))
|
||||
>P : Symbol(P, Decl(intersectionAsWeakTypeSource.ts, 15, 77))
|
||||
>T : Symbol(T, Decl(intersectionAsWeakTypeSource.ts, 15, 24))
|
||||
>Brand : Symbol(Brand, Decl(intersectionAsWeakTypeSource.ts, 13, 1))
|
||||
>T : Symbol(T, Decl(intersectionAsWeakTypeSource.ts, 15, 24))
|
||||
>P : Symbol(P, Decl(intersectionAsWeakTypeSource.ts, 15, 77))
|
||||
|
||||
const wrapped = create({ first: { view: 0, styleMedia: "???" } });
|
||||
>wrapped : Symbol(wrapped, Decl(intersectionAsWeakTypeSource.ts, 16, 5))
|
||||
>create : Symbol(create, Decl(intersectionAsWeakTypeSource.ts, 14, 39))
|
||||
>first : Symbol(first, Decl(intersectionAsWeakTypeSource.ts, 16, 24))
|
||||
>view : Symbol(view, Decl(intersectionAsWeakTypeSource.ts, 16, 33))
|
||||
>styleMedia : Symbol(styleMedia, Decl(intersectionAsWeakTypeSource.ts, 16, 42))
|
||||
|
||||
const vs: ViewStyle = wrapped.first // error, first is a branded number
|
||||
>vs : Symbol(vs, Decl(intersectionAsWeakTypeSource.ts, 17, 5))
|
||||
>ViewStyle : Symbol(ViewStyle, Decl(intersectionAsWeakTypeSource.ts, 7, 17))
|
||||
>wrapped.first : Symbol(first, Decl(intersectionAsWeakTypeSource.ts, 16, 24))
|
||||
>wrapped : Symbol(wrapped, Decl(intersectionAsWeakTypeSource.ts, 16, 5))
|
||||
>first : Symbol(first, Decl(intersectionAsWeakTypeSource.ts, 16, 24))
|
||||
|
60
tests/baselines/reference/intersectionAsWeakTypeSource.types
Normal file
60
tests/baselines/reference/intersectionAsWeakTypeSource.types
Normal file
|
@ -0,0 +1,60 @@
|
|||
=== tests/cases/conformance/types/intersection/intersectionAsWeakTypeSource.ts ===
|
||||
interface X { x: string }
|
||||
>x : string
|
||||
|
||||
interface Y { y: number }
|
||||
>y : number
|
||||
|
||||
interface Z { z?: boolean }
|
||||
>z : boolean
|
||||
|
||||
type XY = X & Y;
|
||||
>XY : XY
|
||||
|
||||
const xy: XY = {x: 'x', y: 10};
|
||||
>xy : XY
|
||||
>{x: 'x', y: 10} : { x: string; y: number; }
|
||||
>x : string
|
||||
>'x' : "x"
|
||||
>y : number
|
||||
>10 : 10
|
||||
|
||||
const z1: Z = xy; // error, {xy} doesn't overlap with {z}
|
||||
>z1 : Z
|
||||
>xy : XY
|
||||
|
||||
|
||||
interface ViewStyle {
|
||||
view: number
|
||||
>view : number
|
||||
|
||||
styleMedia: string
|
||||
>styleMedia : string
|
||||
}
|
||||
type Brand<T> = number & { __brand: T }
|
||||
>Brand : Brand<T>
|
||||
>__brand : T
|
||||
|
||||
declare function create<T extends { [s: string]: ViewStyle }>(styles: T): { [P in keyof T]: Brand<T[P]> };
|
||||
>create : <T extends { [s: string]: ViewStyle; }>(styles: T) => { [P in keyof T]: Brand<T[P]>; }
|
||||
>s : string
|
||||
>styles : T
|
||||
|
||||
const wrapped = create({ first: { view: 0, styleMedia: "???" } });
|
||||
>wrapped : { first: Brand<{ view: number; styleMedia: string; }>; }
|
||||
>create({ first: { view: 0, styleMedia: "???" } }) : { first: Brand<{ view: number; styleMedia: string; }>; }
|
||||
>create : <T extends { [s: string]: ViewStyle; }>(styles: T) => { [P in keyof T]: Brand<T[P]>; }
|
||||
>{ first: { view: 0, styleMedia: "???" } } : { first: { view: number; styleMedia: string; }; }
|
||||
>first : { view: number; styleMedia: string; }
|
||||
>{ view: 0, styleMedia: "???" } : { view: number; styleMedia: string; }
|
||||
>view : number
|
||||
>0 : 0
|
||||
>styleMedia : string
|
||||
>"???" : "???"
|
||||
|
||||
const vs: ViewStyle = wrapped.first // error, first is a branded number
|
||||
>vs : ViewStyle
|
||||
>wrapped.first : Brand<{ view: number; styleMedia: string; }>
|
||||
>wrapped : { first: Brand<{ view: number; styleMedia: string; }>; }
|
||||
>first : Brand<{ view: number; styleMedia: string; }>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
interface X { x: string }
|
||||
interface Y { y: number }
|
||||
interface Z { z?: boolean }
|
||||
|
||||
type XY = X & Y;
|
||||
const xy: XY = {x: 'x', y: 10};
|
||||
|
||||
const z1: Z = xy; // error, {xy} doesn't overlap with {z}
|
||||
|
||||
|
||||
interface ViewStyle {
|
||||
view: number
|
||||
styleMedia: string
|
||||
}
|
||||
type Brand<T> = number & { __brand: T }
|
||||
declare function create<T extends { [s: string]: ViewStyle }>(styles: T): { [P in keyof T]: Brand<T[P]> };
|
||||
const wrapped = create({ first: { view: 0, styleMedia: "???" } });
|
||||
const vs: ViewStyle = wrapped.first // error, first is a branded number
|
Loading…
Reference in a new issue