From efea81b8a038089c28a03dd03225e1a2b309a9f1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 5 Mar 2017 14:07:33 -0800 Subject: [PATCH 1/4] Add failing repro --- .../controlFlow/typeGuardsTypeParameters.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts diff --git a/tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts b/tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts new file mode 100644 index 0000000000..169dbc7a7c --- /dev/null +++ b/tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts @@ -0,0 +1,35 @@ +// @strictNullChecks: true + +// Type guards involving type parameters produce intersection types + +class C { + prop: string; +} + +function f1(x: T) { + if (x instanceof C) { + let v1: T = x; + let v2: C = x; + x.prop; + } +} + +function f2(x: T) { + if (typeof x === "string") { + let v1: T = x; + let v2: string = x; + x.length; + } +} + +// Repro from #13872 + +function fun(item: { [P in keyof T]: T[P] }) { + const strings: string[] = []; + for (const key in item) { + const value = item[key]; + if (typeof value === "string") { + strings.push(value); + } + } +} From 4c71a7c084744cb7d29011c3757822242c64db18 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 5 Mar 2017 14:12:13 -0800 Subject: [PATCH 2/4] Accept error baselines --- .../typeGuardsTypeParameters.errors.txt | 47 +++++++++++++ .../reference/typeGuardsTypeParameters.js | 68 +++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 tests/baselines/reference/typeGuardsTypeParameters.errors.txt create mode 100644 tests/baselines/reference/typeGuardsTypeParameters.js diff --git a/tests/baselines/reference/typeGuardsTypeParameters.errors.txt b/tests/baselines/reference/typeGuardsTypeParameters.errors.txt new file mode 100644 index 0000000000..46eb198f42 --- /dev/null +++ b/tests/baselines/reference/typeGuardsTypeParameters.errors.txt @@ -0,0 +1,47 @@ +tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts(10,13): error TS2322: Type 'C' is not assignable to type 'T'. +tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts(20,11): error TS2339: Property 'length' does not exist on type 'never'. +tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts(31,26): error TS2345: Argument of type 'T[keyof T]' is not assignable to parameter of type 'string'. + + +==== tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts (3 errors) ==== + + // Type guards involving type parameters produce intersection types + + class C { + prop: string; + } + + function f1(x: T) { + if (x instanceof C) { + let v1: T = x; + ~~ +!!! error TS2322: Type 'C' is not assignable to type 'T'. + let v2: C = x; + x.prop; + } + } + + function f2(x: T) { + if (typeof x === "string") { + let v1: T = x; + let v2: string = x; + x.length; + ~~~~~~ +!!! error TS2339: Property 'length' does not exist on type 'never'. + } + } + + // Repro from #13872 + + function fun(item: { [P in keyof T]: T[P] }) { + const strings: string[] = []; + for (const key in item) { + const value = item[key]; + if (typeof value === "string") { + strings.push(value); + ~~~~~ +!!! error TS2345: Argument of type 'T[keyof T]' is not assignable to parameter of type 'string'. + } + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardsTypeParameters.js b/tests/baselines/reference/typeGuardsTypeParameters.js new file mode 100644 index 0000000000..bf0b1d2bac --- /dev/null +++ b/tests/baselines/reference/typeGuardsTypeParameters.js @@ -0,0 +1,68 @@ +//// [typeGuardsTypeParameters.ts] + +// Type guards involving type parameters produce intersection types + +class C { + prop: string; +} + +function f1(x: T) { + if (x instanceof C) { + let v1: T = x; + let v2: C = x; + x.prop; + } +} + +function f2(x: T) { + if (typeof x === "string") { + let v1: T = x; + let v2: string = x; + x.length; + } +} + +// Repro from #13872 + +function fun(item: { [P in keyof T]: T[P] }) { + const strings: string[] = []; + for (const key in item) { + const value = item[key]; + if (typeof value === "string") { + strings.push(value); + } + } +} + + +//// [typeGuardsTypeParameters.js] +// Type guards involving type parameters produce intersection types +var C = (function () { + function C() { + } + return C; +}()); +function f1(x) { + if (x instanceof C) { + var v1 = x; + var v2 = x; + x.prop; + } +} +function f2(x) { + if (typeof x === "string") { + var v1 = x; + var v2 = x; + x.length; + } +} +// Repro from #13872 +function fun(item) { + var strings = []; + for (var key in item) { + var value = item[key]; + if (typeof value === "string") { + strings.push(value); + } + } +} From 91b658da36e1b9a4eb6f19aec6d88f69cbc193c6 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 5 Mar 2017 14:12:55 -0800 Subject: [PATCH 3/4] Construct intersection types for type guards involving type parameters --- src/compiler/checker.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a4c0acc405..5685e54542 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9843,9 +9843,8 @@ namespace ts { if (flags & TypeFlags.NonPrimitive) { return strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; } - if (flags & TypeFlags.TypeParameter) { - const constraint = getConstraintOfTypeParameter(type); - return getTypeFacts(constraint || emptyObjectType); + if (flags & TypeFlags.TypeVariable) { + return getTypeFacts(getBaseConstraintOfType(type) || emptyObjectType); } if (flags & TypeFlags.UnionOrIntersection) { return getTypeFactsOfTypes((type).types); @@ -10632,8 +10631,16 @@ namespace ts { // is a supertype of that primitive type. For example, type 'any' can be narrowed // to one of the primitive types. const targetType = typeofTypesByName.get(literal.text); - if (targetType && isTypeSubtypeOf(targetType, type)) { - return targetType; + if (targetType) { + if (isTypeSubtypeOf(targetType, type)) { + return targetType; + } + if (type.flags & TypeFlags.TypeVariable) { + const constraint = getBaseConstraintOfType(type) || anyType; + if (isTypeSubtypeOf(targetType, constraint)) { + return getIntersectionType([type, targetType]); + } + } } } const facts = assumeTrue ? @@ -10731,10 +10738,9 @@ namespace ts { // Otherwise, if the candidate type is assignable to the target type, narrow to the candidate // type. Otherwise, the types are completely unrelated, so narrow to an intersection of the // two types. - const targetType = type.flags & TypeFlags.TypeParameter ? getApparentType(type) : type; return isTypeSubtypeOf(candidate, type) ? candidate : isTypeAssignableTo(type, candidate) ? type : - isTypeAssignableTo(candidate, targetType) ? candidate : + isTypeAssignableTo(candidate, type) ? candidate : getIntersectionType([type, candidate]); } From af686adf1f64fb31bd6f212777e6ee4f8ff2402e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 5 Mar 2017 14:19:20 -0800 Subject: [PATCH 4/4] Accept new baselines --- .../typeGuardsTypeParameters.errors.txt | 47 -------- .../typeGuardsTypeParameters.symbols | 98 ++++++++++++++++ .../reference/typeGuardsTypeParameters.types | 108 ++++++++++++++++++ 3 files changed, 206 insertions(+), 47 deletions(-) delete mode 100644 tests/baselines/reference/typeGuardsTypeParameters.errors.txt create mode 100644 tests/baselines/reference/typeGuardsTypeParameters.symbols create mode 100644 tests/baselines/reference/typeGuardsTypeParameters.types diff --git a/tests/baselines/reference/typeGuardsTypeParameters.errors.txt b/tests/baselines/reference/typeGuardsTypeParameters.errors.txt deleted file mode 100644 index 46eb198f42..0000000000 --- a/tests/baselines/reference/typeGuardsTypeParameters.errors.txt +++ /dev/null @@ -1,47 +0,0 @@ -tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts(10,13): error TS2322: Type 'C' is not assignable to type 'T'. -tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts(20,11): error TS2339: Property 'length' does not exist on type 'never'. -tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts(31,26): error TS2345: Argument of type 'T[keyof T]' is not assignable to parameter of type 'string'. - - -==== tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts (3 errors) ==== - - // Type guards involving type parameters produce intersection types - - class C { - prop: string; - } - - function f1(x: T) { - if (x instanceof C) { - let v1: T = x; - ~~ -!!! error TS2322: Type 'C' is not assignable to type 'T'. - let v2: C = x; - x.prop; - } - } - - function f2(x: T) { - if (typeof x === "string") { - let v1: T = x; - let v2: string = x; - x.length; - ~~~~~~ -!!! error TS2339: Property 'length' does not exist on type 'never'. - } - } - - // Repro from #13872 - - function fun(item: { [P in keyof T]: T[P] }) { - const strings: string[] = []; - for (const key in item) { - const value = item[key]; - if (typeof value === "string") { - strings.push(value); - ~~~~~ -!!! error TS2345: Argument of type 'T[keyof T]' is not assignable to parameter of type 'string'. - } - } - } - \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardsTypeParameters.symbols b/tests/baselines/reference/typeGuardsTypeParameters.symbols new file mode 100644 index 0000000000..53e6086c0f --- /dev/null +++ b/tests/baselines/reference/typeGuardsTypeParameters.symbols @@ -0,0 +1,98 @@ +=== tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts === + +// Type guards involving type parameters produce intersection types + +class C { +>C : Symbol(C, Decl(typeGuardsTypeParameters.ts, 0, 0)) + + prop: string; +>prop : Symbol(C.prop, Decl(typeGuardsTypeParameters.ts, 3, 9)) +} + +function f1(x: T) { +>f1 : Symbol(f1, Decl(typeGuardsTypeParameters.ts, 5, 1)) +>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 7, 12)) +>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 7, 15)) +>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 7, 12)) + + if (x instanceof C) { +>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 7, 15)) +>C : Symbol(C, Decl(typeGuardsTypeParameters.ts, 0, 0)) + + let v1: T = x; +>v1 : Symbol(v1, Decl(typeGuardsTypeParameters.ts, 9, 11)) +>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 7, 12)) +>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 7, 15)) + + let v2: C = x; +>v2 : Symbol(v2, Decl(typeGuardsTypeParameters.ts, 10, 11)) +>C : Symbol(C, Decl(typeGuardsTypeParameters.ts, 0, 0)) +>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 7, 15)) + + x.prop; +>x.prop : Symbol(C.prop, Decl(typeGuardsTypeParameters.ts, 3, 9)) +>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 7, 15)) +>prop : Symbol(C.prop, Decl(typeGuardsTypeParameters.ts, 3, 9)) + } +} + +function f2(x: T) { +>f2 : Symbol(f2, Decl(typeGuardsTypeParameters.ts, 13, 1)) +>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 15, 12)) +>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 15, 15)) +>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 15, 12)) + + if (typeof x === "string") { +>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 15, 15)) + + let v1: T = x; +>v1 : Symbol(v1, Decl(typeGuardsTypeParameters.ts, 17, 11)) +>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 15, 12)) +>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 15, 15)) + + let v2: string = x; +>v2 : Symbol(v2, Decl(typeGuardsTypeParameters.ts, 18, 11)) +>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 15, 15)) + + x.length; +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardsTypeParameters.ts, 15, 15)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) + } +} + +// Repro from #13872 + +function fun(item: { [P in keyof T]: T[P] }) { +>fun : Symbol(fun, Decl(typeGuardsTypeParameters.ts, 21, 1)) +>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 25, 13)) +>item : Symbol(item, Decl(typeGuardsTypeParameters.ts, 25, 16)) +>P : Symbol(P, Decl(typeGuardsTypeParameters.ts, 25, 25)) +>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 25, 13)) +>T : Symbol(T, Decl(typeGuardsTypeParameters.ts, 25, 13)) +>P : Symbol(P, Decl(typeGuardsTypeParameters.ts, 25, 25)) + + const strings: string[] = []; +>strings : Symbol(strings, Decl(typeGuardsTypeParameters.ts, 26, 9)) + + for (const key in item) { +>key : Symbol(key, Decl(typeGuardsTypeParameters.ts, 27, 14)) +>item : Symbol(item, Decl(typeGuardsTypeParameters.ts, 25, 16)) + + const value = item[key]; +>value : Symbol(value, Decl(typeGuardsTypeParameters.ts, 28, 13)) +>item : Symbol(item, Decl(typeGuardsTypeParameters.ts, 25, 16)) +>key : Symbol(key, Decl(typeGuardsTypeParameters.ts, 27, 14)) + + if (typeof value === "string") { +>value : Symbol(value, Decl(typeGuardsTypeParameters.ts, 28, 13)) + + strings.push(value); +>strings.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>strings : Symbol(strings, Decl(typeGuardsTypeParameters.ts, 26, 9)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>value : Symbol(value, Decl(typeGuardsTypeParameters.ts, 28, 13)) + } + } +} + diff --git a/tests/baselines/reference/typeGuardsTypeParameters.types b/tests/baselines/reference/typeGuardsTypeParameters.types new file mode 100644 index 0000000000..6825402cdc --- /dev/null +++ b/tests/baselines/reference/typeGuardsTypeParameters.types @@ -0,0 +1,108 @@ +=== tests/cases/conformance/controlFlow/typeGuardsTypeParameters.ts === + +// Type guards involving type parameters produce intersection types + +class C { +>C : C + + prop: string; +>prop : string +} + +function f1(x: T) { +>f1 : (x: T) => void +>T : T +>x : T +>T : T + + if (x instanceof C) { +>x instanceof C : boolean +>x : T +>C : typeof C + + let v1: T = x; +>v1 : T +>T : T +>x : T & C + + let v2: C = x; +>v2 : C +>C : C +>x : T & C + + x.prop; +>x.prop : string +>x : T & C +>prop : string + } +} + +function f2(x: T) { +>f2 : (x: T) => void +>T : T +>x : T +>T : T + + if (typeof x === "string") { +>typeof x === "string" : boolean +>typeof x : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : T +>"string" : "string" + + let v1: T = x; +>v1 : T +>T : T +>x : T & string + + let v2: string = x; +>v2 : string +>x : T & string + + x.length; +>x.length : number +>x : T & string +>length : number + } +} + +// Repro from #13872 + +function fun(item: { [P in keyof T]: T[P] }) { +>fun : (item: { [P in keyof T]: T[P]; }) => void +>T : T +>item : { [P in keyof T]: T[P]; } +>P : P +>T : T +>T : T +>P : P + + const strings: string[] = []; +>strings : string[] +>[] : never[] + + for (const key in item) { +>key : keyof T +>item : { [P in keyof T]: T[P]; } + + const value = item[key]; +>value : T[keyof T] +>item[key] : T[keyof T] +>item : { [P in keyof T]: T[P]; } +>key : keyof T + + if (typeof value === "string") { +>typeof value === "string" : boolean +>typeof value : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" +>value : T[keyof T] +>"string" : "string" + + strings.push(value); +>strings.push(value) : number +>strings.push : (...items: string[]) => number +>strings : string[] +>push : (...items: string[]) => number +>value : T[keyof T] & string + } + } +} +