From 0043ba16b17f82c8e7acf87adabd6e6ad540b147 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 24 Aug 2018 10:30:39 -0700 Subject: [PATCH] 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`. --- src/compiler/checker.ts | 2 +- .../intersectionAsWeakTypeSource.errors.txt | 30 ++++++++ .../reference/intersectionAsWeakTypeSource.js | 26 +++++++ .../intersectionAsWeakTypeSource.symbols | 72 +++++++++++++++++++ .../intersectionAsWeakTypeSource.types | 60 ++++++++++++++++ .../intersectionAsWeakTypeSource.ts | 18 +++++ 6 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/intersectionAsWeakTypeSource.errors.txt create mode 100644 tests/baselines/reference/intersectionAsWeakTypeSource.js create mode 100644 tests/baselines/reference/intersectionAsWeakTypeSource.symbols create mode 100644 tests/baselines/reference/intersectionAsWeakTypeSource.types create mode 100644 tests/cases/conformance/types/intersection/intersectionAsWeakTypeSource.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0509d53c9b..50cbb6cb06 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11178,7 +11178,7 @@ namespace ts { } if (relation !== comparableRelation && - !(source.flags & TypeFlags.UnionOrIntersection) && + !(source.flags & TypeFlags.Union) && !(target.flags & TypeFlags.Union) && !isIntersectionConstituent && source !== globalObjectType && diff --git a/tests/baselines/reference/intersectionAsWeakTypeSource.errors.txt b/tests/baselines/reference/intersectionAsWeakTypeSource.errors.txt new file mode 100644 index 0000000000..69da07115a --- /dev/null +++ b/tests/baselines/reference/intersectionAsWeakTypeSource.errors.txt @@ -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 = number & { __brand: T } + declare function create(styles: T): { [P in keyof T]: Brand }; + 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; }; }'. + \ No newline at end of file diff --git a/tests/baselines/reference/intersectionAsWeakTypeSource.js b/tests/baselines/reference/intersectionAsWeakTypeSource.js new file mode 100644 index 0000000000..fefb6f79c2 --- /dev/null +++ b/tests/baselines/reference/intersectionAsWeakTypeSource.js @@ -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 = number & { __brand: T } +declare function create(styles: T): { [P in keyof T]: Brand }; +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 diff --git a/tests/baselines/reference/intersectionAsWeakTypeSource.symbols b/tests/baselines/reference/intersectionAsWeakTypeSource.symbols new file mode 100644 index 0000000000..78c35abc28 --- /dev/null +++ b/tests/baselines/reference/intersectionAsWeakTypeSource.symbols @@ -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 = 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(styles: T): { [P in keyof T]: Brand }; +>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)) + diff --git a/tests/baselines/reference/intersectionAsWeakTypeSource.types b/tests/baselines/reference/intersectionAsWeakTypeSource.types new file mode 100644 index 0000000000..2362f7c92c --- /dev/null +++ b/tests/baselines/reference/intersectionAsWeakTypeSource.types @@ -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 = number & { __brand: T } +>Brand : Brand +>__brand : T + +declare function create(styles: T): { [P in keyof T]: Brand }; +>create : (styles: T) => { [P in keyof T]: Brand; } +>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 : (styles: T) => { [P in keyof T]: Brand; } +>{ 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; }> + diff --git a/tests/cases/conformance/types/intersection/intersectionAsWeakTypeSource.ts b/tests/cases/conformance/types/intersection/intersectionAsWeakTypeSource.ts new file mode 100644 index 0000000000..1ebe78b05f --- /dev/null +++ b/tests/cases/conformance/types/intersection/intersectionAsWeakTypeSource.ts @@ -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 = number & { __brand: T } +declare function create(styles: T): { [P in keyof T]: Brand }; +const wrapped = create({ first: { view: 0, styleMedia: "???" } }); +const vs: ViewStyle = wrapped.first // error, first is a branded number