diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cb538a3689761..1a4f3bb6a0b5b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5622,9 +5622,6 @@ namespace ts { function getConstructorsForTypeArguments(type: Type, typeArgumentNodes: ReadonlyArray | undefined, location: Node): ReadonlyArray { const typeArgCount = length(typeArgumentNodes); const isJavascript = isInJSFile(location); - if (isJSConstructorType(type) && !typeArgCount) { - return getSignaturesOfType(type, SignatureKind.Call); - } return filter(getSignaturesOfType(type, SignatureKind.Construct), sig => (isJavascript || typeArgCount >= getMinTypeArgumentCount(sig.typeParameters)) && typeArgCount <= length(sig.typeParameters)); } @@ -5706,7 +5703,9 @@ namespace ts { const baseTypeNode = getBaseTypeNodeOfClass(type)!; const typeArgs = typeArgumentsFromTypeReferenceNode(baseTypeNode); let baseType: Type; - const originalBaseType = baseConstructorType && baseConstructorType.symbol ? getDeclaredTypeOfSymbol(baseConstructorType.symbol) : undefined; + const originalBaseType = isJSConstructorType(baseConstructorType) ? baseConstructorType : + baseConstructorType.symbol ? getDeclaredTypeOfSymbol(baseConstructorType.symbol) : + undefined; if (baseConstructorType.symbol && baseConstructorType.symbol.flags & SymbolFlags.Class && areAllOuterTypeParametersApplied(originalBaseType!)) { // When base constructor type is a class with no captured type arguments we know that the constructors all have the same type parameters as the @@ -5717,8 +5716,8 @@ namespace ts { else if (baseConstructorType.flags & TypeFlags.Any) { baseType = baseConstructorType; } - else if (isJSConstructorType(baseConstructorType) && !baseTypeNode.typeArguments) { - baseType = getJSClassType(baseConstructorType.symbol) || anyType; + else if (isJSConstructorType(baseConstructorType)) { + baseType = !baseTypeNode.typeArguments && getJSClassType(baseConstructorType.symbol) || anyType; } else { // The class derives from a "class-like" constructor function, check that we have at least one construct signature @@ -6730,6 +6729,7 @@ namespace ts { // will never be observed because a qualified name can't reference signatures. if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { type.callSignatures = getSignaturesOfSymbol(symbol); + type.constructSignatures = filter(type.callSignatures, sig => isJSConstructor(sig.declaration)); } // And likewise for construct signatures for classes if (symbol.flags & SymbolFlags.Class) { @@ -7866,6 +7866,7 @@ namespace ts { let type = signature.target ? instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper!) : signature.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) : getReturnTypeFromAnnotation(signature.declaration!) || + isJSConstructor(signature.declaration) && getJSClassType(getSymbolOfNode(signature.declaration!)) || (nodeIsMissing((signature.declaration).body) ? anyType : getReturnTypeFromBody(signature.declaration)); if (!popTypeResolution()) { if (signature.declaration) { @@ -15451,12 +15452,9 @@ namespace ts { } if (!targetType) { - let constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct); - if (constructSignatures.length === 0) { - constructSignatures = filter(getSignaturesOfType(rightType, SignatureKind.Call), sig => isJSConstructor(sig.declaration)); - } + const constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct); targetType = constructSignatures.length ? - getUnionType(map(constructSignatures, signature => isJSConstructor(signature.declaration) && getJSClassType(getSymbolOfNode(signature.declaration!)) || getReturnTypeOfSignature(getErasedSignature(signature)))) : + getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))) : emptyObjectType; } @@ -20039,12 +20037,12 @@ namespace ts { // Function interface, since they have none by default. This is a bit of a leap of faith // that the user will not add any. const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); - const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; // TS 1.0 Spec: 4.12 // In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual // types are provided for the argument expressions, and the result is always of type Any. - if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, constructSignatures.length)) { + if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { // The unknownType indicates that an error already occurred (and was reported). No // need to report another error in this case. if (funcType !== errorType && node.typeArguments) { @@ -20056,7 +20054,7 @@ namespace ts { // TypeScript employs overload resolution in typed function calls in order to support functions // with multiple call signatures. if (!callSignatures.length) { - if (constructSignatures.length) { + if (numConstructSignatures) { error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); } else { @@ -20272,9 +20270,9 @@ namespace ts { } const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); - const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; - if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, constructSignatures.length)) { + if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, numConstructSignatures)) { return resolveUntypedCall(node); } @@ -20322,8 +20320,8 @@ namespace ts { } const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); - const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct); - if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, constructSignatures.length)) { + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { return resolveUntypedCall(node); } diff --git a/tests/baselines/reference/classCanExtendConstructorFunction.errors.txt b/tests/baselines/reference/classCanExtendConstructorFunction.errors.txt index b3e2f83628375..12dacb9efde54 100644 --- a/tests/baselines/reference/classCanExtendConstructorFunction.errors.txt +++ b/tests/baselines/reference/classCanExtendConstructorFunction.errors.txt @@ -2,9 +2,8 @@ tests/cases/conformance/salsa/first.js(23,9): error TS2554: Expected 1 arguments tests/cases/conformance/salsa/first.js(31,5): error TS2416: Property 'load' in type 'Sql' is not assignable to the same property in base type 'Wagon'. Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[]) => void'. tests/cases/conformance/salsa/first.js(47,24): error TS2507: Type '(numberEaten: number) => void' is not a constructor function type. -tests/cases/conformance/salsa/generic.js(8,15): error TS2508: No base constructor has the specified number of type arguments. -tests/cases/conformance/salsa/generic.js(11,21): error TS2339: Property 'flavour' does not exist on type 'Chowder'. -tests/cases/conformance/salsa/generic.js(18,9): error TS2339: Property 'flavour' does not exist on type 'Chowder'. +tests/cases/conformance/salsa/generic.js(19,19): error TS2554: Expected 1 arguments, but got 0. +tests/cases/conformance/salsa/generic.js(20,32): error TS2345: Argument of type '0' is not assignable to parameter of type '{ claim: "ignorant" | "malicious"; }'. tests/cases/conformance/salsa/second.ts(8,25): error TS2507: Type '(numberEaten: number) => void' is not a constructor function type. tests/cases/conformance/salsa/second.ts(14,7): error TS2417: Class static side 'typeof Conestoga' incorrectly extends base class static side 'typeof Wagon'. Types of property 'circle' are incompatible. @@ -115,7 +114,7 @@ tests/cases/conformance/salsa/second.ts(17,15): error TS2345: Argument of type ' c.drunkOO c.numberOxen -==== tests/cases/conformance/salsa/generic.js (3 errors) ==== +==== tests/cases/conformance/salsa/generic.js (2 errors) ==== /** * @template T * @param {T} flavour @@ -124,21 +123,22 @@ tests/cases/conformance/salsa/second.ts(17,15): error TS2345: Argument of type ' this.flavour = flavour } /** @extends {Soup<{ claim: "ignorant" | "malicious" }>} */ - ~~~~ -!!! error TS2508: No base constructor has the specified number of type arguments. class Chowder extends Soup { log() { return this.flavour - ~~~~~~~ -!!! error TS2339: Property 'flavour' does not exist on type 'Chowder'. } } var soup = new Soup(1); soup.flavour - var chowder = new Chowder(); + var chowder = new Chowder({ claim: "ignorant" }); chowder.flavour.claim - ~~~~~~~ -!!! error TS2339: Property 'flavour' does not exist on type 'Chowder'. + var errorNoArgs = new Chowder(); + ~~~~~~~~~~~~~ +!!! error TS2554: Expected 1 arguments, but got 0. +!!! related TS6210 tests/cases/conformance/salsa/generic.js:5:15: An argument for 'flavour' was not provided. + var errorArgType = new Chowder(0); + ~ +!!! error TS2345: Argument of type '0' is not assignable to parameter of type '{ claim: "ignorant" | "malicious"; }'. \ No newline at end of file diff --git a/tests/baselines/reference/classCanExtendConstructorFunction.symbols b/tests/baselines/reference/classCanExtendConstructorFunction.symbols index 2a794718f4cda..bd935a8a5b809 100644 --- a/tests/baselines/reference/classCanExtendConstructorFunction.symbols +++ b/tests/baselines/reference/classCanExtendConstructorFunction.symbols @@ -218,11 +218,20 @@ soup.flavour >soup : Symbol(soup, Decl(generic.js, 14, 3)) >flavour : Symbol(Soup.flavour, Decl(generic.js, 4, 24)) -var chowder = new Chowder(); +var chowder = new Chowder({ claim: "ignorant" }); >chowder : Symbol(chowder, Decl(generic.js, 16, 3)) >Chowder : Symbol(Chowder, Decl(generic.js, 6, 1)) +>claim : Symbol(claim, Decl(generic.js, 16, 27)) chowder.flavour.claim >chowder : Symbol(chowder, Decl(generic.js, 16, 3)) +var errorNoArgs = new Chowder(); +>errorNoArgs : Symbol(errorNoArgs, Decl(generic.js, 18, 3)) +>Chowder : Symbol(Chowder, Decl(generic.js, 6, 1)) + +var errorArgType = new Chowder(0); +>errorArgType : Symbol(errorArgType, Decl(generic.js, 19, 3)) +>Chowder : Symbol(Chowder, Decl(generic.js, 6, 1)) + diff --git a/tests/baselines/reference/classCanExtendConstructorFunction.types b/tests/baselines/reference/classCanExtendConstructorFunction.types index e1d4498ff7fd2..f42366976fc39 100644 --- a/tests/baselines/reference/classCanExtendConstructorFunction.types +++ b/tests/baselines/reference/classCanExtendConstructorFunction.types @@ -268,16 +268,30 @@ soup.flavour >soup : typeof Soup >flavour : number -var chowder = new Chowder(); ->chowder : Chowder ->new Chowder() : Chowder +var chowder = new Chowder({ claim: "ignorant" }); +>chowder : any +>new Chowder({ claim: "ignorant" }) : any >Chowder : typeof Chowder +>{ claim: "ignorant" } : { claim: "ignorant"; } +>claim : "ignorant" +>"ignorant" : "ignorant" chowder.flavour.claim >chowder.flavour.claim : any >chowder.flavour : any ->chowder : Chowder +>chowder : any >flavour : any >claim : any +var errorNoArgs = new Chowder(); +>errorNoArgs : any +>new Chowder() : any +>Chowder : typeof Chowder + +var errorArgType = new Chowder(0); +>errorArgType : any +>new Chowder(0) : any +>Chowder : typeof Chowder +>0 : 0 + diff --git a/tests/baselines/reference/constructorFunctions.errors.txt b/tests/baselines/reference/constructorFunctions.errors.txt index 1464b1a874c63..3957bb952fdd6 100644 --- a/tests/baselines/reference/constructorFunctions.errors.txt +++ b/tests/baselines/reference/constructorFunctions.errors.txt @@ -25,7 +25,7 @@ tests/cases/conformance/salsa/index.js(55,13): error TS2554: Expected 1 argument if (!(this instanceof C3)) return new C3(); }; - const c3_v1 = C3(); + const c3_v1 = C3(); // error: @class tag requires 'new' ~~~~ !!! error TS2348: Value of type 'typeof C3' is not callable. Did you mean to include 'new'? const c3_v2 = new C3(); @@ -35,7 +35,7 @@ tests/cases/conformance/salsa/index.js(55,13): error TS2554: Expected 1 argument if (!(this instanceof C4)) return new C4(); }; - const c4_v1 = C4(); + const c4_v1 = C4(); // error: @class tag requires 'new' ~~~~ !!! error TS2348: Value of type 'typeof C4' is not callable. Did you mean to include 'new'? const c4_v2 = new C4(); diff --git a/tests/baselines/reference/constructorFunctions.symbols b/tests/baselines/reference/constructorFunctions.symbols index 83e514b0b8d55..590d4b9b8db2b 100644 --- a/tests/baselines/reference/constructorFunctions.symbols +++ b/tests/baselines/reference/constructorFunctions.symbols @@ -49,7 +49,7 @@ function C3() { }; -const c3_v1 = C3(); +const c3_v1 = C3(); // error: @class tag requires 'new' >c3_v1 : Symbol(c3_v1, Decl(index.js, 21, 5)) >C3 : Symbol(C3, Decl(index.js, 14, 23)) @@ -68,7 +68,7 @@ var C4 = function () { }; -const c4_v1 = C4(); +const c4_v1 = C4(); // error: @class tag requires 'new' >c4_v1 : Symbol(c4_v1, Decl(index.js, 29, 5)) >C4 : Symbol(C4, Decl(index.js, 25, 3)) diff --git a/tests/baselines/reference/constructorFunctions.types b/tests/baselines/reference/constructorFunctions.types index 4ae813076ef33..d5bd442543518 100644 --- a/tests/baselines/reference/constructorFunctions.types +++ b/tests/baselines/reference/constructorFunctions.types @@ -76,9 +76,9 @@ function C3() { }; -const c3_v1 = C3(); ->c3_v1 : C3 ->C3() : C3 +const c3_v1 = C3(); // error: @class tag requires 'new' +>c3_v1 : any +>C3() : any >C3 : typeof C3 const c3_v2 = new C3(); @@ -102,9 +102,9 @@ var C4 = function () { }; -const c4_v1 = C4(); ->c4_v1 : C4 ->C4() : C4 +const c4_v1 = C4(); // error: @class tag requires 'new' +>c4_v1 : any +>C4() : any >C4 : typeof C4 const c4_v2 = new C4(); diff --git a/tests/baselines/reference/constructorTagOnObjectLiteralMethod.errors.txt b/tests/baselines/reference/constructorTagOnObjectLiteralMethod.errors.txt new file mode 100644 index 0000000000000..2ebd6b532605d --- /dev/null +++ b/tests/baselines/reference/constructorTagOnObjectLiteralMethod.errors.txt @@ -0,0 +1,15 @@ +tests/cases/conformance/jsdoc/example.js(3,16): error TS2339: Property 'bar' does not exist on type '{ Foo(): void; }'. +tests/cases/conformance/jsdoc/example.js(5,2): error TS7009: 'new' expression, whose target lacks a construct signature, implicitly has an 'any' type. + + +==== tests/cases/conformance/jsdoc/example.js (2 errors) ==== + const obj = { + /** @constructor */ + Foo() { this.bar = "bar" } + ~~~ +!!! error TS2339: Property 'bar' does not exist on type '{ Foo(): void; }'. + }; + (new obj.Foo()).bar + ~~~~~~~~~~~~~ +!!! error TS7009: 'new' expression, whose target lacks a construct signature, implicitly has an 'any' type. + \ No newline at end of file diff --git a/tests/baselines/reference/constructorTagOnObjectLiteralMethod.symbols b/tests/baselines/reference/constructorTagOnObjectLiteralMethod.symbols new file mode 100644 index 0000000000000..bd76f0c33bc14 --- /dev/null +++ b/tests/baselines/reference/constructorTagOnObjectLiteralMethod.symbols @@ -0,0 +1,16 @@ +=== tests/cases/conformance/jsdoc/example.js === +const obj = { +>obj : Symbol(obj, Decl(example.js, 0, 5)) + + /** @constructor */ + Foo() { this.bar = "bar" } +>Foo : Symbol(Foo, Decl(example.js, 0, 13)) +>this : Symbol(obj, Decl(example.js, 0, 11)) +>bar : Symbol(bar, Decl(example.js, 2, 9)) + +}; +(new obj.Foo()).bar +>obj.Foo : Symbol(Foo, Decl(example.js, 0, 13)) +>obj : Symbol(obj, Decl(example.js, 0, 5)) +>Foo : Symbol(Foo, Decl(example.js, 0, 13)) + diff --git a/tests/baselines/reference/constructorTagOnObjectLiteralMethod.types b/tests/baselines/reference/constructorTagOnObjectLiteralMethod.types new file mode 100644 index 0000000000000..c731f3bc632bd --- /dev/null +++ b/tests/baselines/reference/constructorTagOnObjectLiteralMethod.types @@ -0,0 +1,24 @@ +=== tests/cases/conformance/jsdoc/example.js === +const obj = { +>obj : { Foo(): void; } +>{ /** @constructor */ Foo() { this.bar = "bar" }} : { Foo(): void; } + + /** @constructor */ + Foo() { this.bar = "bar" } +>Foo : () => void +>this.bar = "bar" : "bar" +>this.bar : any +>this : { Foo(): void; } +>bar : any +>"bar" : "bar" + +}; +(new obj.Foo()).bar +>(new obj.Foo()).bar : any +>(new obj.Foo()) : any +>new obj.Foo() : any +>obj.Foo : () => void +>obj : { Foo(): void; } +>Foo : () => void +>bar : any + diff --git a/tests/baselines/reference/typeFromPropertyAssignment12.types b/tests/baselines/reference/typeFromPropertyAssignment12.types index 21b8dd7e32cba..c2bee51375b4c 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment12.types +++ b/tests/baselines/reference/typeFromPropertyAssignment12.types @@ -1,7 +1,7 @@ === tests/cases/conformance/salsa/module.js === var Outer = function(element, config) {}; ->Outer : { (element: any, config: any): void; Pos(line: any, ch: any): void; } ->function(element, config) {} : { (element: any, config: any): void; Pos(line: any, ch: any): void; } +>Outer : { (element: any, config: any): void; Pos(line: any, ch: any): Pos; } +>function(element, config) {} : { (element: any, config: any): void; Pos(line: any, ch: any): Pos; } >element : any >config : any @@ -10,7 +10,7 @@ var Outer = function(element, config) {}; Outer.Pos = function (line, ch) {}; >Outer.Pos = function (line, ch) {} : typeof Pos >Outer.Pos : typeof Pos ->Outer : { (element: any, config: any): void; Pos(line: any, ch: any): void; } +>Outer : { (element: any, config: any): void; Pos(line: any, ch: any): Pos; } >Pos : typeof Pos >function (line, ch) {} : typeof Pos >line : any @@ -21,7 +21,7 @@ Outer.Pos.prototype.line; >Outer.Pos.prototype.line : any >Outer.Pos.prototype : any >Outer.Pos : typeof Pos ->Outer : { (element: any, config: any): void; Pos(line: any, ch: any): void; } +>Outer : { (element: any, config: any): void; Pos(line: any, ch: any): Pos; } >Pos : typeof Pos >prototype : any >line : any @@ -30,7 +30,7 @@ var pos = new Outer.Pos(1, 'x'); >pos : Pos >new Outer.Pos(1, 'x') : Pos >Outer.Pos : typeof Pos ->Outer : { (element: any, config: any): void; Pos(line: any, ch: any): void; } +>Outer : { (element: any, config: any): void; Pos(line: any, ch: any): Pos; } >Pos : typeof Pos >1 : 1 >'x' : "x" diff --git a/tests/cases/conformance/jsdoc/constructorTagOnObjectLiteralMethod.ts b/tests/cases/conformance/jsdoc/constructorTagOnObjectLiteralMethod.ts new file mode 100644 index 0000000000000..d73ba6766c5ff --- /dev/null +++ b/tests/cases/conformance/jsdoc/constructorTagOnObjectLiteralMethod.ts @@ -0,0 +1,10 @@ +// @noEmit: true +// @Filename: example.js +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +const obj = { + /** @constructor */ + Foo() { this.bar = "bar" } +}; +(new obj.Foo()).bar diff --git a/tests/cases/conformance/salsa/classCanExtendConstructorFunction.ts b/tests/cases/conformance/salsa/classCanExtendConstructorFunction.ts index 6946507c80e28..895365846431e 100644 --- a/tests/cases/conformance/salsa/classCanExtendConstructorFunction.ts +++ b/tests/cases/conformance/salsa/classCanExtendConstructorFunction.ts @@ -99,6 +99,8 @@ class Chowder extends Soup { var soup = new Soup(1); soup.flavour -var chowder = new Chowder(); +var chowder = new Chowder({ claim: "ignorant" }); chowder.flavour.claim +var errorNoArgs = new Chowder(); +var errorArgType = new Chowder(0); diff --git a/tests/cases/conformance/salsa/constructorFunctions.ts b/tests/cases/conformance/salsa/constructorFunctions.ts index 97486bb988c60..35eae4b0e2891 100644 --- a/tests/cases/conformance/salsa/constructorFunctions.ts +++ b/tests/cases/conformance/salsa/constructorFunctions.ts @@ -24,7 +24,7 @@ function C3() { if (!(this instanceof C3)) return new C3(); }; -const c3_v1 = C3(); +const c3_v1 = C3(); // error: @class tag requires 'new' const c3_v2 = new C3(); /** @class */ @@ -32,7 +32,7 @@ var C4 = function () { if (!(this instanceof C4)) return new C4(); }; -const c4_v1 = C4(); +const c4_v1 = C4(); // error: @class tag requires 'new' const c4_v2 = new C4(); var c5_v1; diff --git a/tests/cases/fourslash/jsDocFunctionSignatures8.ts b/tests/cases/fourslash/jsDocFunctionSignatures8.ts index 6853c70daf2cf..f7f2f4dca5e3a 100644 --- a/tests/cases/fourslash/jsDocFunctionSignatures8.ts +++ b/tests/cases/fourslash/jsDocFunctionSignatures8.ts @@ -14,4 +14,4 @@ ////} ////var p = new Pers/**/on(); goTo.marker(); -verify.quickInfoIs("function Person(name: string, age: number): void", "Represents a person\na b multiline test"); +verify.quickInfoIs("function Person(name: string, age: number): Person", "Represents a person\na b multiline test");