From a2609b1f1b58f9b6ef62cb3b6ff47efb35059eee Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 6 Apr 2020 13:36:20 -0700 Subject: [PATCH 01/10] Extra check in assignment of intersections with generic constituents (#37537) * Consolidated extra property check with intersections * Fix comment * Add tests * Properly propagate intersectionState * Route property check through recursive type tracking logic * Accept new baselines * Skip check when apparent type of source is never * Accept new baselines * Only check when apparent type of source is a structured type --- src/compiler/checker.ts | 34 ++++++---- ...yofReliesOnKeyofNeverUpperBound.errors.txt | 62 +++++++++---------- .../intersectionPropertyCheck.errors.txt | 46 ++++++++++++++ .../reference/intersectionPropertyCheck.js | 42 +++++++++++++ .../intersectionPropertyCheck.symbols | 58 +++++++++++++++++ .../reference/intersectionPropertyCheck.types | 59 ++++++++++++++++++ tests/baselines/reference/weakType.errors.txt | 10 +-- .../compiler/intersectionPropertyCheck.ts | 20 ++++++ 8 files changed, 282 insertions(+), 49 deletions(-) create mode 100644 tests/baselines/reference/intersectionPropertyCheck.errors.txt create mode 100644 tests/baselines/reference/intersectionPropertyCheck.js create mode 100644 tests/baselines/reference/intersectionPropertyCheck.symbols create mode 100644 tests/baselines/reference/intersectionPropertyCheck.types create mode 100644 tests/cases/compiler/intersectionPropertyCheck.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ffec8d3cb16..feb27525052 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -195,7 +195,7 @@ namespace ts { None = 0, Source = 1 << 0, Target = 1 << 1, - ExcessCheck = 1 << 2, + PropertyCheck = 1 << 2, } const enum MappedTypeModifiers { @@ -15561,7 +15561,7 @@ namespace ts { if (source.flags & TypeFlags.Union) { result = relation === comparableRelation ? someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState) : - eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState & IntersectionState.ExcessCheck); + eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState); } else { if (target.flags & TypeFlags.Union) { @@ -15569,12 +15569,6 @@ namespace ts { } else if (target.flags & TypeFlags.Intersection) { result = typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors, IntersectionState.Target); - if (result && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks) && !(intersectionState & IntersectionState.ExcessCheck)) { - // Validate against excess props using the original `source` - if (!propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.ExcessCheck)) { - return Ternary.False; - } - } } else if (source.flags & TypeFlags.Intersection) { // Check to see if any constituents of the intersection are immediately related to the target. @@ -15590,9 +15584,7 @@ namespace ts { // // - For a primitive type or type parameter (such as 'number = A & B') there is no point in // breaking the intersection apart. - if (!isNonGenericObjectType(target) || !every((source).types, t => isNonGenericObjectType(t) && !(getObjectFlags(t) & ObjectFlags.NonInferrableType))) { - result = someTypeRelatedToType(source, target, /*reportErrors*/ false, IntersectionState.Source); - } + result = someTypeRelatedToType(source, target, /*reportErrors*/ false, IntersectionState.Source); } if (!result && (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable)) { if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState)) { @@ -15624,6 +15616,23 @@ 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: + // + // let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property + // + // declare let wrong: { a: { y: string } }; + // let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type + // + // function foo(x: { a?: string }, y: T & { a: boolean }) { + // x = y; // Mismatched property in source intersection + // } + if (result && ( + target.flags & TypeFlags.Intersection && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks) || + isNonGenericObjectType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) { + result &= recursiveTypeRelatedTo(source, target, reportErrors, IntersectionState.PropertyCheck); + } if (!result && reportErrors) { source = originalSource.aliasSymbol ? originalSource : source; @@ -16000,6 +16009,9 @@ namespace ts { } function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + if (intersectionState & IntersectionState.PropertyCheck) { + return propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None); + } const flags = source.flags & target.flags; if (relation === identityRelation && !(flags & TypeFlags.Object)) { if (flags & TypeFlags.Index) { diff --git a/tests/baselines/reference/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.errors.txt b/tests/baselines/reference/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.errors.txt index 9abaa6db803..af63c18523d 100644 --- a/tests/baselines/reference/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.errors.txt +++ b/tests/baselines/reference/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.errors.txt @@ -5,23 +5,20 @@ tests/cases/compiler/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.t Type '"text" | "email"' is not assignable to type 'ChannelOfType["type"] & ChannelOfType["type"]'. Type '"text"' is not assignable to type 'ChannelOfType["type"] & ChannelOfType["type"]'. Type '"text"' is not assignable to type 'ChannelOfType["type"]'. - Type '"text"' is not assignable to type 'T & "text"'. - Type '"text"' is not assignable to type 'T'. - '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'. - Type 'T' is not assignable to type 'ChannelOfType["type"]'. - Type '"text" | "email"' is not assignable to type 'ChannelOfType["type"]'. - Type '"text"' is not assignable to type 'ChannelOfType["type"]'. - Type '"text"' is not assignable to type 'T & "text"'. - Type '"text"' is not assignable to type 'T'. - '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'. - Type 'T' is not assignable to type 'T & "text"'. - Type '"text" | "email"' is not assignable to type 'T & "text"'. - Type '"text"' is not assignable to type 'T & "text"'. - Type '"text"' is not assignable to type 'T'. - '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'. - Type 'T' is not assignable to type '"text"'. - Type '"text" | "email"' is not assignable to type '"text"'. - Type '"email"' is not assignable to type '"text"'. + Type 'T' is not assignable to type 'ChannelOfType["type"]'. + Type '"text" | "email"' is not assignable to type 'ChannelOfType["type"]'. + Type '"text"' is not assignable to type 'ChannelOfType["type"]'. + Type '"text"' is not assignable to type 'T & "text"'. + Type '"text"' is not assignable to type 'T'. + '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'. + Type 'T' is not assignable to type 'T & "text"'. + Type '"text" | "email"' is not assignable to type 'T & "text"'. + Type '"text"' is not assignable to type 'T & "text"'. + Type '"text"' is not assignable to type 'T'. + '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'. + Type 'T' is not assignable to type '"text"'. + Type '"text" | "email"' is not assignable to type '"text"'. + Type '"email"' is not assignable to type '"text"'. ==== tests/cases/compiler/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.ts (1 errors) ==== @@ -66,23 +63,20 @@ tests/cases/compiler/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.t !!! error TS2322: Type '"text" | "email"' is not assignable to type 'ChannelOfType["type"] & ChannelOfType["type"]'. !!! error TS2322: Type '"text"' is not assignable to type 'ChannelOfType["type"] & ChannelOfType["type"]'. !!! error TS2322: Type '"text"' is not assignable to type 'ChannelOfType["type"]'. -!!! error TS2322: Type '"text"' is not assignable to type 'T & "text"'. -!!! error TS2322: Type '"text"' is not assignable to type 'T'. -!!! error TS2322: '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'. -!!! error TS2322: Type 'T' is not assignable to type 'ChannelOfType["type"]'. -!!! error TS2322: Type '"text" | "email"' is not assignable to type 'ChannelOfType["type"]'. -!!! error TS2322: Type '"text"' is not assignable to type 'ChannelOfType["type"]'. -!!! error TS2322: Type '"text"' is not assignable to type 'T & "text"'. -!!! error TS2322: Type '"text"' is not assignable to type 'T'. -!!! error TS2322: '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'. -!!! error TS2322: Type 'T' is not assignable to type 'T & "text"'. -!!! error TS2322: Type '"text" | "email"' is not assignable to type 'T & "text"'. -!!! error TS2322: Type '"text"' is not assignable to type 'T & "text"'. -!!! error TS2322: Type '"text"' is not assignable to type 'T'. -!!! error TS2322: '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'. -!!! error TS2322: Type 'T' is not assignable to type '"text"'. -!!! error TS2322: Type '"text" | "email"' is not assignable to type '"text"'. -!!! error TS2322: Type '"email"' is not assignable to type '"text"'. +!!! error TS2322: Type 'T' is not assignable to type 'ChannelOfType["type"]'. +!!! error TS2322: Type '"text" | "email"' is not assignable to type 'ChannelOfType["type"]'. +!!! error TS2322: Type '"text"' is not assignable to type 'ChannelOfType["type"]'. +!!! error TS2322: Type '"text"' is not assignable to type 'T & "text"'. +!!! error TS2322: Type '"text"' is not assignable to type 'T'. +!!! error TS2322: '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'. +!!! error TS2322: Type 'T' is not assignable to type 'T & "text"'. +!!! error TS2322: Type '"text" | "email"' is not assignable to type 'T & "text"'. +!!! error TS2322: Type '"text"' is not assignable to type 'T & "text"'. +!!! error TS2322: Type '"text"' is not assignable to type 'T'. +!!! error TS2322: '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'. +!!! error TS2322: Type 'T' is not assignable to type '"text"'. +!!! error TS2322: Type '"text" | "email"' is not assignable to type '"text"'. +!!! error TS2322: Type '"email"' is not assignable to type '"text"'. } const newTextChannel = makeNewChannel('text'); diff --git a/tests/baselines/reference/intersectionPropertyCheck.errors.txt b/tests/baselines/reference/intersectionPropertyCheck.errors.txt new file mode 100644 index 00000000000..7c74c3512ea --- /dev/null +++ b/tests/baselines/reference/intersectionPropertyCheck.errors.txt @@ -0,0 +1,46 @@ +tests/cases/compiler/intersectionPropertyCheck.ts(1,68): error TS2322: Type '{ x: string; y: number; }' is not assignable to type '{ x: string; }'. + Object literal may only specify known properties, and 'y' does not exist in type '{ x: string; }'. +tests/cases/compiler/intersectionPropertyCheck.ts(4,5): error TS2322: Type '{ a: { y: string; }; }' is not assignable to type '{ a?: { x?: number | undefined; } | undefined; } & { c?: string | undefined; }'. + Types of property 'a' are incompatible. + Type '{ y: string; }' has no properties in common with type '{ x?: number | undefined; }'. +tests/cases/compiler/intersectionPropertyCheck.ts(7,3): error TS2322: Type 'T & { a: boolean; }' is not assignable to type '{ a?: string | undefined; }'. + Types of property 'a' are incompatible. + Type 'boolean' is not assignable to type 'string | undefined'. +tests/cases/compiler/intersectionPropertyCheck.ts(17,22): error TS2322: Type 'true' is not assignable to type 'string[] | undefined'. + + +==== tests/cases/compiler/intersectionPropertyCheck.ts (4 errors) ==== + let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property + ~~~~ +!!! error TS2322: Type '{ x: string; y: number; }' is not assignable to type '{ x: string; }'. +!!! error TS2322: Object literal may only specify known properties, and 'y' does not exist in type '{ x: string; }'. +!!! related TS6500 tests/cases/compiler/intersectionPropertyCheck.ts:1:12: The expected type comes from property 'a' which is declared here on type '{ a: { x: string; }; } & { c: number; }' + + declare let wrong: { a: { y: string } }; + let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type + ~~~~ +!!! error TS2322: Type '{ a: { y: string; }; }' is not assignable to type '{ a?: { x?: number | undefined; } | undefined; } & { c?: string | undefined; }'. +!!! error TS2322: Types of property 'a' are incompatible. +!!! error TS2322: Type '{ y: string; }' has no properties in common with type '{ x?: number | undefined; }'. + + function foo(x: { a?: string }, y: T & { a: boolean }) { + x = y; // Mismatched property in source intersection + ~ +!!! error TS2322: Type 'T & { a: boolean; }' is not assignable to type '{ a?: string | undefined; }'. +!!! error TS2322: Types of property 'a' are incompatible. +!!! error TS2322: Type 'boolean' is not assignable to type 'string | undefined'. + } + + // Repro from #36637 + + interface Test { + readonly hi?: string[] + } + + function test(value: T): Test { + return { ...value, hi: true } + ~~ +!!! error TS2322: Type 'true' is not assignable to type 'string[] | undefined'. +!!! related TS6500 tests/cases/compiler/intersectionPropertyCheck.ts:13:12: The expected type comes from property 'hi' which is declared here on type 'Test' + } + \ No newline at end of file diff --git a/tests/baselines/reference/intersectionPropertyCheck.js b/tests/baselines/reference/intersectionPropertyCheck.js new file mode 100644 index 00000000000..a62491b8366 --- /dev/null +++ b/tests/baselines/reference/intersectionPropertyCheck.js @@ -0,0 +1,42 @@ +//// [intersectionPropertyCheck.ts] +let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property + +declare let wrong: { a: { y: string } }; +let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type + +function foo(x: { a?: string }, y: T & { a: boolean }) { + x = y; // Mismatched property in source intersection +} + +// Repro from #36637 + +interface Test { + readonly hi?: string[] +} + +function test(value: T): Test { + return { ...value, hi: true } +} + + +//// [intersectionPropertyCheck.js] +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var obj = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property +var weak = wrong; // Nested weak object type +function foo(x, y) { + x = y; // Mismatched property in source intersection +} +function test(value) { + return __assign(__assign({}, value), { hi: true }); +} diff --git a/tests/baselines/reference/intersectionPropertyCheck.symbols b/tests/baselines/reference/intersectionPropertyCheck.symbols new file mode 100644 index 00000000000..e1fec18bf69 --- /dev/null +++ b/tests/baselines/reference/intersectionPropertyCheck.symbols @@ -0,0 +1,58 @@ +=== tests/cases/compiler/intersectionPropertyCheck.ts === +let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property +>obj : Symbol(obj, Decl(intersectionPropertyCheck.ts, 0, 3)) +>a : Symbol(a, Decl(intersectionPropertyCheck.ts, 0, 10)) +>x : Symbol(x, Decl(intersectionPropertyCheck.ts, 0, 15)) +>c : Symbol(c, Decl(intersectionPropertyCheck.ts, 0, 33)) +>a : Symbol(a, Decl(intersectionPropertyCheck.ts, 0, 49)) +>x : Symbol(x, Decl(intersectionPropertyCheck.ts, 0, 54)) +>y : Symbol(y, Decl(intersectionPropertyCheck.ts, 0, 66)) +>c : Symbol(c, Decl(intersectionPropertyCheck.ts, 0, 74)) + +declare let wrong: { a: { y: string } }; +>wrong : Symbol(wrong, Decl(intersectionPropertyCheck.ts, 2, 11)) +>a : Symbol(a, Decl(intersectionPropertyCheck.ts, 2, 20)) +>y : Symbol(y, Decl(intersectionPropertyCheck.ts, 2, 25)) + +let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type +>weak : Symbol(weak, Decl(intersectionPropertyCheck.ts, 3, 3)) +>a : Symbol(a, Decl(intersectionPropertyCheck.ts, 3, 11)) +>x : Symbol(x, Decl(intersectionPropertyCheck.ts, 3, 17)) +>c : Symbol(c, Decl(intersectionPropertyCheck.ts, 3, 36)) +>wrong : Symbol(wrong, Decl(intersectionPropertyCheck.ts, 2, 11)) + +function foo(x: { a?: string }, y: T & { a: boolean }) { +>foo : Symbol(foo, Decl(intersectionPropertyCheck.ts, 3, 58)) +>T : Symbol(T, Decl(intersectionPropertyCheck.ts, 5, 13)) +>x : Symbol(x, Decl(intersectionPropertyCheck.ts, 5, 31)) +>a : Symbol(a, Decl(intersectionPropertyCheck.ts, 5, 35)) +>y : Symbol(y, Decl(intersectionPropertyCheck.ts, 5, 49)) +>T : Symbol(T, Decl(intersectionPropertyCheck.ts, 5, 13)) +>a : Symbol(a, Decl(intersectionPropertyCheck.ts, 5, 58)) + + x = y; // Mismatched property in source intersection +>x : Symbol(x, Decl(intersectionPropertyCheck.ts, 5, 31)) +>y : Symbol(y, Decl(intersectionPropertyCheck.ts, 5, 49)) +} + +// Repro from #36637 + +interface Test { +>Test : Symbol(Test, Decl(intersectionPropertyCheck.ts, 7, 1)) + + readonly hi?: string[] +>hi : Symbol(Test.hi, Decl(intersectionPropertyCheck.ts, 11, 16)) +} + +function test(value: T): Test { +>test : Symbol(test, Decl(intersectionPropertyCheck.ts, 13, 1)) +>T : Symbol(T, Decl(intersectionPropertyCheck.ts, 15, 14)) +>value : Symbol(value, Decl(intersectionPropertyCheck.ts, 15, 32)) +>T : Symbol(T, Decl(intersectionPropertyCheck.ts, 15, 14)) +>Test : Symbol(Test, Decl(intersectionPropertyCheck.ts, 7, 1)) + + return { ...value, hi: true } +>value : Symbol(value, Decl(intersectionPropertyCheck.ts, 15, 32)) +>hi : Symbol(hi, Decl(intersectionPropertyCheck.ts, 16, 20)) +} + diff --git a/tests/baselines/reference/intersectionPropertyCheck.types b/tests/baselines/reference/intersectionPropertyCheck.types new file mode 100644 index 00000000000..515d5009732 --- /dev/null +++ b/tests/baselines/reference/intersectionPropertyCheck.types @@ -0,0 +1,59 @@ +=== tests/cases/compiler/intersectionPropertyCheck.ts === +let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property +>obj : { a: { x: string;}; } & { c: number; } +>a : { x: string; } +>x : string +>c : number +>{ a: { x: 'hello', y: 2 }, c: 5 } : { a: { x: string; y: number; }; c: number; } +>a : { x: string; y: number; } +>{ x: 'hello', y: 2 } : { x: string; y: number; } +>x : string +>'hello' : "hello" +>y : number +>2 : 2 +>c : number +>5 : 5 + +declare let wrong: { a: { y: string } }; +>wrong : { a: { y: string;}; } +>a : { y: string; } +>y : string + +let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type +>weak : { a?: { x?: number | undefined; } | undefined; } & { c?: string | undefined; } +>a : { x?: number | undefined; } | undefined +>x : number | undefined +>c : string | undefined +>wrong : { a: { y: string; }; } + +function foo(x: { a?: string }, y: T & { a: boolean }) { +>foo : (x: { a?: string;}, y: T & { a: boolean;}) => void +>x : { a?: string | undefined; } +>a : string | undefined +>y : T & { a: boolean; } +>a : boolean + + x = y; // Mismatched property in source intersection +>x = y : T & { a: boolean; } +>x : { a?: string | undefined; } +>y : T & { a: boolean; } +} + +// Repro from #36637 + +interface Test { + readonly hi?: string[] +>hi : string[] | undefined +} + +function test(value: T): Test { +>test : (value: T) => Test +>value : T + + return { ...value, hi: true } +>{ ...value, hi: true } : T & { hi: boolean; } +>value : T +>hi : boolean +>true : true +} + diff --git a/tests/baselines/reference/weakType.errors.txt b/tests/baselines/reference/weakType.errors.txt index 9fc9deab703..477087a5859 100644 --- a/tests/baselines/reference/weakType.errors.txt +++ b/tests/baselines/reference/weakType.errors.txt @@ -5,8 +5,9 @@ tests/cases/compiler/weakType.ts(18,13): error TS2559: Type '12' has no properti tests/cases/compiler/weakType.ts(19,13): error TS2559: Type '"completely wrong"' has no properties in common with type 'Settings'. tests/cases/compiler/weakType.ts(20,13): error TS2559: Type 'false' has no properties in common with type 'Settings'. tests/cases/compiler/weakType.ts(37,18): error TS2559: Type '{ error?: number; }' has no properties in common with type 'ChangeOptions'. -tests/cases/compiler/weakType.ts(62,5): error TS2326: Types of property 'properties' are incompatible. - Type '{ wrong: string; }' has no properties in common with type '{ b?: number; }'. +tests/cases/compiler/weakType.ts(62,5): error TS2322: Type '{ properties: { wrong: string; }; }' is not assignable to type 'Weak & Spoiler'. + Types of property 'properties' are incompatible. + Type '{ wrong: string; }' has no properties in common with type '{ b?: number; }'. ==== tests/cases/compiler/weakType.ts (8 errors) ==== @@ -90,7 +91,8 @@ tests/cases/compiler/weakType.ts(62,5): error TS2326: Types of property 'propert } let weak: Weak & Spoiler = propertiesWrong ~~~~ -!!! error TS2326: Types of property 'properties' are incompatible. -!!! error TS2326: Type '{ wrong: string; }' has no properties in common with type '{ b?: number; }'. +!!! error TS2322: Type '{ properties: { wrong: string; }; }' is not assignable to type 'Weak & Spoiler'. +!!! error TS2322: Types of property 'properties' are incompatible. +!!! error TS2322: Type '{ wrong: string; }' has no properties in common with type '{ b?: number; }'. \ No newline at end of file diff --git a/tests/cases/compiler/intersectionPropertyCheck.ts b/tests/cases/compiler/intersectionPropertyCheck.ts new file mode 100644 index 00000000000..77bfd31097e --- /dev/null +++ b/tests/cases/compiler/intersectionPropertyCheck.ts @@ -0,0 +1,20 @@ +// @strict: true + +let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property + +declare let wrong: { a: { y: string } }; +let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type + +function foo(x: { a?: string }, y: T & { a: boolean }) { + x = y; // Mismatched property in source intersection +} + +// Repro from #36637 + +interface Test { + readonly hi?: string[] +} + +function test(value: T): Test { + return { ...value, hi: true } +} From 7ca6334dbd1d58984dc364b7b7fb3e453e3b1649 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 7 Apr 2020 08:04:33 -0700 Subject: [PATCH 02/10] Look for outer type parameters on VariableStatements (#37819) This only applies in JS, where `@template` tags can apply to initialisers of variable declarations: ```js /** * @template T * @returns {(b: T) => T} */ const seq = a => b => b ``` Fixes #36201 --- src/compiler/checker.ts | 4 +++ ...lateTagTypeParameterOnVariableStatement.js | 23 ++++++++++++++ ...agTypeParameterOnVariableStatement.symbols | 25 +++++++++++++++ ...eTagTypeParameterOnVariableStatement.types | 31 +++++++++++++++++++ ...lateTagTypeParameterOnVariableStatement.ts | 17 ++++++++++ 5 files changed, 100 insertions(+) create mode 100644 tests/baselines/reference/instantiateTemplateTagTypeParameterOnVariableStatement.js create mode 100644 tests/baselines/reference/instantiateTemplateTagTypeParameterOnVariableStatement.symbols create mode 100644 tests/baselines/reference/instantiateTemplateTagTypeParameterOnVariableStatement.types create mode 100644 tests/cases/conformance/jsdoc/instantiateTemplateTagTypeParameterOnVariableStatement.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index feb27525052..3b4451e50a3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8238,6 +8238,7 @@ namespace ts { return undefined; } switch (node.kind) { + case SyntaxKind.VariableStatement: case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: case SyntaxKind.InterfaceDeclaration: @@ -8265,6 +8266,9 @@ namespace ts { else if (node.kind === SyntaxKind.ConditionalType) { return concatenate(outerTypeParameters, getInferTypeParameters(node)); } + else if (node.kind === SyntaxKind.VariableStatement && !isInJSFile(node)) { + break; + } const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(node)); const thisType = includeThisTypes && (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration || isJSConstructor(node)) && diff --git a/tests/baselines/reference/instantiateTemplateTagTypeParameterOnVariableStatement.js b/tests/baselines/reference/instantiateTemplateTagTypeParameterOnVariableStatement.js new file mode 100644 index 00000000000..0db9d9355c7 --- /dev/null +++ b/tests/baselines/reference/instantiateTemplateTagTypeParameterOnVariableStatement.js @@ -0,0 +1,23 @@ +//// [instantiateTemplateTagTypeParameterOnVariableStatement.js] +/** + * @template T + * @param {T} a + * @returns {(b: T) => T} + */ +const seq = a => b => b; + +const text1 = "hello"; +const text2 = "world"; + +/** @type {string} */ +var text3 = seq(text1)(text2); + + + + +//// [instantiateTemplateTagTypeParameterOnVariableStatement.d.ts] +declare function seq(a: T): (b: T) => T; +declare const text1: "hello"; +declare const text2: "world"; +/** @type {string} */ +declare var text3: string; diff --git a/tests/baselines/reference/instantiateTemplateTagTypeParameterOnVariableStatement.symbols b/tests/baselines/reference/instantiateTemplateTagTypeParameterOnVariableStatement.symbols new file mode 100644 index 00000000000..74b7bd1f028 --- /dev/null +++ b/tests/baselines/reference/instantiateTemplateTagTypeParameterOnVariableStatement.symbols @@ -0,0 +1,25 @@ +=== tests/cases/conformance/jsdoc/instantiateTemplateTagTypeParameterOnVariableStatement.js === +/** + * @template T + * @param {T} a + * @returns {(b: T) => T} + */ +const seq = a => b => b; +>seq : Symbol(seq, Decl(instantiateTemplateTagTypeParameterOnVariableStatement.js, 5, 5)) +>a : Symbol(a, Decl(instantiateTemplateTagTypeParameterOnVariableStatement.js, 5, 11)) +>b : Symbol(b, Decl(instantiateTemplateTagTypeParameterOnVariableStatement.js, 5, 16)) +>b : Symbol(b, Decl(instantiateTemplateTagTypeParameterOnVariableStatement.js, 5, 16)) + +const text1 = "hello"; +>text1 : Symbol(text1, Decl(instantiateTemplateTagTypeParameterOnVariableStatement.js, 7, 5)) + +const text2 = "world"; +>text2 : Symbol(text2, Decl(instantiateTemplateTagTypeParameterOnVariableStatement.js, 8, 5)) + +/** @type {string} */ +var text3 = seq(text1)(text2); +>text3 : Symbol(text3, Decl(instantiateTemplateTagTypeParameterOnVariableStatement.js, 11, 3)) +>seq : Symbol(seq, Decl(instantiateTemplateTagTypeParameterOnVariableStatement.js, 5, 5)) +>text1 : Symbol(text1, Decl(instantiateTemplateTagTypeParameterOnVariableStatement.js, 7, 5)) +>text2 : Symbol(text2, Decl(instantiateTemplateTagTypeParameterOnVariableStatement.js, 8, 5)) + diff --git a/tests/baselines/reference/instantiateTemplateTagTypeParameterOnVariableStatement.types b/tests/baselines/reference/instantiateTemplateTagTypeParameterOnVariableStatement.types new file mode 100644 index 00000000000..a817bcec75b --- /dev/null +++ b/tests/baselines/reference/instantiateTemplateTagTypeParameterOnVariableStatement.types @@ -0,0 +1,31 @@ +=== tests/cases/conformance/jsdoc/instantiateTemplateTagTypeParameterOnVariableStatement.js === +/** + * @template T + * @param {T} a + * @returns {(b: T) => T} + */ +const seq = a => b => b; +>seq : (a: T) => (b: T) => T +>a => b => b : (a: T) => (b: T) => T +>a : T +>b => b : (b: T) => T +>b : T +>b : T + +const text1 = "hello"; +>text1 : "hello" +>"hello" : "hello" + +const text2 = "world"; +>text2 : "world" +>"world" : "world" + +/** @type {string} */ +var text3 = seq(text1)(text2); +>text3 : string +>seq(text1)(text2) : string +>seq(text1) : (b: string) => string +>seq : (a: T) => (b: T) => T +>text1 : "hello" +>text2 : "world" + diff --git a/tests/cases/conformance/jsdoc/instantiateTemplateTagTypeParameterOnVariableStatement.ts b/tests/cases/conformance/jsdoc/instantiateTemplateTagTypeParameterOnVariableStatement.ts new file mode 100644 index 00000000000..b20d191d3da --- /dev/null +++ b/tests/cases/conformance/jsdoc/instantiateTemplateTagTypeParameterOnVariableStatement.ts @@ -0,0 +1,17 @@ +// @checkJs: true +// @allowJs: true +// @declaration: true +// @emitDeclarationOnly: true +// @filename: instantiateTemplateTagTypeParameterOnVariableStatement.js +/** + * @template T + * @param {T} a + * @returns {(b: T) => T} + */ +const seq = a => b => b; + +const text1 = "hello"; +const text2 = "world"; + +/** @type {string} */ +var text3 = seq(text1)(text2); From c47aca0da321663415b7fc2ed000f66c4d7bcb5d Mon Sep 17 00:00:00 2001 From: Bannerets Date: Tue, 7 Apr 2020 16:16:20 +0000 Subject: [PATCH 03/10] Accurate Array.prototype.flat definition (#32131) * Better Array.prototype.flat definition * Use more meaningful names * Rename 'Flat' to 'FlatArray' --- src/lib/es2019.array.d.ts | 170 ++++---------------------------------- 1 file changed, 16 insertions(+), 154 deletions(-) diff --git a/src/lib/es2019.array.d.ts b/src/lib/es2019.array.d.ts index 9e3e04e5815..5181677c5f9 100644 --- a/src/lib/es2019.array.d.ts +++ b/src/lib/es2019.array.d.ts @@ -1,3 +1,10 @@ +type FlatArray = { + "done": Arr, + "recur": Arr extends ReadonlyArray + ? FlatArray + : Arr +}[Depth extends -1 ? "done" : "recur"]; + interface ReadonlyArray { /** @@ -22,95 +29,11 @@ interface ReadonlyArray { * * @param depth The maximum recursion depth */ - flat(this: - ReadonlyArray | - - ReadonlyArray> | - ReadonlyArray[]> | - ReadonlyArray[][]> | - ReadonlyArray[][][]> | - - ReadonlyArray>> | - ReadonlyArray[][]>> | - ReadonlyArray>[][]> | - ReadonlyArray[]>[]> | - ReadonlyArray>[]> | - ReadonlyArray[]>> | - - ReadonlyArray>>> | - ReadonlyArray[]>>> | - ReadonlyArray>[]>> | - ReadonlyArray>>[]> | - - ReadonlyArray>>>>, - depth: 4): U[]; - - /** - * Returns a new array with all sub-array elements concatenated into it recursively up to the - * specified depth. - * - * @param depth The maximum recursion depth - */ - flat(this: - ReadonlyArray | - - ReadonlyArray[][]> | - ReadonlyArray[]> | - ReadonlyArray> | - - ReadonlyArray>> | - ReadonlyArray[]>> | - ReadonlyArray>[]> | - - ReadonlyArray>>>, - depth: 3): U[]; - - /** - * Returns a new array with all sub-array elements concatenated into it recursively up to the - * specified depth. - * - * @param depth The maximum recursion depth - */ - flat(this: - ReadonlyArray | - - ReadonlyArray> | - ReadonlyArray[]> | - - ReadonlyArray>>, - depth: 2): U[]; - - /** - * Returns a new array with all sub-array elements concatenated into it recursively up to the - * specified depth. - * - * @param depth The maximum recursion depth - */ - flat(this: - ReadonlyArray | - ReadonlyArray>, - depth?: 1 - ): U[]; - - /** - * Returns a new array with all sub-array elements concatenated into it recursively up to the - * specified depth. - * - * @param depth The maximum recursion depth - */ - flat(this: - ReadonlyArray, - depth: 0 - ): U[]; - - /** - * Returns a new array with all sub-array elements concatenated into it recursively up to the - * specified depth. If no depth is provided, flat method defaults to the depth of 1. - * - * @param depth The maximum recursion depth - */ - flat(depth?: number): any[]; -} + flat( + this: A, + depth?: D + ): FlatArray[] + } interface Array { @@ -135,69 +58,8 @@ interface Array { * * @param depth The maximum recursion depth */ - flat(this: U[][][][][][][][], depth: 7): U[]; - - /** - * Returns a new array with all sub-array elements concatenated into it recursively up to the - * specified depth. - * - * @param depth The maximum recursion depth - */ - flat(this: U[][][][][][][], depth: 6): U[]; - - /** - * Returns a new array with all sub-array elements concatenated into it recursively up to the - * specified depth. - * - * @param depth The maximum recursion depth - */ - flat(this: U[][][][][][], depth: 5): U[]; - - /** - * Returns a new array with all sub-array elements concatenated into it recursively up to the - * specified depth. - * - * @param depth The maximum recursion depth - */ - flat(this: U[][][][][], depth: 4): U[]; - - /** - * Returns a new array with all sub-array elements concatenated into it recursively up to the - * specified depth. - * - * @param depth The maximum recursion depth - */ - flat(this: U[][][][], depth: 3): U[]; - - /** - * Returns a new array with all sub-array elements concatenated into it recursively up to the - * specified depth. - * - * @param depth The maximum recursion depth - */ - flat(this: U[][][], depth: 2): U[]; - - /** - * Returns a new array with all sub-array elements concatenated into it recursively up to the - * specified depth. - * - * @param depth The maximum recursion depth - */ - flat(this: U[][], depth?: 1): U[]; - - /** - * Returns a new array with all sub-array elements concatenated into it recursively up to the - * specified depth. - * - * @param depth The maximum recursion depth - */ - flat(this: U[], depth: 0): U[]; - - /** - * Returns a new array with all sub-array elements concatenated into it recursively up to the - * specified depth. If no depth is provided, flat method defaults to the depth of 1. - * - * @param depth The maximum recursion depth - */ - flat(depth?: number): any[]; + flat( + this: A, + depth?: D + ): FlatArray[] } From 3e86f15f5101a354625427befbd907e3bac57b30 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 7 Apr 2020 09:55:56 -0800 Subject: [PATCH 04/10] Disambiguate types with same name from different namespaces in mapToTypeNodes (#37543) * Disambiguate types with same name from different namespaces in mapToTypeNodes * Update baseline with additional example * Fix typo --- src/compiler/checker.ts | 34 ++++++++++++++- src/compiler/core.ts | 18 ++++++++ src/compiler/utilities.ts | 14 +++++++ .../namespaceDisambiguationInUnion.errors.txt | 29 +++++++++++++ .../namespaceDisambiguationInUnion.js | 21 ++++++++++ .../namespaceDisambiguationInUnion.symbols | 42 +++++++++++++++++++ .../namespaceDisambiguationInUnion.types | 41 ++++++++++++++++++ .../namespaceDisambiguationInUnion.ts | 13 ++++++ 8 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/namespaceDisambiguationInUnion.errors.txt create mode 100644 tests/baselines/reference/namespaceDisambiguationInUnion.js create mode 100644 tests/baselines/reference/namespaceDisambiguationInUnion.symbols create mode 100644 tests/baselines/reference/namespaceDisambiguationInUnion.types create mode 100644 tests/cases/compiler/namespaceDisambiguationInUnion.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3b4451e50a3..0a472e5e5df 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4742,7 +4742,10 @@ namespace ts { ]; } } - const result = []; + const mayHaveNameCollisions = !(context.flags & NodeBuilderFlags.UseFullyQualifiedType); + /** Map from type reference identifier text to [type, index in `result` where the type node is] */ + const seenNames = mayHaveNameCollisions ? createUnderscoreEscapedMultiMap<[Type, number]>() : undefined; + const result: TypeNode[] = []; let i = 0; for (const type of types) { i++; @@ -4758,13 +4761,42 @@ namespace ts { const typeNode = typeToTypeNodeHelper(type, context); if (typeNode) { result.push(typeNode); + if (seenNames && isIdentifierTypeReference(typeNode)) { + seenNames.add(typeNode.typeName.escapedText, [type, result.length - 1]); + } } } + if (seenNames) { + // To avoid printing types like `[Foo, Foo]` or `Bar & Bar` where + // occurrences of the same name actually come from different + // namespaces, go through the single-identifier type reference nodes + // we just generated, and see if any names were generated more than + // once while referring to different types. If so, regenerate the + // type node for each entry by that name with the + // `UseFullyQualifiedType` flag enabled. + const saveContextFlags = context.flags; + context.flags |= NodeBuilderFlags.UseFullyQualifiedType; + seenNames.forEach(types => { + if (!arrayIsHomogeneous(types, ([a], [b]) => typesAreSameReference(a, b))) { + for (const [type, resultIndex] of types) { + result[resultIndex] = typeToTypeNodeHelper(type, context); + } + } + }); + context.flags = saveContextFlags; + } + return result; } } + function typesAreSameReference(a: Type, b: Type): boolean { + return a === b + || !!a.symbol && a.symbol === b.symbol + || !!a.aliasSymbol && a.aliasSymbol === b.aliasSymbol; + } + function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, kind: IndexKind, context: NodeBuilderContext): IndexSignatureDeclaration { const name = getNameFromIndexInfo(indexInfo) || "x"; const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 1e8326621d1..a309eb97c1b 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1351,6 +1351,24 @@ namespace ts { } } + export interface UnderscoreEscapedMultiMap extends UnderscoreEscapedMap { + /** + * Adds the value to an array of values associated with the key, and returns the array. + * Creates the array if it does not already exist. + */ + add(key: __String, value: T): T[]; + /** + * Removes a value from an array of values associated with the key. + * Does not preserve the order of those values. + * Does nothing if `key` is not in `map`, or `value` is not in `map[key]`. + */ + remove(key: __String, value: T): void; + } + + export function createUnderscoreEscapedMultiMap(): UnderscoreEscapedMultiMap { + return createMultiMap() as UnderscoreEscapedMultiMap; + } + /** * Tests whether a value is an array. */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 4948a4bb6e3..a5235eeae23 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6353,4 +6353,18 @@ namespace ts { }) as HeritageClause | undefined; return heritageClause?.token === SyntaxKind.ImplementsKeyword || heritageClause?.parent.kind === SyntaxKind.InterfaceDeclaration; } + + export function isIdentifierTypeReference(node: Node): node is TypeReferenceNode & { typeName: Identifier } { + return isTypeReferenceNode(node) && isIdentifier(node.typeName); + } + + export function arrayIsHomogeneous(array: readonly T[], comparer: EqualityComparer = equateValues) { + if (array.length < 2) return true; + const first = array[0]; + for (let i = 1, length = array.length; i < length; i++) { + const target = array[i]; + if (!comparer(first, target)) return false; + } + return true; + } } diff --git a/tests/baselines/reference/namespaceDisambiguationInUnion.errors.txt b/tests/baselines/reference/namespaceDisambiguationInUnion.errors.txt new file mode 100644 index 00000000000..1f2a8ab69ce --- /dev/null +++ b/tests/baselines/reference/namespaceDisambiguationInUnion.errors.txt @@ -0,0 +1,29 @@ +tests/cases/compiler/namespaceDisambiguationInUnion.ts(10,7): error TS2322: Type '{ type: string; }' is not assignable to type 'Foo.Yep | Bar.Yep'. + Type '{ type: string; }' is not assignable to type 'Yep'. + Types of property 'type' are incompatible. + Type 'string' is not assignable to type '"bar.yep"'. +tests/cases/compiler/namespaceDisambiguationInUnion.ts(13,7): error TS2739: Type '{ type: string; }[]' is missing the following properties from type '[Foo.Yep, Bar.Yep]': 0, 1 + + +==== tests/cases/compiler/namespaceDisambiguationInUnion.ts (2 errors) ==== + namespace Foo { + export type Yep = { type: "foo.yep" }; + } + + namespace Bar { + export type Yep = { type: "bar.yep" }; + } + + const x = { type: "wat.nup" }; + const val1: Foo.Yep | Bar.Yep = x; + ~~~~ +!!! error TS2322: Type '{ type: string; }' is not assignable to type 'Foo.Yep | Bar.Yep'. +!!! error TS2322: Type '{ type: string; }' is not assignable to type 'Yep'. +!!! error TS2322: Types of property 'type' are incompatible. +!!! error TS2322: Type 'string' is not assignable to type '"bar.yep"'. + + const y = [{ type: "a" }, { type: "b" }]; + const val2: [Foo.Yep, Bar.Yep] = y; + ~~~~ +!!! error TS2739: Type '{ type: string; }[]' is missing the following properties from type '[Foo.Yep, Bar.Yep]': 0, 1 + \ No newline at end of file diff --git a/tests/baselines/reference/namespaceDisambiguationInUnion.js b/tests/baselines/reference/namespaceDisambiguationInUnion.js new file mode 100644 index 00000000000..b5fb1271a1e --- /dev/null +++ b/tests/baselines/reference/namespaceDisambiguationInUnion.js @@ -0,0 +1,21 @@ +//// [namespaceDisambiguationInUnion.ts] +namespace Foo { + export type Yep = { type: "foo.yep" }; +} + +namespace Bar { + export type Yep = { type: "bar.yep" }; +} + +const x = { type: "wat.nup" }; +const val1: Foo.Yep | Bar.Yep = x; + +const y = [{ type: "a" }, { type: "b" }]; +const val2: [Foo.Yep, Bar.Yep] = y; + + +//// [namespaceDisambiguationInUnion.js] +var x = { type: "wat.nup" }; +var val1 = x; +var y = [{ type: "a" }, { type: "b" }]; +var val2 = y; diff --git a/tests/baselines/reference/namespaceDisambiguationInUnion.symbols b/tests/baselines/reference/namespaceDisambiguationInUnion.symbols new file mode 100644 index 00000000000..7f2a6dfb69a --- /dev/null +++ b/tests/baselines/reference/namespaceDisambiguationInUnion.symbols @@ -0,0 +1,42 @@ +=== tests/cases/compiler/namespaceDisambiguationInUnion.ts === +namespace Foo { +>Foo : Symbol(Foo, Decl(namespaceDisambiguationInUnion.ts, 0, 0)) + + export type Yep = { type: "foo.yep" }; +>Yep : Symbol(Yep, Decl(namespaceDisambiguationInUnion.ts, 0, 15)) +>type : Symbol(type, Decl(namespaceDisambiguationInUnion.ts, 1, 21)) +} + +namespace Bar { +>Bar : Symbol(Bar, Decl(namespaceDisambiguationInUnion.ts, 2, 1)) + + export type Yep = { type: "bar.yep" }; +>Yep : Symbol(Yep, Decl(namespaceDisambiguationInUnion.ts, 4, 15)) +>type : Symbol(type, Decl(namespaceDisambiguationInUnion.ts, 5, 21)) +} + +const x = { type: "wat.nup" }; +>x : Symbol(x, Decl(namespaceDisambiguationInUnion.ts, 8, 5)) +>type : Symbol(type, Decl(namespaceDisambiguationInUnion.ts, 8, 11)) + +const val1: Foo.Yep | Bar.Yep = x; +>val1 : Symbol(val1, Decl(namespaceDisambiguationInUnion.ts, 9, 5)) +>Foo : Symbol(Foo, Decl(namespaceDisambiguationInUnion.ts, 0, 0)) +>Yep : Symbol(Foo.Yep, Decl(namespaceDisambiguationInUnion.ts, 0, 15)) +>Bar : Symbol(Bar, Decl(namespaceDisambiguationInUnion.ts, 2, 1)) +>Yep : Symbol(Bar.Yep, Decl(namespaceDisambiguationInUnion.ts, 4, 15)) +>x : Symbol(x, Decl(namespaceDisambiguationInUnion.ts, 8, 5)) + +const y = [{ type: "a" }, { type: "b" }]; +>y : Symbol(y, Decl(namespaceDisambiguationInUnion.ts, 11, 5)) +>type : Symbol(type, Decl(namespaceDisambiguationInUnion.ts, 11, 12)) +>type : Symbol(type, Decl(namespaceDisambiguationInUnion.ts, 11, 27)) + +const val2: [Foo.Yep, Bar.Yep] = y; +>val2 : Symbol(val2, Decl(namespaceDisambiguationInUnion.ts, 12, 5)) +>Foo : Symbol(Foo, Decl(namespaceDisambiguationInUnion.ts, 0, 0)) +>Yep : Symbol(Foo.Yep, Decl(namespaceDisambiguationInUnion.ts, 0, 15)) +>Bar : Symbol(Bar, Decl(namespaceDisambiguationInUnion.ts, 2, 1)) +>Yep : Symbol(Bar.Yep, Decl(namespaceDisambiguationInUnion.ts, 4, 15)) +>y : Symbol(y, Decl(namespaceDisambiguationInUnion.ts, 11, 5)) + diff --git a/tests/baselines/reference/namespaceDisambiguationInUnion.types b/tests/baselines/reference/namespaceDisambiguationInUnion.types new file mode 100644 index 00000000000..a7852beebd2 --- /dev/null +++ b/tests/baselines/reference/namespaceDisambiguationInUnion.types @@ -0,0 +1,41 @@ +=== tests/cases/compiler/namespaceDisambiguationInUnion.ts === +namespace Foo { + export type Yep = { type: "foo.yep" }; +>Yep : Yep +>type : "foo.yep" +} + +namespace Bar { + export type Yep = { type: "bar.yep" }; +>Yep : Yep +>type : "bar.yep" +} + +const x = { type: "wat.nup" }; +>x : { type: string; } +>{ type: "wat.nup" } : { type: string; } +>type : string +>"wat.nup" : "wat.nup" + +const val1: Foo.Yep | Bar.Yep = x; +>val1 : Foo.Yep | Bar.Yep +>Foo : any +>Bar : any +>x : { type: string; } + +const y = [{ type: "a" }, { type: "b" }]; +>y : { type: string; }[] +>[{ type: "a" }, { type: "b" }] : { type: string; }[] +>{ type: "a" } : { type: string; } +>type : string +>"a" : "a" +>{ type: "b" } : { type: string; } +>type : string +>"b" : "b" + +const val2: [Foo.Yep, Bar.Yep] = y; +>val2 : [Foo.Yep, Bar.Yep] +>Foo : any +>Bar : any +>y : { type: string; }[] + diff --git a/tests/cases/compiler/namespaceDisambiguationInUnion.ts b/tests/cases/compiler/namespaceDisambiguationInUnion.ts new file mode 100644 index 00000000000..b0416d0df89 --- /dev/null +++ b/tests/cases/compiler/namespaceDisambiguationInUnion.ts @@ -0,0 +1,13 @@ +namespace Foo { + export type Yep = { type: "foo.yep" }; +} + +namespace Bar { + export type Yep = { type: "bar.yep" }; +} + +const x = { type: "wat.nup" }; +const val1: Foo.Yep | Bar.Yep = x; + +const y = [{ type: "a" }, { type: "b" }]; +const val2: [Foo.Yep, Bar.Yep] = y; From 3398c9bfe1e474e2f81e5ea05d01645e329e5860 Mon Sep 17 00:00:00 2001 From: Orta Date: Tue, 7 Apr 2020 15:00:44 -0400 Subject: [PATCH 05/10] Make skipLibCheck: true the default in --init (#37808) --- src/compiler/commandLineParser.ts | 3 ++- .../tsConfig/Default initialized TSConfig/tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../Initialized TSConfig with files options/tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../declarationDir-is-specified.js | 3 ++- .../when-outDir-and-declarationDir-is-specified.js | 3 ++- .../when-outDir-is-specified.js | 3 ++- .../with-outFile.js | 3 ++- .../without-outDir-or-outFile-is-specified.js | 3 ++- 14 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index d286eaf3118..35ef50db8ad 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1115,7 +1115,8 @@ namespace ts { target: ScriptTarget.ES5, strict: true, esModuleInterop: true, - forceConsistentCasingInFileNames: true + forceConsistentCasingInFileNames: true, + skipLibCheck: true }; /* @internal */ diff --git a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json index d4decd1da4a..c9f603c2199 100644 --- a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json @@ -63,6 +63,7 @@ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } } diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json index 1ce0b81d192..f2ff8ef9f00 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json @@ -63,6 +63,7 @@ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } } diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json index 0569b546996..286219dae6e 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json @@ -63,6 +63,7 @@ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } } diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json index d4bd5c32c52..0ec33da9842 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json @@ -63,6 +63,7 @@ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ }, "files": [ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json index 94b36f0dcf3..ea8f60ca0cf 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json @@ -63,6 +63,7 @@ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } } diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json index d4decd1da4a..c9f603c2199 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json @@ -63,6 +63,7 @@ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } } diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json index 814e985a982..7add8d543f8 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json @@ -63,6 +63,7 @@ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } } diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json index 6ef0e3a101b..29a0374fa26 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json @@ -63,6 +63,7 @@ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } } diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js index 0b0bdfd343a..3ba97be0f14 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js @@ -85,6 +85,7 @@ interface Array { length: number; [n: number]: T; } /* Advanced Options */ "declarationDir": "decls", /* Output directory for generated declaration files. */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } } @@ -127,7 +128,7 @@ Output:: Program root files: ["/user/username/projects/myproject/file1.ts","/user/username/projects/myproject/src/file2.ts"] -Program options: {"target":1,"module":2,"declaration":true,"strict":true,"esModuleInterop":true,"declarationDir":"/user/username/projects/myproject/decls","forceConsistentCasingInFileNames":true,"watch":true,"project":"/user/username/projects/myproject/tsconfig.json","configFilePath":"/user/username/projects/myproject/tsconfig.json"} +Program options: {"target":1,"module":2,"declaration":true,"strict":true,"esModuleInterop":true,"declarationDir":"/user/username/projects/myproject/decls","skipLibCheck":true,"forceConsistentCasingInFileNames":true,"watch":true,"project":"/user/username/projects/myproject/tsconfig.json","configFilePath":"/user/username/projects/myproject/tsconfig.json"} Program files:: /a/lib/lib.d.ts /user/username/projects/myproject/file1.ts diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js index 93a9754adb5..637cf598966 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js @@ -85,6 +85,7 @@ interface Array { length: number; [n: number]: T; } /* Advanced Options */ "declarationDir": "decls", /* Output directory for generated declaration files. */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } } @@ -127,7 +128,7 @@ Output:: Program root files: ["/user/username/projects/myproject/file1.ts","/user/username/projects/myproject/src/file2.ts"] -Program options: {"target":1,"module":2,"declaration":true,"outDir":"/user/username/projects/myproject/build","strict":true,"esModuleInterop":true,"declarationDir":"/user/username/projects/myproject/decls","forceConsistentCasingInFileNames":true,"watch":true,"project":"/user/username/projects/myproject/tsconfig.json","configFilePath":"/user/username/projects/myproject/tsconfig.json"} +Program options: {"target":1,"module":2,"declaration":true,"outDir":"/user/username/projects/myproject/build","strict":true,"esModuleInterop":true,"declarationDir":"/user/username/projects/myproject/decls","skipLibCheck":true,"forceConsistentCasingInFileNames":true,"watch":true,"project":"/user/username/projects/myproject/tsconfig.json","configFilePath":"/user/username/projects/myproject/tsconfig.json"} Program files:: /a/lib/lib.d.ts /user/username/projects/myproject/file1.ts diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js index 0c8fcf74653..0e126eb1e1b 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js @@ -84,6 +84,7 @@ interface Array { length: number; [n: number]: T; } // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } } @@ -118,7 +119,7 @@ Output:: Program root files: ["/user/username/projects/myproject/file1.ts","/user/username/projects/myproject/src/file2.ts"] -Program options: {"target":1,"module":2,"outDir":"/user/username/projects/myproject/build","strict":true,"esModuleInterop":true,"forceConsistentCasingInFileNames":true,"watch":true,"project":"/user/username/projects/myproject/tsconfig.json","configFilePath":"/user/username/projects/myproject/tsconfig.json"} +Program options: {"target":1,"module":2,"outDir":"/user/username/projects/myproject/build","strict":true,"esModuleInterop":true,"skipLibCheck":true,"forceConsistentCasingInFileNames":true,"watch":true,"project":"/user/username/projects/myproject/tsconfig.json","configFilePath":"/user/username/projects/myproject/tsconfig.json"} Program files:: /a/lib/lib.d.ts /user/username/projects/myproject/file1.ts diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js index 8ef1989cdb0..a0a613282c1 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js @@ -84,6 +84,7 @@ interface Array { length: number; [n: number]: T; } // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } } @@ -115,7 +116,7 @@ Output:: Program root files: ["/user/username/projects/myproject/file1.ts","/user/username/projects/myproject/src/file2.ts"] -Program options: {"target":1,"module":2,"outFile":"/user/username/projects/myproject/build/outFile.js","strict":true,"esModuleInterop":true,"forceConsistentCasingInFileNames":true,"watch":true,"project":"/user/username/projects/myproject/tsconfig.json","configFilePath":"/user/username/projects/myproject/tsconfig.json"} +Program options: {"target":1,"module":2,"outFile":"/user/username/projects/myproject/build/outFile.js","strict":true,"esModuleInterop":true,"skipLibCheck":true,"forceConsistentCasingInFileNames":true,"watch":true,"project":"/user/username/projects/myproject/tsconfig.json","configFilePath":"/user/username/projects/myproject/tsconfig.json"} Program files:: /a/lib/lib.d.ts /user/username/projects/myproject/file1.ts diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js index fbe282741c8..7eec5119a93 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js @@ -84,6 +84,7 @@ interface Array { length: number; [n: number]: T; } // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } } @@ -118,7 +119,7 @@ Output:: Program root files: ["/user/username/projects/myproject/file1.ts","/user/username/projects/myproject/src/file2.ts"] -Program options: {"target":1,"module":2,"strict":true,"esModuleInterop":true,"forceConsistentCasingInFileNames":true,"watch":true,"project":"/user/username/projects/myproject/tsconfig.json","configFilePath":"/user/username/projects/myproject/tsconfig.json"} +Program options: {"target":1,"module":2,"strict":true,"esModuleInterop":true,"skipLibCheck":true,"forceConsistentCasingInFileNames":true,"watch":true,"project":"/user/username/projects/myproject/tsconfig.json","configFilePath":"/user/username/projects/myproject/tsconfig.json"} Program files:: /a/lib/lib.d.ts /user/username/projects/myproject/file1.ts From dcc6c9461e1ff6015cff5bec3ccd9872cbf762b1 Mon Sep 17 00:00:00 2001 From: Eli Barzilay Date: Fri, 27 Mar 2020 15:19:43 -0400 Subject: [PATCH 06/10] Minor improvement Get the `declaration` container just once instead of in three places. (Minor change: one place used to start looking from `declaration.parent`, but that shouldn't make any difference.) Also, don't pass it to the helper functions since they're local anyway. --- src/compiler/checker.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0a472e5e5df..ac38c26d62e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1347,6 +1347,7 @@ namespace ts { function isBlockScopedNameDeclaredBeforeUse(declaration: Declaration, usage: Node): boolean { const declarationFile = getSourceFileOfNode(declaration); const useFile = getSourceFileOfNode(usage); + const declContainer = getEnclosingBlockScopeContainer(declaration); if (declarationFile !== useFile) { if ((moduleKind && (declarationFile.externalModuleIndicator || useFile.externalModuleIndicator)) || (!compilerOptions.outFile && !compilerOptions.out) || @@ -1389,11 +1390,10 @@ namespace ts { return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ false); } else if (isParameterPropertyDeclaration(declaration, declaration.parent)) { - const container = getEnclosingBlockScopeContainer(declaration.parent); // foo = this.bar is illegal in esnext+useDefineForClassFields when bar is a parameter property return !(compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields && getContainingClass(declaration) === getContainingClass(usage) - && isUsedInFunctionOrInstanceProperty(usage, declaration, container)); + && isUsedInFunctionOrInstanceProperty(usage, declaration)); } return true; } @@ -1418,11 +1418,10 @@ namespace ts { return true; } - const container = getEnclosingBlockScopeContainer(declaration); if (!!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage)) { return true; } - if (isUsedInFunctionOrInstanceProperty(usage, declaration, container)) { + if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { if (compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields && getContainingClass(declaration)) { return (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent)) && !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true); @@ -1434,15 +1433,13 @@ namespace ts { return false; function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { - const container = getEnclosingBlockScopeContainer(declaration); - switch (declaration.parent.parent.kind) { case SyntaxKind.VariableStatement: case SyntaxKind.ForStatement: case SyntaxKind.ForOfStatement: // variable statement/for/for-of statement case, // use site should not be inside variable declaration (initializer of declaration or binding element) - if (isSameScopeDescendentOf(usage, declaration, container)) { + if (isSameScopeDescendentOf(usage, declaration, declContainer)) { return true; } break; @@ -1450,12 +1447,12 @@ namespace ts { // ForIn/ForOf case - use site should not be used in expression part const grandparent = declaration.parent.parent; - return isForInOrOfStatement(grandparent) && isSameScopeDescendentOf(usage, grandparent.expression, container); + return isForInOrOfStatement(grandparent) && isSameScopeDescendentOf(usage, grandparent.expression, declContainer); } - function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node, container?: Node): boolean { + function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node): boolean { return !!findAncestor(usage, current => { - if (current === container) { + if (current === declContainer) { return "quit"; } if (isFunctionLike(current)) { From e4babd40e00f572c3f308e47c8d98e2d7df550a4 Mon Sep 17 00:00:00 2001 From: Eli Barzilay Date: Fri, 27 Mar 2020 15:54:34 -0400 Subject: [PATCH 07/10] Skip `isBlockScopedNameDeclaredBeforeUse` error in interface or type declarations Fixes #35947. --- src/compiler/checker.ts | 6 +++- .../reference/forwardRefInTypeDeclaration.js | 18 ++++++++++++ .../forwardRefInTypeDeclaration.symbols | 27 +++++++++++++++++ .../forwardRefInTypeDeclaration.types | 29 +++++++++++++++++++ .../compiler/forwardRefInTypeDeclaration.ts | 11 +++++++ 5 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/forwardRefInTypeDeclaration.js create mode 100644 tests/baselines/reference/forwardRefInTypeDeclaration.symbols create mode 100644 tests/baselines/reference/forwardRefInTypeDeclaration.types create mode 100644 tests/cases/compiler/forwardRefInTypeDeclaration.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ac38c26d62e..6ebcbdc2227 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1418,7 +1418,7 @@ namespace ts { return true; } - if (!!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage)) { + if (!!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage) || usageInTypeDeclaration()) { return true; } if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { @@ -1432,6 +1432,10 @@ namespace ts { } return false; + function usageInTypeDeclaration() { + return !!findAncestor(usage, node => isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)); + } + function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { switch (declaration.parent.parent.kind) { case SyntaxKind.VariableStatement: diff --git a/tests/baselines/reference/forwardRefInTypeDeclaration.js b/tests/baselines/reference/forwardRefInTypeDeclaration.js new file mode 100644 index 00000000000..ee2dda355f6 --- /dev/null +++ b/tests/baselines/reference/forwardRefInTypeDeclaration.js @@ -0,0 +1,18 @@ +//// [forwardRefInTypeDeclaration.ts] +// forward ref ignored in a typeof +declare let s: typeof s1; +const s1 = "x"; + +// ignored anywhere in an interface (#35947) +interface Foo2 { [s2]: number; } +const s2 = "x"; + +// or in a type definition +type Foo3 = { [s3]: number; } +const s3 = "x"; + + +//// [forwardRefInTypeDeclaration.js] +var s1 = "x"; +var s2 = "x"; +var s3 = "x"; diff --git a/tests/baselines/reference/forwardRefInTypeDeclaration.symbols b/tests/baselines/reference/forwardRefInTypeDeclaration.symbols new file mode 100644 index 00000000000..f8ad803779c --- /dev/null +++ b/tests/baselines/reference/forwardRefInTypeDeclaration.symbols @@ -0,0 +1,27 @@ +=== tests/cases/compiler/forwardRefInTypeDeclaration.ts === +// forward ref ignored in a typeof +declare let s: typeof s1; +>s : Symbol(s, Decl(forwardRefInTypeDeclaration.ts, 1, 11)) +>s1 : Symbol(s1, Decl(forwardRefInTypeDeclaration.ts, 2, 5)) + +const s1 = "x"; +>s1 : Symbol(s1, Decl(forwardRefInTypeDeclaration.ts, 2, 5)) + +// ignored anywhere in an interface (#35947) +interface Foo2 { [s2]: number; } +>Foo2 : Symbol(Foo2, Decl(forwardRefInTypeDeclaration.ts, 2, 15)) +>[s2] : Symbol(Foo2[s2], Decl(forwardRefInTypeDeclaration.ts, 5, 16)) +>s2 : Symbol(s2, Decl(forwardRefInTypeDeclaration.ts, 6, 5)) + +const s2 = "x"; +>s2 : Symbol(s2, Decl(forwardRefInTypeDeclaration.ts, 6, 5)) + +// or in a type definition +type Foo3 = { [s3]: number; } +>Foo3 : Symbol(Foo3, Decl(forwardRefInTypeDeclaration.ts, 6, 15)) +>[s3] : Symbol([s3], Decl(forwardRefInTypeDeclaration.ts, 9, 13)) +>s3 : Symbol(s3, Decl(forwardRefInTypeDeclaration.ts, 10, 5)) + +const s3 = "x"; +>s3 : Symbol(s3, Decl(forwardRefInTypeDeclaration.ts, 10, 5)) + diff --git a/tests/baselines/reference/forwardRefInTypeDeclaration.types b/tests/baselines/reference/forwardRefInTypeDeclaration.types new file mode 100644 index 00000000000..de487ec372c --- /dev/null +++ b/tests/baselines/reference/forwardRefInTypeDeclaration.types @@ -0,0 +1,29 @@ +=== tests/cases/compiler/forwardRefInTypeDeclaration.ts === +// forward ref ignored in a typeof +declare let s: typeof s1; +>s : "x" +>s1 : "x" + +const s1 = "x"; +>s1 : "x" +>"x" : "x" + +// ignored anywhere in an interface (#35947) +interface Foo2 { [s2]: number; } +>[s2] : number +>s2 : "x" + +const s2 = "x"; +>s2 : "x" +>"x" : "x" + +// or in a type definition +type Foo3 = { [s3]: number; } +>Foo3 : Foo3 +>[s3] : number +>s3 : "x" + +const s3 = "x"; +>s3 : "x" +>"x" : "x" + diff --git a/tests/cases/compiler/forwardRefInTypeDeclaration.ts b/tests/cases/compiler/forwardRefInTypeDeclaration.ts new file mode 100644 index 00000000000..ef29f727862 --- /dev/null +++ b/tests/cases/compiler/forwardRefInTypeDeclaration.ts @@ -0,0 +1,11 @@ +// forward ref ignored in a typeof +declare let s: typeof s1; +const s1 = "x"; + +// ignored anywhere in an interface (#35947) +interface Foo2 { [s2]: number; } +const s2 = "x"; + +// or in a type definition +type Foo3 = { [s3]: number; } +const s3 = "x"; From e897eb1b2a7585f2300e41c7a4d9f549b07ed739 Mon Sep 17 00:00:00 2001 From: Alexander T Date: Wed, 8 Apr 2020 02:59:54 +0300 Subject: [PATCH 08/10] fix(37817): omit comments in name accessor (#37822) --- .../generateGetAccessorAndSetAccessor.ts | 1 + ...efactorConvertToGetAccessAndSetAccess36.ts | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess36.ts diff --git a/src/services/refactors/generateGetAccessorAndSetAccessor.ts b/src/services/refactors/generateGetAccessorAndSetAccessor.ts index d0d2d11df7e..038cd04cdeb 100644 --- a/src/services/refactors/generateGetAccessorAndSetAccessor.ts +++ b/src/services/refactors/generateGetAccessorAndSetAccessor.ts @@ -46,6 +46,7 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor { const { isStatic, isReadonly, fieldName, accessorName, originalName, type, container, declaration, renameAccessor } = fieldInfo; suppressLeadingAndTrailingTrivia(fieldName); + suppressLeadingAndTrailingTrivia(accessorName); suppressLeadingAndTrailingTrivia(declaration); suppressLeadingAndTrailingTrivia(container); diff --git a/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess36.ts b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess36.ts new file mode 100644 index 00000000000..c5f82c3c440 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess36.ts @@ -0,0 +1,28 @@ +/// + +////class Foo { +//// /** +//// * Property description +//// */ +//// /*a*/_prop!: string; // comment/*b*/ +////} + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Generate 'get' and 'set' accessors", + actionName: "Generate 'get' and 'set' accessors", + actionDescription: "Generate 'get' and 'set' accessors", + newContent: +`class Foo { + /** + * Property description + */ + private _prop!: string; // comment + public get /*RENAME*/prop(): string { + return this._prop; + } + public set prop(value: string) { + this._prop = value; + } +}` +}); From 126c6ab80d2dca28d4a3d1889f19ad3504c1f74d Mon Sep 17 00:00:00 2001 From: zhangciwu Date: Wed, 8 Apr 2020 23:17:22 +0800 Subject: [PATCH 09/10] Fix easy misunderstanding "! ===" (#37838) * Remove unnecessary Non-null assertion operator * Wrap Non-null assertion operator inside parentheses --- src/compiler/scanner.ts | 4 ++-- src/services/codefixes/addMissingAwait.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index fef7179f608..c841d5bdba4 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -307,14 +307,14 @@ namespace ts { /* @internal */ export function isUnicodeIdentifierStart(code: number, languageVersion: ScriptTarget | undefined) { return languageVersion! >= ScriptTarget.ES2015 ? lookupInUnicodeMap(code, unicodeESNextIdentifierStart) : - languageVersion! === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierStart) : + languageVersion === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierStart) : lookupInUnicodeMap(code, unicodeES3IdentifierStart); } function isUnicodeIdentifierPart(code: number, languageVersion: ScriptTarget | undefined) { return languageVersion! >= ScriptTarget.ES2015 ? lookupInUnicodeMap(code, unicodeESNextIdentifierPart) : - languageVersion! === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierPart) : + languageVersion === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierPart) : lookupInUnicodeMap(code, unicodeES3IdentifierPart); } diff --git a/src/services/codefixes/addMissingAwait.ts b/src/services/codefixes/addMissingAwait.ts index 60f5814c396..fa562d77e8d 100644 --- a/src/services/codefixes/addMissingAwait.ts +++ b/src/services/codefixes/addMissingAwait.ts @@ -211,7 +211,7 @@ namespace ts.codefix { reference; const diagnostic = find(diagnostics, diagnostic => diagnostic.start === errorNode.getStart(sourceFile) && - diagnostic.start + diagnostic.length! === errorNode.getEnd()); + (diagnostic.start + diagnostic.length!) === errorNode.getEnd()); return diagnostic && contains(errorCodes, diagnostic.code) || // A Promise is usually not correct in a binary expression (it’s not valid From 5a7916962d84c23c8b637664b7f854a9b59cd250 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 8 Apr 2020 12:06:40 -0700 Subject: [PATCH 10/10] Fix metadata serialization for invalid jsdoc types (#37836) --- src/compiler/transformers/ts.ts | 12 ++++++ .../decoratorMetadata-jsdoc.errors.txt | 22 +++++++++++ .../reference/decoratorMetadata-jsdoc.js | 39 +++++++++++++++++++ .../reference/decoratorMetadata-jsdoc.symbols | 25 ++++++++++++ .../reference/decoratorMetadata-jsdoc.types | 28 +++++++++++++ .../decorators/decoratorMetadata-jsdoc.ts | 14 +++++++ 6 files changed, 140 insertions(+) create mode 100644 tests/baselines/reference/decoratorMetadata-jsdoc.errors.txt create mode 100644 tests/baselines/reference/decoratorMetadata-jsdoc.js create mode 100644 tests/baselines/reference/decoratorMetadata-jsdoc.symbols create mode 100644 tests/baselines/reference/decoratorMetadata-jsdoc.types create mode 100644 tests/cases/conformance/decorators/decoratorMetadata-jsdoc.ts diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index dc8b62796cc..0ef7db06272 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -1590,6 +1590,18 @@ namespace ts { case SyntaxKind.ImportType: break; + // handle JSDoc types from an invalid parse + case SyntaxKind.JSDocAllType: + case SyntaxKind.JSDocUnknownType: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocVariadicType: + case SyntaxKind.JSDocNamepathType: + break; + + case SyntaxKind.JSDocNullableType: + case SyntaxKind.JSDocNonNullableType: + case SyntaxKind.JSDocOptionalType: + return serializeTypeNode((node).type); default: return Debug.failBadSyntaxKind(node); diff --git a/tests/baselines/reference/decoratorMetadata-jsdoc.errors.txt b/tests/baselines/reference/decoratorMetadata-jsdoc.errors.txt new file mode 100644 index 00000000000..a2af419361d --- /dev/null +++ b/tests/baselines/reference/decoratorMetadata-jsdoc.errors.txt @@ -0,0 +1,22 @@ +tests/cases/conformance/decorators/decoratorMetadata-jsdoc.ts(5,9): error TS8020: JSDoc types can only be used inside documentation comments. +tests/cases/conformance/decorators/decoratorMetadata-jsdoc.ts(7,9): error TS8020: JSDoc types can only be used inside documentation comments. +tests/cases/conformance/decorators/decoratorMetadata-jsdoc.ts(9,9): error TS8020: JSDoc types can only be used inside documentation comments. + + +==== tests/cases/conformance/decorators/decoratorMetadata-jsdoc.ts (3 errors) ==== + declare var decorator: any; + + class X { + @decorator() + a?: string?; + ~~~~~~~ +!!! error TS8020: JSDoc types can only be used inside documentation comments. + @decorator() + b?: string!; + ~~~~~~~ +!!! error TS8020: JSDoc types can only be used inside documentation comments. + @decorator() + c?: *; + ~ +!!! error TS8020: JSDoc types can only be used inside documentation comments. + } \ No newline at end of file diff --git a/tests/baselines/reference/decoratorMetadata-jsdoc.js b/tests/baselines/reference/decoratorMetadata-jsdoc.js new file mode 100644 index 00000000000..ff7b1acbec5 --- /dev/null +++ b/tests/baselines/reference/decoratorMetadata-jsdoc.js @@ -0,0 +1,39 @@ +//// [decoratorMetadata-jsdoc.ts] +declare var decorator: any; + +class X { + @decorator() + a?: string?; + @decorator() + b?: string!; + @decorator() + c?: *; +} + +//// [decoratorMetadata-jsdoc.js] +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var X = /** @class */ (function () { + function X() { + } + __decorate([ + decorator(), + __metadata("design:type", String) + ], X.prototype, "a", void 0); + __decorate([ + decorator(), + __metadata("design:type", String) + ], X.prototype, "b", void 0); + __decorate([ + decorator(), + __metadata("design:type", Object) + ], X.prototype, "c", void 0); + return X; +}()); diff --git a/tests/baselines/reference/decoratorMetadata-jsdoc.symbols b/tests/baselines/reference/decoratorMetadata-jsdoc.symbols new file mode 100644 index 00000000000..260214db22a --- /dev/null +++ b/tests/baselines/reference/decoratorMetadata-jsdoc.symbols @@ -0,0 +1,25 @@ +=== tests/cases/conformance/decorators/decoratorMetadata-jsdoc.ts === +declare var decorator: any; +>decorator : Symbol(decorator, Decl(decoratorMetadata-jsdoc.ts, 0, 11)) + +class X { +>X : Symbol(X, Decl(decoratorMetadata-jsdoc.ts, 0, 27)) + + @decorator() +>decorator : Symbol(decorator, Decl(decoratorMetadata-jsdoc.ts, 0, 11)) + + a?: string?; +>a : Symbol(X.a, Decl(decoratorMetadata-jsdoc.ts, 2, 9)) + + @decorator() +>decorator : Symbol(decorator, Decl(decoratorMetadata-jsdoc.ts, 0, 11)) + + b?: string!; +>b : Symbol(X.b, Decl(decoratorMetadata-jsdoc.ts, 4, 16)) + + @decorator() +>decorator : Symbol(decorator, Decl(decoratorMetadata-jsdoc.ts, 0, 11)) + + c?: *; +>c : Symbol(X.c, Decl(decoratorMetadata-jsdoc.ts, 6, 16)) +} diff --git a/tests/baselines/reference/decoratorMetadata-jsdoc.types b/tests/baselines/reference/decoratorMetadata-jsdoc.types new file mode 100644 index 00000000000..ef618b7cda0 --- /dev/null +++ b/tests/baselines/reference/decoratorMetadata-jsdoc.types @@ -0,0 +1,28 @@ +=== tests/cases/conformance/decorators/decoratorMetadata-jsdoc.ts === +declare var decorator: any; +>decorator : any + +class X { +>X : X + + @decorator() +>decorator() : any +>decorator : any + + a?: string?; +>a : string + + @decorator() +>decorator() : any +>decorator : any + + b?: string!; +>b : string + + @decorator() +>decorator() : any +>decorator : any + + c?: *; +>c : any +} diff --git a/tests/cases/conformance/decorators/decoratorMetadata-jsdoc.ts b/tests/cases/conformance/decorators/decoratorMetadata-jsdoc.ts new file mode 100644 index 00000000000..822269366bc --- /dev/null +++ b/tests/cases/conformance/decorators/decoratorMetadata-jsdoc.ts @@ -0,0 +1,14 @@ +// @experimentalDecorators: true +// @emitDecoratorMetadata: true +// @target: es5 +// @module: commonjs +declare var decorator: any; + +class X { + @decorator() + a?: string?; + @decorator() + b?: string!; + @decorator() + c?: *; +} \ No newline at end of file