diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fb06920e5127e..96f85d6035039 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12387,6 +12387,11 @@ namespace ts { return links.resolvedType; } + function getAssignableIntersectionType(type1: Type, type2: Type) { + const type = getIntersectionType([type1, type2]); + return isTypeAssignableTo(type, type1) && isTypeAssignableTo(type, type2) ? type : neverType; + } + function createIndexType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { const result = createType(TypeFlags.Index); result.type = type; @@ -20678,7 +20683,7 @@ namespace ts { return isTypeSubtypeOf(candidate, type) ? candidate : isTypeAssignableTo(type, candidate) ? type : isTypeAssignableTo(candidate, type) ? candidate : - getIntersectionType([type, candidate]); + getAssignableIntersectionType(type, candidate); } function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { diff --git a/tests/baselines/reference/intersectionNotAssignableToConstituents.errors.txt b/tests/baselines/reference/intersectionNotAssignableToConstituents.errors.txt new file mode 100644 index 0000000000000..441894c990126 --- /dev/null +++ b/tests/baselines/reference/intersectionNotAssignableToConstituents.errors.txt @@ -0,0 +1,83 @@ +tests/cases/compiler/intersectionNotAssignableToConstituents.ts(15,3): error TS2322: Type 'B' is not assignable to type 'A'. + Types have separate declarations of a private property 'x'. +tests/cases/compiler/intersectionNotAssignableToConstituents.ts(16,3): error TS2322: Type 'A' is not assignable to type 'B'. + Types have separate declarations of a private property 'x'. +tests/cases/compiler/intersectionNotAssignableToConstituents.ts(17,3): error TS2322: Type 'A & B' is not assignable to type 'A'. + Property 'x' has conflicting declarations and is inaccessible in type 'A & B'. +tests/cases/compiler/intersectionNotAssignableToConstituents.ts(18,3): error TS2322: Type 'A & B' is not assignable to type 'B'. + Property 'x' has conflicting declarations and is inaccessible in type 'A & B'. + + +==== tests/cases/compiler/intersectionNotAssignableToConstituents.ts (4 errors) ==== + class A { private x: unknown } + class B { private x: unknown } + + function f1(node: A | B) { + if (node instanceof A || node instanceof A) { + node; // A + } + else { + node; // B + } + node; // A | B + } + + function f2(a: A, b: B, c: A & B) { + a = b; // Error + ~ +!!! error TS2322: Type 'B' is not assignable to type 'A'. +!!! error TS2322: Types have separate declarations of a private property 'x'. + b = a; // Error + ~ +!!! error TS2322: Type 'A' is not assignable to type 'B'. +!!! error TS2322: Types have separate declarations of a private property 'x'. + a = c; // Error (conflicting private fields) + ~ +!!! error TS2322: Type 'A & B' is not assignable to type 'A'. +!!! error TS2322: Property 'x' has conflicting declarations and is inaccessible in type 'A & B'. + b = c; // Error (conflicting private fields) + ~ +!!! error TS2322: Type 'A & B' is not assignable to type 'B'. +!!! error TS2322: Property 'x' has conflicting declarations and is inaccessible in type 'A & B'. + } + + // Repro from #37659 + + abstract class ViewNode { } + abstract class ViewRefNode extends ViewNode { } + abstract class ViewRefFileNode extends ViewRefNode { } + + class CommitFileNode extends ViewRefFileNode { + private _id: any; + } + + class ResultsFileNode extends ViewRefFileNode { + private _id: any; + } + + class StashFileNode extends CommitFileNode { + private _id2: any; + } + + class StatusFileNode extends ViewNode { + private _id: any; + } + + class Foo { + private async foo(node: CommitFileNode | ResultsFileNode | StashFileNode) { + if ( + !(node instanceof CommitFileNode) && + !(node instanceof StashFileNode) && + !(node instanceof ResultsFileNode) + ) { + return; + } + + await this.bar(node); + } + + private async bar(node: CommitFileNode | ResultsFileNode | StashFileNode | StatusFileNode, options?: {}) { + return Promise.resolve(undefined); + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/intersectionNotAssignableToConstituents.js b/tests/baselines/reference/intersectionNotAssignableToConstituents.js new file mode 100644 index 0000000000000..4f9817aed5a96 --- /dev/null +++ b/tests/baselines/reference/intersectionNotAssignableToConstituents.js @@ -0,0 +1,111 @@ +//// [intersectionNotAssignableToConstituents.ts] +class A { private x: unknown } +class B { private x: unknown } + +function f1(node: A | B) { + if (node instanceof A || node instanceof A) { + node; // A + } + else { + node; // B + } + node; // A | B +} + +function f2(a: A, b: B, c: A & B) { + a = b; // Error + b = a; // Error + a = c; // Error (conflicting private fields) + b = c; // Error (conflicting private fields) +} + +// Repro from #37659 + +abstract class ViewNode { } +abstract class ViewRefNode extends ViewNode { } +abstract class ViewRefFileNode extends ViewRefNode { } + +class CommitFileNode extends ViewRefFileNode { + private _id: any; +} + +class ResultsFileNode extends ViewRefFileNode { + private _id: any; +} + +class StashFileNode extends CommitFileNode { + private _id2: any; +} + +class StatusFileNode extends ViewNode { + private _id: any; +} + +class Foo { + private async foo(node: CommitFileNode | ResultsFileNode | StashFileNode) { + if ( + !(node instanceof CommitFileNode) && + !(node instanceof StashFileNode) && + !(node instanceof ResultsFileNode) + ) { + return; + } + + await this.bar(node); + } + + private async bar(node: CommitFileNode | ResultsFileNode | StashFileNode | StatusFileNode, options?: {}) { + return Promise.resolve(undefined); + } +} + + +//// [intersectionNotAssignableToConstituents.js] +"use strict"; +class A { +} +class B { +} +function f1(node) { + if (node instanceof A || node instanceof A) { + node; // A + } + else { + node; // B + } + node; // A | B +} +function f2(a, b, c) { + a = b; // Error + b = a; // Error + a = c; // Error (conflicting private fields) + b = c; // Error (conflicting private fields) +} +// Repro from #37659 +class ViewNode { +} +class ViewRefNode extends ViewNode { +} +class ViewRefFileNode extends ViewRefNode { +} +class CommitFileNode extends ViewRefFileNode { +} +class ResultsFileNode extends ViewRefFileNode { +} +class StashFileNode extends CommitFileNode { +} +class StatusFileNode extends ViewNode { +} +class Foo { + async foo(node) { + if (!(node instanceof CommitFileNode) && + !(node instanceof StashFileNode) && + !(node instanceof ResultsFileNode)) { + return; + } + await this.bar(node); + } + async bar(node, options) { + return Promise.resolve(undefined); + } +} diff --git a/tests/baselines/reference/intersectionNotAssignableToConstituents.symbols b/tests/baselines/reference/intersectionNotAssignableToConstituents.symbols new file mode 100644 index 0000000000000..0af387d928273 --- /dev/null +++ b/tests/baselines/reference/intersectionNotAssignableToConstituents.symbols @@ -0,0 +1,155 @@ +=== tests/cases/compiler/intersectionNotAssignableToConstituents.ts === +class A { private x: unknown } +>A : Symbol(A, Decl(intersectionNotAssignableToConstituents.ts, 0, 0)) +>x : Symbol(A.x, Decl(intersectionNotAssignableToConstituents.ts, 0, 9)) + +class B { private x: unknown } +>B : Symbol(B, Decl(intersectionNotAssignableToConstituents.ts, 0, 30)) +>x : Symbol(B.x, Decl(intersectionNotAssignableToConstituents.ts, 1, 9)) + +function f1(node: A | B) { +>f1 : Symbol(f1, Decl(intersectionNotAssignableToConstituents.ts, 1, 30)) +>node : Symbol(node, Decl(intersectionNotAssignableToConstituents.ts, 3, 12)) +>A : Symbol(A, Decl(intersectionNotAssignableToConstituents.ts, 0, 0)) +>B : Symbol(B, Decl(intersectionNotAssignableToConstituents.ts, 0, 30)) + + if (node instanceof A || node instanceof A) { +>node : Symbol(node, Decl(intersectionNotAssignableToConstituents.ts, 3, 12)) +>A : Symbol(A, Decl(intersectionNotAssignableToConstituents.ts, 0, 0)) +>node : Symbol(node, Decl(intersectionNotAssignableToConstituents.ts, 3, 12)) +>A : Symbol(A, Decl(intersectionNotAssignableToConstituents.ts, 0, 0)) + + node; // A +>node : Symbol(node, Decl(intersectionNotAssignableToConstituents.ts, 3, 12)) + } + else { + node; // B +>node : Symbol(node, Decl(intersectionNotAssignableToConstituents.ts, 3, 12)) + } + node; // A | B +>node : Symbol(node, Decl(intersectionNotAssignableToConstituents.ts, 3, 12)) +} + +function f2(a: A, b: B, c: A & B) { +>f2 : Symbol(f2, Decl(intersectionNotAssignableToConstituents.ts, 11, 1)) +>a : Symbol(a, Decl(intersectionNotAssignableToConstituents.ts, 13, 12)) +>A : Symbol(A, Decl(intersectionNotAssignableToConstituents.ts, 0, 0)) +>b : Symbol(b, Decl(intersectionNotAssignableToConstituents.ts, 13, 17)) +>B : Symbol(B, Decl(intersectionNotAssignableToConstituents.ts, 0, 30)) +>c : Symbol(c, Decl(intersectionNotAssignableToConstituents.ts, 13, 23)) +>A : Symbol(A, Decl(intersectionNotAssignableToConstituents.ts, 0, 0)) +>B : Symbol(B, Decl(intersectionNotAssignableToConstituents.ts, 0, 30)) + + a = b; // Error +>a : Symbol(a, Decl(intersectionNotAssignableToConstituents.ts, 13, 12)) +>b : Symbol(b, Decl(intersectionNotAssignableToConstituents.ts, 13, 17)) + + b = a; // Error +>b : Symbol(b, Decl(intersectionNotAssignableToConstituents.ts, 13, 17)) +>a : Symbol(a, Decl(intersectionNotAssignableToConstituents.ts, 13, 12)) + + a = c; // Error (conflicting private fields) +>a : Symbol(a, Decl(intersectionNotAssignableToConstituents.ts, 13, 12)) +>c : Symbol(c, Decl(intersectionNotAssignableToConstituents.ts, 13, 23)) + + b = c; // Error (conflicting private fields) +>b : Symbol(b, Decl(intersectionNotAssignableToConstituents.ts, 13, 17)) +>c : Symbol(c, Decl(intersectionNotAssignableToConstituents.ts, 13, 23)) +} + +// Repro from #37659 + +abstract class ViewNode { } +>ViewNode : Symbol(ViewNode, Decl(intersectionNotAssignableToConstituents.ts, 18, 1)) + +abstract class ViewRefNode extends ViewNode { } +>ViewRefNode : Symbol(ViewRefNode, Decl(intersectionNotAssignableToConstituents.ts, 22, 27)) +>ViewNode : Symbol(ViewNode, Decl(intersectionNotAssignableToConstituents.ts, 18, 1)) + +abstract class ViewRefFileNode extends ViewRefNode { } +>ViewRefFileNode : Symbol(ViewRefFileNode, Decl(intersectionNotAssignableToConstituents.ts, 23, 47)) +>ViewRefNode : Symbol(ViewRefNode, Decl(intersectionNotAssignableToConstituents.ts, 22, 27)) + +class CommitFileNode extends ViewRefFileNode { +>CommitFileNode : Symbol(CommitFileNode, Decl(intersectionNotAssignableToConstituents.ts, 24, 54)) +>ViewRefFileNode : Symbol(ViewRefFileNode, Decl(intersectionNotAssignableToConstituents.ts, 23, 47)) + + private _id: any; +>_id : Symbol(CommitFileNode._id, Decl(intersectionNotAssignableToConstituents.ts, 26, 46)) +} + +class ResultsFileNode extends ViewRefFileNode { +>ResultsFileNode : Symbol(ResultsFileNode, Decl(intersectionNotAssignableToConstituents.ts, 28, 1)) +>ViewRefFileNode : Symbol(ViewRefFileNode, Decl(intersectionNotAssignableToConstituents.ts, 23, 47)) + + private _id: any; +>_id : Symbol(ResultsFileNode._id, Decl(intersectionNotAssignableToConstituents.ts, 30, 47)) +} + +class StashFileNode extends CommitFileNode { +>StashFileNode : Symbol(StashFileNode, Decl(intersectionNotAssignableToConstituents.ts, 32, 1)) +>CommitFileNode : Symbol(CommitFileNode, Decl(intersectionNotAssignableToConstituents.ts, 24, 54)) + + private _id2: any; +>_id2 : Symbol(StashFileNode._id2, Decl(intersectionNotAssignableToConstituents.ts, 34, 44)) +} + +class StatusFileNode extends ViewNode { +>StatusFileNode : Symbol(StatusFileNode, Decl(intersectionNotAssignableToConstituents.ts, 36, 1)) +>ViewNode : Symbol(ViewNode, Decl(intersectionNotAssignableToConstituents.ts, 18, 1)) + + private _id: any; +>_id : Symbol(StatusFileNode._id, Decl(intersectionNotAssignableToConstituents.ts, 38, 39)) +} + +class Foo { +>Foo : Symbol(Foo, Decl(intersectionNotAssignableToConstituents.ts, 40, 1)) + + private async foo(node: CommitFileNode | ResultsFileNode | StashFileNode) { +>foo : Symbol(Foo.foo, Decl(intersectionNotAssignableToConstituents.ts, 42, 11)) +>node : Symbol(node, Decl(intersectionNotAssignableToConstituents.ts, 43, 20)) +>CommitFileNode : Symbol(CommitFileNode, Decl(intersectionNotAssignableToConstituents.ts, 24, 54)) +>ResultsFileNode : Symbol(ResultsFileNode, Decl(intersectionNotAssignableToConstituents.ts, 28, 1)) +>StashFileNode : Symbol(StashFileNode, Decl(intersectionNotAssignableToConstituents.ts, 32, 1)) + + if ( + !(node instanceof CommitFileNode) && +>node : Symbol(node, Decl(intersectionNotAssignableToConstituents.ts, 43, 20)) +>CommitFileNode : Symbol(CommitFileNode, Decl(intersectionNotAssignableToConstituents.ts, 24, 54)) + + !(node instanceof StashFileNode) && +>node : Symbol(node, Decl(intersectionNotAssignableToConstituents.ts, 43, 20)) +>StashFileNode : Symbol(StashFileNode, Decl(intersectionNotAssignableToConstituents.ts, 32, 1)) + + !(node instanceof ResultsFileNode) +>node : Symbol(node, Decl(intersectionNotAssignableToConstituents.ts, 43, 20)) +>ResultsFileNode : Symbol(ResultsFileNode, Decl(intersectionNotAssignableToConstituents.ts, 28, 1)) + + ) { + return; + } + + await this.bar(node); +>this.bar : Symbol(Foo.bar, Decl(intersectionNotAssignableToConstituents.ts, 53, 2)) +>this : Symbol(Foo, Decl(intersectionNotAssignableToConstituents.ts, 40, 1)) +>bar : Symbol(Foo.bar, Decl(intersectionNotAssignableToConstituents.ts, 53, 2)) +>node : Symbol(node, Decl(intersectionNotAssignableToConstituents.ts, 43, 20)) + } + + private async bar(node: CommitFileNode | ResultsFileNode | StashFileNode | StatusFileNode, options?: {}) { +>bar : Symbol(Foo.bar, Decl(intersectionNotAssignableToConstituents.ts, 53, 2)) +>node : Symbol(node, Decl(intersectionNotAssignableToConstituents.ts, 55, 20)) +>CommitFileNode : Symbol(CommitFileNode, Decl(intersectionNotAssignableToConstituents.ts, 24, 54)) +>ResultsFileNode : Symbol(ResultsFileNode, Decl(intersectionNotAssignableToConstituents.ts, 28, 1)) +>StashFileNode : Symbol(StashFileNode, Decl(intersectionNotAssignableToConstituents.ts, 32, 1)) +>StatusFileNode : Symbol(StatusFileNode, Decl(intersectionNotAssignableToConstituents.ts, 36, 1)) +>options : Symbol(options, Decl(intersectionNotAssignableToConstituents.ts, 55, 92)) + + return Promise.resolve(undefined); +>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) +>undefined : Symbol(undefined) + } +} + diff --git a/tests/baselines/reference/intersectionNotAssignableToConstituents.types b/tests/baselines/reference/intersectionNotAssignableToConstituents.types new file mode 100644 index 0000000000000..ce0dafeefaf70 --- /dev/null +++ b/tests/baselines/reference/intersectionNotAssignableToConstituents.types @@ -0,0 +1,163 @@ +=== tests/cases/compiler/intersectionNotAssignableToConstituents.ts === +class A { private x: unknown } +>A : A +>x : unknown + +class B { private x: unknown } +>B : B +>x : unknown + +function f1(node: A | B) { +>f1 : (node: A | B) => void +>node : A | B + + if (node instanceof A || node instanceof A) { +>node instanceof A || node instanceof A : boolean +>node instanceof A : boolean +>node : A | B +>A : typeof A +>node instanceof A : boolean +>node : B +>A : typeof A + + node; // A +>node : A + } + else { + node; // B +>node : B + } + node; // A | B +>node : A | B +} + +function f2(a: A, b: B, c: A & B) { +>f2 : (a: A, b: B, c: A & B) => void +>a : A +>b : B +>c : A & B + + a = b; // Error +>a = b : B +>a : A +>b : B + + b = a; // Error +>b = a : A +>b : B +>a : A + + a = c; // Error (conflicting private fields) +>a = c : A & B +>a : A +>c : A & B + + b = c; // Error (conflicting private fields) +>b = c : A & B +>b : B +>c : A & B +} + +// Repro from #37659 + +abstract class ViewNode { } +>ViewNode : ViewNode + +abstract class ViewRefNode extends ViewNode { } +>ViewRefNode : ViewRefNode +>ViewNode : ViewNode + +abstract class ViewRefFileNode extends ViewRefNode { } +>ViewRefFileNode : ViewRefFileNode +>ViewRefNode : ViewRefNode + +class CommitFileNode extends ViewRefFileNode { +>CommitFileNode : CommitFileNode +>ViewRefFileNode : ViewRefFileNode + + private _id: any; +>_id : any +} + +class ResultsFileNode extends ViewRefFileNode { +>ResultsFileNode : ResultsFileNode +>ViewRefFileNode : ViewRefFileNode + + private _id: any; +>_id : any +} + +class StashFileNode extends CommitFileNode { +>StashFileNode : StashFileNode +>CommitFileNode : CommitFileNode + + private _id2: any; +>_id2 : any +} + +class StatusFileNode extends ViewNode { +>StatusFileNode : StatusFileNode +>ViewNode : ViewNode + + private _id: any; +>_id : any +} + +class Foo { +>Foo : Foo + + private async foo(node: CommitFileNode | ResultsFileNode | StashFileNode) { +>foo : (node: CommitFileNode | ResultsFileNode | StashFileNode) => Promise +>node : CommitFileNode | ResultsFileNode | StashFileNode + + if ( + !(node instanceof CommitFileNode) && +>!(node instanceof CommitFileNode) && !(node instanceof StashFileNode) && !(node instanceof ResultsFileNode) : boolean +>!(node instanceof CommitFileNode) && !(node instanceof StashFileNode) : boolean +>!(node instanceof CommitFileNode) : boolean +>(node instanceof CommitFileNode) : boolean +>node instanceof CommitFileNode : boolean +>node : CommitFileNode | ResultsFileNode | StashFileNode +>CommitFileNode : typeof CommitFileNode + + !(node instanceof StashFileNode) && +>!(node instanceof StashFileNode) : boolean +>(node instanceof StashFileNode) : boolean +>node instanceof StashFileNode : boolean +>node : ResultsFileNode +>StashFileNode : typeof StashFileNode + + !(node instanceof ResultsFileNode) +>!(node instanceof ResultsFileNode) : boolean +>(node instanceof ResultsFileNode) : boolean +>node instanceof ResultsFileNode : boolean +>node : ResultsFileNode +>ResultsFileNode : typeof ResultsFileNode + + ) { + return; + } + + await this.bar(node); +>await this.bar(node) : undefined +>this.bar(node) : Promise +>this.bar : (node: CommitFileNode | ResultsFileNode | StashFileNode | StatusFileNode, options?: {} | undefined) => Promise +>this : this +>bar : (node: CommitFileNode | ResultsFileNode | StashFileNode | StatusFileNode, options?: {} | undefined) => Promise +>node : CommitFileNode | ResultsFileNode + } + + private async bar(node: CommitFileNode | ResultsFileNode | StashFileNode | StatusFileNode, options?: {}) { +>bar : (node: CommitFileNode | ResultsFileNode | StashFileNode | StatusFileNode, options?: {} | undefined) => Promise +>node : CommitFileNode | ResultsFileNode | StashFileNode | StatusFileNode +>options : {} | undefined + + return Promise.resolve(undefined); +>Promise.resolve(undefined) : Promise +>Promise.resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>Promise : PromiseConstructor +>resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>undefined : undefined + } +} + diff --git a/tests/cases/compiler/intersectionNotAssignableToConstituents.ts b/tests/cases/compiler/intersectionNotAssignableToConstituents.ts new file mode 100644 index 0000000000000..5d7708e67d69d --- /dev/null +++ b/tests/cases/compiler/intersectionNotAssignableToConstituents.ts @@ -0,0 +1,62 @@ +// @strict: true +// @target: esnext + +class A { private x: unknown } +class B { private x: unknown } + +function f1(node: A | B) { + if (node instanceof A || node instanceof A) { + node; // A + } + else { + node; // B + } + node; // A | B +} + +function f2(a: A, b: B, c: A & B) { + a = b; // Error + b = a; // Error + a = c; // Error (conflicting private fields) + b = c; // Error (conflicting private fields) +} + +// Repro from #37659 + +abstract class ViewNode { } +abstract class ViewRefNode extends ViewNode { } +abstract class ViewRefFileNode extends ViewRefNode { } + +class CommitFileNode extends ViewRefFileNode { + private _id: any; +} + +class ResultsFileNode extends ViewRefFileNode { + private _id: any; +} + +class StashFileNode extends CommitFileNode { + private _id2: any; +} + +class StatusFileNode extends ViewNode { + private _id: any; +} + +class Foo { + private async foo(node: CommitFileNode | ResultsFileNode | StashFileNode) { + if ( + !(node instanceof CommitFileNode) && + !(node instanceof StashFileNode) && + !(node instanceof ResultsFileNode) + ) { + return; + } + + await this.bar(node); + } + + private async bar(node: CommitFileNode | ResultsFileNode | StashFileNode | StatusFileNode, options?: {}) { + return Promise.resolve(undefined); + } +}