From f67ee4437993bfcf469e72ae4a753a570300339c Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Mon, 19 Apr 2021 23:23:40 -0700 Subject: [PATCH] Instantiate getter when infering setter parameter value (#43564) * Instantiate getter when infering setter parameter value * Use esnext on tests * Instantiate for JsDoc and getter from body * PR comments * Updated baseline --- src/compiler/checker.ts | 39 ++++++----- .../reference/genericSetterInClassType.js | 49 ++++++++++++++ .../genericSetterInClassType.symbols | 63 +++++++++++++++++ .../reference/genericSetterInClassType.types | 67 +++++++++++++++++++ .../genericSetterInClassTypeJsDoc.js | 58 ++++++++++++++++ .../genericSetterInClassTypeJsDoc.symbols | 45 +++++++++++++ .../genericSetterInClassTypeJsDoc.types | 51 ++++++++++++++ .../classTypes/genericSetterInClassType.ts | 27 ++++++++ .../genericSetterInClassTypeJsDoc.ts | 30 +++++++++ 9 files changed, 411 insertions(+), 18 deletions(-) create mode 100644 tests/baselines/reference/genericSetterInClassType.js create mode 100644 tests/baselines/reference/genericSetterInClassType.symbols create mode 100644 tests/baselines/reference/genericSetterInClassType.types create mode 100644 tests/baselines/reference/genericSetterInClassTypeJsDoc.js create mode 100644 tests/baselines/reference/genericSetterInClassTypeJsDoc.symbols create mode 100644 tests/baselines/reference/genericSetterInClassTypeJsDoc.types create mode 100644 tests/cases/conformance/classes/members/classTypes/genericSetterInClassType.ts create mode 100644 tests/cases/conformance/classes/members/classTypes/genericSetterInClassTypeJsDoc.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 125a5be6cd6..7471ce4cdb5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9082,42 +9082,36 @@ namespace ts { const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); + const setterType = getAnnotatedAccessorType(setter); + // For write operations, prioritize type annotations on the setter - if (writing) { - const setterParameterType = getAnnotatedAccessorType(setter); - if (setterParameterType) { - const flags = getCheckFlags(symbol); - if (flags & CheckFlags.Instantiated) { - const links = getSymbolLinks(symbol); - return instantiateType(setterParameterType, links.mapper); - } - return setterParameterType; - } + if (writing && setterType) { + return instantiateTypeIfNeeded(setterType, symbol); } // Else defer to the getter type if (getter && isInJSFile(getter)) { const jsDocType = getTypeForDeclarationFromJSDocComment(getter); if (jsDocType) { - return jsDocType; + return instantiateTypeIfNeeded(jsDocType, symbol); } } // Try to see if the user specified a return type on the get-accessor. - const getterReturnType = getAnnotatedAccessorType(getter); - if (getterReturnType) { - return getterReturnType; + const getterType = getAnnotatedAccessorType(getter); + if (getterType) { + return instantiateTypeIfNeeded(getterType, symbol); } // If the user didn't specify a return type, try to use the set-accessor's parameter type. - const setterParameterType = getAnnotatedAccessorType(setter); - if (setterParameterType) { - return setterParameterType; + if (setterType) { + return setterType; } // If there are no specified types, try to infer it from the body of the get accessor if it exists. if (getter && getter.body) { - return getReturnTypeFromBody(getter); + const returnTypeFromBody = getReturnTypeFromBody(getter); + return instantiateTypeIfNeeded(returnTypeFromBody, symbol); } // Otherwise, fall back to 'any'. @@ -9135,6 +9129,15 @@ namespace ts { return anyType; } return undefined; + + function instantiateTypeIfNeeded(type: Type, symbol: Symbol) { + if (getCheckFlags(symbol) & CheckFlags.Instantiated) { + const links = getSymbolLinks(symbol); + return instantiateType(type, links.mapper); + } + + return type; + } } function getBaseTypeVariableOfClass(symbol: Symbol) { diff --git a/tests/baselines/reference/genericSetterInClassType.js b/tests/baselines/reference/genericSetterInClassType.js new file mode 100644 index 00000000000..24b006cb9f0 --- /dev/null +++ b/tests/baselines/reference/genericSetterInClassType.js @@ -0,0 +1,49 @@ +//// [genericSetterInClassType.ts] +module Generic { + class C { + get y(): T { + return 1 as never; + } + set y(v) { } + } + + var c = new C(); + c.y = c.y; + + class Box { + #value!: T; + + get value() { + return this.#value; + } + + set value(value) { + this.#value = value; + } + } + + new Box().value = 3; +} + +//// [genericSetterInClassType.js] +var Generic; +(function (Generic) { + class C { + get y() { + return 1; + } + set y(v) { } + } + var c = new C(); + c.y = c.y; + class Box { + #value; + get value() { + return this.#value; + } + set value(value) { + this.#value = value; + } + } + new Box().value = 3; +})(Generic || (Generic = {})); diff --git a/tests/baselines/reference/genericSetterInClassType.symbols b/tests/baselines/reference/genericSetterInClassType.symbols new file mode 100644 index 00000000000..61422acaea1 --- /dev/null +++ b/tests/baselines/reference/genericSetterInClassType.symbols @@ -0,0 +1,63 @@ +=== tests/cases/conformance/classes/members/classTypes/genericSetterInClassType.ts === +module Generic { +>Generic : Symbol(Generic, Decl(genericSetterInClassType.ts, 0, 0)) + + class C { +>C : Symbol(C, Decl(genericSetterInClassType.ts, 0, 16)) +>T : Symbol(T, Decl(genericSetterInClassType.ts, 1, 12)) + + get y(): T { +>y : Symbol(C.y, Decl(genericSetterInClassType.ts, 1, 16), Decl(genericSetterInClassType.ts, 4, 9)) +>T : Symbol(T, Decl(genericSetterInClassType.ts, 1, 12)) + + return 1 as never; + } + set y(v) { } +>y : Symbol(C.y, Decl(genericSetterInClassType.ts, 1, 16), Decl(genericSetterInClassType.ts, 4, 9)) +>v : Symbol(v, Decl(genericSetterInClassType.ts, 5, 14)) + } + + var c = new C(); +>c : Symbol(c, Decl(genericSetterInClassType.ts, 8, 7)) +>C : Symbol(C, Decl(genericSetterInClassType.ts, 0, 16)) + + c.y = c.y; +>c.y : Symbol(C.y, Decl(genericSetterInClassType.ts, 1, 16), Decl(genericSetterInClassType.ts, 4, 9)) +>c : Symbol(c, Decl(genericSetterInClassType.ts, 8, 7)) +>y : Symbol(C.y, Decl(genericSetterInClassType.ts, 1, 16), Decl(genericSetterInClassType.ts, 4, 9)) +>c.y : Symbol(C.y, Decl(genericSetterInClassType.ts, 1, 16), Decl(genericSetterInClassType.ts, 4, 9)) +>c : Symbol(c, Decl(genericSetterInClassType.ts, 8, 7)) +>y : Symbol(C.y, Decl(genericSetterInClassType.ts, 1, 16), Decl(genericSetterInClassType.ts, 4, 9)) + + class Box { +>Box : Symbol(Box, Decl(genericSetterInClassType.ts, 9, 14)) +>T : Symbol(T, Decl(genericSetterInClassType.ts, 11, 14)) + + #value!: T; +>#value : Symbol(Box.#value, Decl(genericSetterInClassType.ts, 11, 18)) +>T : Symbol(T, Decl(genericSetterInClassType.ts, 11, 14)) + + get value() { +>value : Symbol(Box.value, Decl(genericSetterInClassType.ts, 12, 19), Decl(genericSetterInClassType.ts, 16, 9)) + + return this.#value; +>this.#value : Symbol(Box.#value, Decl(genericSetterInClassType.ts, 11, 18)) +>this : Symbol(Box, Decl(genericSetterInClassType.ts, 9, 14)) + } + + set value(value) { +>value : Symbol(Box.value, Decl(genericSetterInClassType.ts, 12, 19), Decl(genericSetterInClassType.ts, 16, 9)) +>value : Symbol(value, Decl(genericSetterInClassType.ts, 18, 18)) + + this.#value = value; +>this.#value : Symbol(Box.#value, Decl(genericSetterInClassType.ts, 11, 18)) +>this : Symbol(Box, Decl(genericSetterInClassType.ts, 9, 14)) +>value : Symbol(value, Decl(genericSetterInClassType.ts, 18, 18)) + } + } + + new Box().value = 3; +>new Box().value : Symbol(Box.value, Decl(genericSetterInClassType.ts, 12, 19), Decl(genericSetterInClassType.ts, 16, 9)) +>Box : Symbol(Box, Decl(genericSetterInClassType.ts, 9, 14)) +>value : Symbol(Box.value, Decl(genericSetterInClassType.ts, 12, 19), Decl(genericSetterInClassType.ts, 16, 9)) +} diff --git a/tests/baselines/reference/genericSetterInClassType.types b/tests/baselines/reference/genericSetterInClassType.types new file mode 100644 index 00000000000..ef77d729046 --- /dev/null +++ b/tests/baselines/reference/genericSetterInClassType.types @@ -0,0 +1,67 @@ +=== tests/cases/conformance/classes/members/classTypes/genericSetterInClassType.ts === +module Generic { +>Generic : typeof Generic + + class C { +>C : C + + get y(): T { +>y : T + + return 1 as never; +>1 as never : never +>1 : 1 + } + set y(v) { } +>y : T +>v : T + } + + var c = new C(); +>c : C +>new C() : C +>C : typeof C + + c.y = c.y; +>c.y = c.y : number +>c.y : number +>c : C +>y : number +>c.y : number +>c : C +>y : number + + class Box { +>Box : Box + + #value!: T; +>#value : T + + get value() { +>value : T + + return this.#value; +>this.#value : T +>this : this + } + + set value(value) { +>value : T +>value : T + + this.#value = value; +>this.#value = value : T +>this.#value : T +>this : this +>value : T + } + } + + new Box().value = 3; +>new Box().value = 3 : 3 +>new Box().value : number +>new Box() : Box +>Box : typeof Box +>value : number +>3 : 3 +} diff --git a/tests/baselines/reference/genericSetterInClassTypeJsDoc.js b/tests/baselines/reference/genericSetterInClassTypeJsDoc.js new file mode 100644 index 00000000000..c02b7cfeeb6 --- /dev/null +++ b/tests/baselines/reference/genericSetterInClassTypeJsDoc.js @@ -0,0 +1,58 @@ +//// [genericSetterInClassTypeJsDoc.js] +/** + * @template T + */ + class Box { + #value; + + /** @param {T} initialValue */ + constructor(initialValue) { + this.#value = initialValue; + } + + /** @type {T} */ + get value() { + return this.#value; + } + + set value(value) { + this.#value = value; + } +} + +new Box(3).value = 3; + + +//// [genericSetterInClassTypeJsDoc-out.js] +/** + * @template T + */ +class Box { + #value; + /** @param {T} initialValue */ + constructor(initialValue) { + this.#value = initialValue; + } + /** @type {T} */ + get value() { + return this.#value; + } + set value(value) { + this.#value = value; + } +} +new Box(3).value = 3; + + +//// [genericSetterInClassTypeJsDoc-out.d.ts] +/** + * @template T + */ +declare class Box { + /** @param {T} initialValue */ + constructor(initialValue: T); + set value(arg: T); + /** @type {T} */ + get value(): T; + #private; +} diff --git a/tests/baselines/reference/genericSetterInClassTypeJsDoc.symbols b/tests/baselines/reference/genericSetterInClassTypeJsDoc.symbols new file mode 100644 index 00000000000..dc3e2436c1c --- /dev/null +++ b/tests/baselines/reference/genericSetterInClassTypeJsDoc.symbols @@ -0,0 +1,45 @@ +=== tests/cases/conformance/classes/members/classTypes/genericSetterInClassTypeJsDoc.js === +/** + * @template T + */ + class Box { +>Box : Symbol(Box, Decl(genericSetterInClassTypeJsDoc.js, 0, 0)) + + #value; +>#value : Symbol(Box.#value, Decl(genericSetterInClassTypeJsDoc.js, 3, 12)) + + /** @param {T} initialValue */ + constructor(initialValue) { +>initialValue : Symbol(initialValue, Decl(genericSetterInClassTypeJsDoc.js, 7, 16)) + + this.#value = initialValue; +>this.#value : Symbol(Box.#value, Decl(genericSetterInClassTypeJsDoc.js, 3, 12)) +>this : Symbol(Box, Decl(genericSetterInClassTypeJsDoc.js, 0, 0)) +>initialValue : Symbol(initialValue, Decl(genericSetterInClassTypeJsDoc.js, 7, 16)) + } + + /** @type {T} */ + get value() { +>value : Symbol(Box.value, Decl(genericSetterInClassTypeJsDoc.js, 9, 5), Decl(genericSetterInClassTypeJsDoc.js, 14, 5)) + + return this.#value; +>this.#value : Symbol(Box.#value, Decl(genericSetterInClassTypeJsDoc.js, 3, 12)) +>this : Symbol(Box, Decl(genericSetterInClassTypeJsDoc.js, 0, 0)) + } + + set value(value) { +>value : Symbol(Box.value, Decl(genericSetterInClassTypeJsDoc.js, 9, 5), Decl(genericSetterInClassTypeJsDoc.js, 14, 5)) +>value : Symbol(value, Decl(genericSetterInClassTypeJsDoc.js, 16, 14)) + + this.#value = value; +>this.#value : Symbol(Box.#value, Decl(genericSetterInClassTypeJsDoc.js, 3, 12)) +>this : Symbol(Box, Decl(genericSetterInClassTypeJsDoc.js, 0, 0)) +>value : Symbol(value, Decl(genericSetterInClassTypeJsDoc.js, 16, 14)) + } +} + +new Box(3).value = 3; +>new Box(3).value : Symbol(Box.value, Decl(genericSetterInClassTypeJsDoc.js, 9, 5), Decl(genericSetterInClassTypeJsDoc.js, 14, 5)) +>Box : Symbol(Box, Decl(genericSetterInClassTypeJsDoc.js, 0, 0)) +>value : Symbol(Box.value, Decl(genericSetterInClassTypeJsDoc.js, 9, 5), Decl(genericSetterInClassTypeJsDoc.js, 14, 5)) + diff --git a/tests/baselines/reference/genericSetterInClassTypeJsDoc.types b/tests/baselines/reference/genericSetterInClassTypeJsDoc.types new file mode 100644 index 00000000000..e61a5449c0e --- /dev/null +++ b/tests/baselines/reference/genericSetterInClassTypeJsDoc.types @@ -0,0 +1,51 @@ +=== tests/cases/conformance/classes/members/classTypes/genericSetterInClassTypeJsDoc.js === +/** + * @template T + */ + class Box { +>Box : Box + + #value; +>#value : T + + /** @param {T} initialValue */ + constructor(initialValue) { +>initialValue : T + + this.#value = initialValue; +>this.#value = initialValue : T +>this.#value : T +>this : this +>initialValue : T + } + + /** @type {T} */ + get value() { +>value : T + + return this.#value; +>this.#value : T +>this : this + } + + set value(value) { +>value : T +>value : T + + this.#value = value; +>this.#value = value : T +>this.#value : T +>this : this +>value : T + } +} + +new Box(3).value = 3; +>new Box(3).value = 3 : 3 +>new Box(3).value : number +>new Box(3) : Box +>Box : typeof Box +>3 : 3 +>value : number +>3 : 3 + diff --git a/tests/cases/conformance/classes/members/classTypes/genericSetterInClassType.ts b/tests/cases/conformance/classes/members/classTypes/genericSetterInClassType.ts new file mode 100644 index 00000000000..ce695924992 --- /dev/null +++ b/tests/cases/conformance/classes/members/classTypes/genericSetterInClassType.ts @@ -0,0 +1,27 @@ +// @target: esnext + +module Generic { + class C { + get y(): T { + return 1 as never; + } + set y(v) { } + } + + var c = new C(); + c.y = c.y; + + class Box { + #value!: T; + + get value() { + return this.#value; + } + + set value(value) { + this.#value = value; + } + } + + new Box().value = 3; +} \ No newline at end of file diff --git a/tests/cases/conformance/classes/members/classTypes/genericSetterInClassTypeJsDoc.ts b/tests/cases/conformance/classes/members/classTypes/genericSetterInClassTypeJsDoc.ts new file mode 100644 index 00000000000..e035eddc448 --- /dev/null +++ b/tests/cases/conformance/classes/members/classTypes/genericSetterInClassTypeJsDoc.ts @@ -0,0 +1,30 @@ +// @target: esnext +// @lib: esnext +// @declaration: true +// @allowJs: true +// @checkJs: true +// @filename: genericSetterInClassTypeJsDoc.js +// @out: genericSetterInClassTypeJsDoc-out.js + +/** + * @template T + */ + class Box { + #value; + + /** @param {T} initialValue */ + constructor(initialValue) { + this.#value = initialValue; + } + + /** @type {T} */ + get value() { + return this.#value; + } + + set value(value) { + this.#value = value; + } +} + +new Box(3).value = 3;