diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index de44e8bb37ca7..49c26e67f9076 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8687,21 +8687,17 @@ namespace ts { * the type of this reference is just the type of the value we resolved to. */ function getJSDocTypeReference(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[] | undefined): Type | undefined { - if (!pushTypeResolution(symbol, TypeSystemPropertyName.JSDocTypeReference)) { - return errorType; - } - const assignedType = getAssignedClassType(symbol); - const valueType = getTypeOfSymbol(symbol); - const referenceType = valueType.symbol && valueType.symbol !== symbol && !isInferredClassType(valueType) && getTypeReferenceTypeWorker(node, valueType.symbol, typeArguments); - if (!popTypeResolution()) { - getSymbolLinks(symbol).resolvedJSDocType = errorType; - error(node, Diagnostics.JSDoc_type_0_circularly_references_itself, symbolToString(symbol)); - return errorType; - } - if (referenceType || assignedType) { - // TODO: GH#18217 (should the `|| assignedType` be at a lower precedence?) - const type = (referenceType && assignedType ? getIntersectionType([assignedType, referenceType]) : referenceType || assignedType)!; - return getSymbolLinks(symbol).resolvedJSDocType = type; + // In the case of an assignment of a function expression (binary expressions, variable declarations, etc.), we will get the + // correct instance type for the symbol on the LHS by finding the type for RHS. For example if we want to get the type of the symbol `foo`: + // var foo = function() {} + // We will find the static type of the assigned anonymous function. + const staticType = getTypeOfSymbol(symbol); + const instanceType = + staticType.symbol && + staticType.symbol !== symbol && // Make sure this is an assignment like expression by checking that symbol -> type -> symbol doesn't roundtrips. + getTypeReferenceTypeWorker(node, staticType.symbol, typeArguments); // Get the instance type of the RHS symbol. + if (instanceType) { + return getSymbolLinks(symbol).resolvedJSDocType = instanceType; } } @@ -8722,8 +8718,11 @@ namespace ts { if (symbol.flags & SymbolFlags.Function && isJSDocTypeReference(node) && - (symbol.members || getJSDocClassTag(symbol.valueDeclaration))) { - return getInferredClassType(symbol); + isJSConstructor(symbol.valueDeclaration)) { + const resolved = resolveStructuredTypeMembers(getTypeOfSymbol(symbol)); + if (resolved.callSignatures.length === 1) { + return getReturnTypeOfSignature(resolved.callSignatures[0]); + } } } @@ -16735,7 +16734,7 @@ namespace ts { else if (isInJS && (container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) && getJSDocClassTag(container)) { - const classType = getJSClassType(container.symbol); + const classType = getJSClassType(getMergedSymbol(container.symbol)); if (classType) { return getFlowTypeOfReference(node, classType); } @@ -20893,7 +20892,7 @@ namespace ts { // If the symbol of the node has members, treat it like a constructor. const symbol = getSymbolOfNode(func); - return !!symbol && symbol.members !== undefined; + return !!symbol && (symbol.members !== undefined || symbol.exports !== undefined && symbol.exports.get("prototype" as __String) !== undefined); } return false; } @@ -20912,10 +20911,6 @@ namespace ts { inferred = getInferredClassType(symbol); } const assigned = getAssignedClassType(symbol); - const valueType = getTypeOfSymbol(symbol); - if (valueType.symbol && !isInferredClassType(valueType) && isJSConstructor(valueType.symbol.valueDeclaration)) { - inferred = getInferredClassType(valueType.symbol); - } return assigned && inferred ? getIntersectionType([inferred, assigned]) : assigned || inferred; @@ -20955,12 +20950,6 @@ namespace ts { return links.inferredClassType; } - function isInferredClassType(type: Type) { - return type.symbol - && getObjectFlags(type) & ObjectFlags.Anonymous - && getSymbolLinks(type.symbol).inferredClassType === type; - } - /** * Syntactically and semantically checks a call or new expression. * @param node The call/new expression to be checked. @@ -20982,21 +20971,10 @@ namespace ts { declaration.kind !== SyntaxKind.Constructor && declaration.kind !== SyntaxKind.ConstructSignature && declaration.kind !== SyntaxKind.ConstructorType && - !isJSDocConstructSignature(declaration)) { + !isJSDocConstructSignature(declaration) && + !isJSConstructor(declaration)) { - // When resolved signature is a call signature (and not a construct signature) the result type is any, unless - // the declaring function had members created through 'x.prototype.y = expr' or 'this.y = expr' psuedodeclarations - // in a JS file - // Note:JS inferred classes might come from a variable declaration instead of a function declaration. - // In this case, using getResolvedSymbol directly is required to avoid losing the members from the declaration. - let funcSymbol = checkExpression(node.expression).symbol; - if (!funcSymbol && node.expression.kind === SyntaxKind.Identifier) { - funcSymbol = getResolvedSymbol(node.expression as Identifier); - } - const type = funcSymbol && getJSClassType(funcSymbol); - if (type) { - return signature.target ? instantiateType(type, signature.mapper) : type; - } + // When resolved signature is a call signature (and not a construct signature) the result type is any if (noImplicitAny) { error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type); } diff --git a/tests/baselines/reference/classCanExtendConstructorFunction.types b/tests/baselines/reference/classCanExtendConstructorFunction.types index f42366976fc39..c9ac9c76ebf37 100644 --- a/tests/baselines/reference/classCanExtendConstructorFunction.types +++ b/tests/baselines/reference/classCanExtendConstructorFunction.types @@ -269,8 +269,8 @@ soup.flavour >flavour : number var chowder = new Chowder({ claim: "ignorant" }); ->chowder : any ->new Chowder({ claim: "ignorant" }) : any +>chowder : Chowder +>new Chowder({ claim: "ignorant" }) : Chowder >Chowder : typeof Chowder >{ claim: "ignorant" } : { claim: "ignorant"; } >claim : "ignorant" @@ -279,7 +279,7 @@ var chowder = new Chowder({ claim: "ignorant" }); chowder.flavour.claim >chowder.flavour.claim : any >chowder.flavour : any ->chowder : any +>chowder : Chowder >flavour : any >claim : any diff --git a/tests/baselines/reference/jsContainerMergeJsContainer.types b/tests/baselines/reference/jsContainerMergeJsContainer.types index 73e61aded0df9..56cb598269dbc 100644 --- a/tests/baselines/reference/jsContainerMergeJsContainer.types +++ b/tests/baselines/reference/jsContainerMergeJsContainer.types @@ -5,19 +5,19 @@ const a = {}; >{} : {} a.d = function() {}; ->a.d = function() {} : { (): void; prototype: {}; } ->a.d : { (): void; prototype: {}; } +>a.d = function() {} : typeof d +>a.d : typeof d >a : typeof a ->d : { (): void; prototype: {}; } ->function() {} : { (): void; prototype: {}; } +>d : typeof d +>function() {} : typeof d === tests/cases/conformance/salsa/b.js === a.d.prototype = {}; >a.d.prototype = {} : {} >a.d.prototype : {} ->a.d : { (): void; prototype: {}; } +>a.d : typeof d >a : typeof a ->d : { (): void; prototype: {}; } +>d : typeof d >prototype : {} >{} : {} diff --git a/tests/baselines/reference/typeFromPropertyAssignment14.types b/tests/baselines/reference/typeFromPropertyAssignment14.types index bc1b2a69be9b0..bb8169db35949 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment14.types +++ b/tests/baselines/reference/typeFromPropertyAssignment14.types @@ -5,18 +5,18 @@ var Outer = {}; === tests/cases/conformance/salsa/work.js === Outer.Inner = function () {} ->Outer.Inner = function () {} : { (): void; prototype: { x: number; m(): void; }; } ->Outer.Inner : { (): void; prototype: { x: number; m(): void; }; } +>Outer.Inner = function () {} : typeof Inner +>Outer.Inner : typeof Inner >Outer : typeof Outer ->Inner : { (): void; prototype: { x: number; m(): void; }; } ->function () {} : { (): void; prototype: { x: number; m(): void; }; } +>Inner : typeof Inner +>function () {} : typeof Inner Outer.Inner.prototype = { >Outer.Inner.prototype = { x: 1, m() { }} : { x: number; m(): void; } >Outer.Inner.prototype : { x: number; m(): void; } ->Outer.Inner : { (): void; prototype: { x: number; m(): void; }; } +>Outer.Inner : typeof Inner >Outer : typeof Outer ->Inner : { (): void; prototype: { x: number; m(): void; }; } +>Inner : typeof Inner >prototype : { x: number; m(): void; } >{ x: 1, m() { }} : { x: number; m(): void; } @@ -47,9 +47,9 @@ inner.m() var inno = new Outer.Inner() >inno : { x: number; m(): void; } >new Outer.Inner() : { x: number; m(): void; } ->Outer.Inner : { (): void; prototype: { x: number; m(): void; }; } +>Outer.Inner : typeof Inner >Outer : typeof Outer ->Inner : { (): void; prototype: { x: number; m(): void; }; } +>Inner : typeof Inner inno.x >inno.x : number diff --git a/tests/baselines/reference/typeFromPropertyAssignment16.types b/tests/baselines/reference/typeFromPropertyAssignment16.types index f2cb476e5fa30..1d94d096ea02c 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment16.types +++ b/tests/baselines/reference/typeFromPropertyAssignment16.types @@ -4,18 +4,18 @@ var Outer = {}; >{} : {} Outer.Inner = function () {} ->Outer.Inner = function () {} : { (): void; prototype: { x: number; m(): void; }; } ->Outer.Inner : { (): void; prototype: { x: number; m(): void; }; } +>Outer.Inner = function () {} : typeof Inner +>Outer.Inner : typeof Inner >Outer : typeof Outer ->Inner : { (): void; prototype: { x: number; m(): void; }; } ->function () {} : { (): void; prototype: { x: number; m(): void; }; } +>Inner : typeof Inner +>function () {} : typeof Inner Outer.Inner.prototype = { >Outer.Inner.prototype = { x: 1, m() { }} : { x: number; m(): void; } >Outer.Inner.prototype : { x: number; m(): void; } ->Outer.Inner : { (): void; prototype: { x: number; m(): void; }; } +>Outer.Inner : typeof Inner >Outer : typeof Outer ->Inner : { (): void; prototype: { x: number; m(): void; }; } +>Inner : typeof Inner >prototype : { x: number; m(): void; } >{ x: 1, m() { }} : { x: number; m(): void; } @@ -45,9 +45,9 @@ inner.m() var inno = new Outer.Inner() >inno : { x: number; m(): void; } >new Outer.Inner() : { x: number; m(): void; } ->Outer.Inner : { (): void; prototype: { x: number; m(): void; }; } +>Outer.Inner : typeof Inner >Outer : typeof Outer ->Inner : { (): void; prototype: { x: number; m(): void; }; } +>Inner : typeof Inner inno.x >inno.x : number diff --git a/tests/baselines/reference/typeFromPrototypeAssignment3.errors.txt b/tests/baselines/reference/typeFromPrototypeAssignment3.errors.txt new file mode 100644 index 0000000000000..579c3efac7265 --- /dev/null +++ b/tests/baselines/reference/typeFromPrototypeAssignment3.errors.txt @@ -0,0 +1,26 @@ +tests/cases/conformance/salsa/bug26885.js(2,5): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +tests/cases/conformance/salsa/bug26885.js(11,16): error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. + + +==== tests/cases/conformance/salsa/bug26885.js (2 errors) ==== + function Multimap3() { + this._map = {}; + ~~~~ +!!! error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. + }; + + Multimap3.prototype = { + /** + * @param {string} key + * @returns {number} the value ok + */ + get(key) { + return this._map[key + '']; + ~~~~~~~~~~~~~~~~~~~ +!!! error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. + } + } + + /** @type {Multimap3} */ + const map = new Multimap3(); + const n = map.get('hi') \ No newline at end of file diff --git a/tests/baselines/reference/typeFromPrototypeAssignment3.symbols b/tests/baselines/reference/typeFromPrototypeAssignment3.symbols new file mode 100644 index 0000000000000..65b72e9441b7c --- /dev/null +++ b/tests/baselines/reference/typeFromPrototypeAssignment3.symbols @@ -0,0 +1,40 @@ +=== tests/cases/conformance/salsa/bug26885.js === +function Multimap3() { +>Multimap3 : Symbol(Multimap3, Decl(bug26885.js, 0, 0), Decl(bug26885.js, 2, 2)) + + this._map = {}; +>_map : Symbol(Multimap3._map, Decl(bug26885.js, 0, 22)) + +}; + +Multimap3.prototype = { +>Multimap3.prototype : Symbol(Multimap3.prototype, Decl(bug26885.js, 2, 2)) +>Multimap3 : Symbol(Multimap3, Decl(bug26885.js, 0, 0), Decl(bug26885.js, 2, 2)) +>prototype : Symbol(Multimap3.prototype, Decl(bug26885.js, 2, 2)) + + /** + * @param {string} key + * @returns {number} the value ok + */ + get(key) { +>get : Symbol(get, Decl(bug26885.js, 4, 23)) +>key : Symbol(key, Decl(bug26885.js, 9, 8)) + + return this._map[key + '']; +>this._map : Symbol(Multimap3._map, Decl(bug26885.js, 0, 22)) +>_map : Symbol(Multimap3._map, Decl(bug26885.js, 0, 22)) +>key : Symbol(key, Decl(bug26885.js, 9, 8)) + } +} + +/** @type {Multimap3} */ +const map = new Multimap3(); +>map : Symbol(map, Decl(bug26885.js, 15, 5)) +>Multimap3 : Symbol(Multimap3, Decl(bug26885.js, 0, 0), Decl(bug26885.js, 2, 2)) + +const n = map.get('hi') +>n : Symbol(n, Decl(bug26885.js, 16, 5)) +>map.get : Symbol(get, Decl(bug26885.js, 4, 23)) +>map : Symbol(map, Decl(bug26885.js, 15, 5)) +>get : Symbol(get, Decl(bug26885.js, 4, 23)) + diff --git a/tests/baselines/reference/typeFromPrototypeAssignment3.types b/tests/baselines/reference/typeFromPrototypeAssignment3.types new file mode 100644 index 0000000000000..82fef59c42afc --- /dev/null +++ b/tests/baselines/reference/typeFromPrototypeAssignment3.types @@ -0,0 +1,53 @@ +=== tests/cases/conformance/salsa/bug26885.js === +function Multimap3() { +>Multimap3 : typeof Multimap3 + + this._map = {}; +>this._map = {} : {} +>this._map : any +>this : any +>_map : any +>{} : {} + +}; + +Multimap3.prototype = { +>Multimap3.prototype = { /** * @param {string} key * @returns {number} the value ok */ get(key) { return this._map[key + '']; }} : { get(key: string): number; } +>Multimap3.prototype : { get(key: string): number; } +>Multimap3 : typeof Multimap3 +>prototype : { get(key: string): number; } +>{ /** * @param {string} key * @returns {number} the value ok */ get(key) { return this._map[key + '']; }} : { get(key: string): number; } + + /** + * @param {string} key + * @returns {number} the value ok + */ + get(key) { +>get : (key: string) => number +>key : string + + return this._map[key + '']; +>this._map[key + ''] : any +>this._map : {} +>this : Multimap3 & { get(key: string): number; } +>_map : {} +>key + '' : string +>key : string +>'' : "" + } +} + +/** @type {Multimap3} */ +const map = new Multimap3(); +>map : Multimap3 & { get(key: string): number; } +>new Multimap3() : Multimap3 & { get(key: string): number; } +>Multimap3 : typeof Multimap3 + +const n = map.get('hi') +>n : number +>map.get('hi') : number +>map.get : (key: string) => number +>map : Multimap3 & { get(key: string): number; } +>get : (key: string) => number +>'hi' : "hi" + diff --git a/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.errors.txt b/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.errors.txt index 2469e647685eb..d98cb9297dc37 100644 --- a/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.errors.txt +++ b/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.errors.txt @@ -1,14 +1,11 @@ tests/cases/conformance/jsdoc/bug27346.js(2,4): error TS8030: The type of a function declaration must match the function's signature. -tests/cases/conformance/jsdoc/bug27346.js(2,11): error TS2587: JSDoc type 'MyClass' circularly references itself. -==== tests/cases/conformance/jsdoc/bug27346.js (2 errors) ==== +==== tests/cases/conformance/jsdoc/bug27346.js (1 errors) ==== /** * @type {MyClass} ~~~~~~~~~~~~~~~ !!! error TS8030: The type of a function declaration must match the function's signature. - ~~~~~~~ -!!! error TS2587: JSDoc type 'MyClass' circularly references itself. */ function MyClass() { } MyClass.prototype = {}; diff --git a/tests/cases/conformance/salsa/typeFromPrototypeAssignment3.ts b/tests/cases/conformance/salsa/typeFromPrototypeAssignment3.ts new file mode 100644 index 0000000000000..00475db6cc1b5 --- /dev/null +++ b/tests/cases/conformance/salsa/typeFromPrototypeAssignment3.ts @@ -0,0 +1,23 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: bug26885.js +// @strict: true + +function Multimap3() { + this._map = {}; +}; + +Multimap3.prototype = { + /** + * @param {string} key + * @returns {number} the value ok + */ + get(key) { + return this._map[key + '']; + } +} + +/** @type {Multimap3} */ +const map = new Multimap3(); +const n = map.get('hi') \ No newline at end of file