diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f3fba142213..509f8975eb5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3291,7 +3291,7 @@ namespace ts { if (symbol) { const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; id = (isConstructorObject ? "+" : "") + getSymbolId(symbol); - if (isJavaScriptConstructor(symbol.valueDeclaration)) { + if (isJavascriptConstructor(symbol.valueDeclaration)) { // Instance and static types share the same symbol; only add 'typeof' for the static side. const isInstanceType = type === getInferredClassType(symbol) ? SymbolFlags.Type : SymbolFlags.Value; return symbolToTypeNode(symbol, context, isInstanceType); @@ -5501,7 +5501,7 @@ namespace ts { const constraint = getBaseConstraintOfType(type); return !!constraint && isValidBaseType(constraint) && isMixinConstructorType(constraint); } - return false; + return isJavascriptConstructorType(type); } function getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments | undefined { @@ -5510,9 +5510,12 @@ namespace ts { function getConstructorsForTypeArguments(type: Type, typeArgumentNodes: ReadonlyArray | undefined, location: Node): ReadonlyArray { const typeArgCount = length(typeArgumentNodes); - const isJavaScript = isInJavaScriptFile(location); + const isJavascript = isInJavaScriptFile(location); + if (isJavascriptConstructorType(type) && !typeArgCount) { + return getSignaturesOfType(type, SignatureKind.Call); + } return filter(getSignaturesOfType(type, SignatureKind.Construct), - sig => (isJavaScript || typeArgCount >= getMinTypeArgumentCount(sig.typeParameters)) && typeArgCount <= length(sig.typeParameters)); + sig => (isJavascript || typeArgCount >= getMinTypeArgumentCount(sig.typeParameters)) && typeArgCount <= length(sig.typeParameters)); } function getInstantiatedConstructorsForTypeArguments(type: Type, typeArgumentNodes: ReadonlyArray | undefined, location: Node): ReadonlyArray { @@ -5603,6 +5606,9 @@ namespace ts { else if (baseConstructorType.flags & TypeFlags.Any) { baseType = baseConstructorType; } + else if (isJavascriptConstructorType(baseConstructorType) && !baseTypeNode.typeArguments) { + baseType = getJavascriptClassType(baseConstructorType.symbol) || anyType; + } else { // The class derives from a "class-like" constructor function, check that we have at least one construct signature // with a matching number of type parameters and use the return type of the first instantiated signature. Elsewhere @@ -10110,7 +10116,7 @@ namespace ts { } } let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true); - if (isJavaScriptConstructor(declaration)) { + if (isJavascriptConstructor(declaration)) { const templateTagParameters = getTypeParametersFromDeclaration(declaration as DeclarationWithTypeParameters); outerTypeParameters = addRange(outerTypeParameters, templateTagParameters); } @@ -10407,7 +10413,7 @@ namespace ts { function getTypeWithoutSignatures(type: Type): Type { if (type.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(type); - if (resolved.constructSignatures.length) { + if (resolved.constructSignatures.length || resolved.callSignatures.length) { const result = createObjectType(ObjectFlags.Anonymous, type.symbol); result.members = resolved.members; result.properties = resolved.properties; @@ -10741,13 +10747,13 @@ namespace ts { } if (!ignoreReturnTypes) { - const targetReturnType = (target.declaration && isJavaScriptConstructor(target.declaration)) ? - getJavaScriptClassType(target.declaration.symbol)! : getReturnTypeOfSignature(target); + const targetReturnType = (target.declaration && isJavascriptConstructor(target.declaration)) ? + getJavascriptClassType(target.declaration.symbol)! : getReturnTypeOfSignature(target); if (targetReturnType === voidType) { return result; } - const sourceReturnType = (source.declaration && isJavaScriptConstructor(source.declaration)) ? - getJavaScriptClassType(source.declaration.symbol)! : getReturnTypeOfSignature(source); + const sourceReturnType = (source.declaration && isJavascriptConstructor(source.declaration)) ? + getJavascriptClassType(source.declaration.symbol)! : getReturnTypeOfSignature(source); // The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions const targetTypePredicate = getTypePredicateOfSignature(target); @@ -12014,8 +12020,8 @@ namespace ts { return Ternary.True; } - const sourceIsJSConstructor = source.symbol && isJavaScriptConstructor(source.symbol.valueDeclaration); - const targetIsJSConstructor = target.symbol && isJavaScriptConstructor(target.symbol.valueDeclaration); + const sourceIsJSConstructor = source.symbol && isJavascriptConstructor(source.symbol.valueDeclaration); + const targetIsJSConstructor = target.symbol && isJavascriptConstructor(target.symbol.valueDeclaration); const sourceSignatures = getSignaturesOfType(source, (sourceIsJSConstructor && kind === SignatureKind.Construct) ? SignatureKind.Call : kind); @@ -15500,7 +15506,7 @@ namespace ts { // * /** @constructor */ var x = function() { ... } else if ((container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) && getJSDocClassTag(container)) { - const classType = getJavaScriptClassType(container.symbol); + const classType = getJavascriptClassType(container.symbol); if (classType) { return getFlowTypeOfReference(node, classType); } @@ -19660,7 +19666,7 @@ namespace ts { const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call); if (callSignatures.length) { const signature = resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp); - if (signature.declaration && !isJavaScriptConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) { + if (signature.declaration && !isJavascriptConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) { error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword); } if (getThisTypeOfSignature(signature) === voidType) { @@ -19941,7 +19947,7 @@ namespace ts { * Indicates whether a declaration can be treated as a constructor in a JavaScript * file. */ - function isJavaScriptConstructor(node: Declaration | undefined): boolean { + function isJavascriptConstructor(node: Declaration | undefined): boolean { if (node && isInJavaScriptFile(node)) { // If the node has a @class tag, treat it like a constructor. if (getJSDocClassTag(node)) return true; @@ -19957,14 +19963,22 @@ namespace ts { return false; } - function getJavaScriptClassType(symbol: Symbol): Type | undefined { + function isJavascriptConstructorType(type: Type) { + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers(type); + return resolved.callSignatures.length === 1 && isJavascriptConstructor(resolved.callSignatures[0].declaration); + } + return false; + } + + function getJavascriptClassType(symbol: Symbol): Type | undefined { let inferred: Type | undefined; - if (isJavaScriptConstructor(symbol.valueDeclaration)) { + if (isJavascriptConstructor(symbol.valueDeclaration)) { inferred = getInferredClassType(symbol); } const assigned = getAssignedClassType(symbol); const valueType = getTypeOfSymbol(symbol); - if (valueType.symbol && !isInferredClassType(valueType) && isJavaScriptConstructor(valueType.symbol.valueDeclaration)) { + if (valueType.symbol && !isInferredClassType(valueType) && isJavascriptConstructor(valueType.symbol.valueDeclaration)) { inferred = getInferredClassType(valueType.symbol); } return assigned && inferred ? @@ -20047,7 +20061,7 @@ namespace ts { if (!funcSymbol && node.expression.kind === SyntaxKind.Identifier) { funcSymbol = getResolvedSymbol(node.expression as Identifier); } - const type = funcSymbol && getJavaScriptClassType(funcSymbol); + const type = funcSymbol && getJavascriptClassType(funcSymbol); if (type) { return signature.target ? instantiateType(type, signature.mapper) : type; } @@ -20655,7 +20669,7 @@ namespace ts { return undefined; } if (strictNullChecks && aggregatedTypes.length && hasReturnWithNoExpression && - !(isJavaScriptConstructor(func) && aggregatedTypes.some(t => t.symbol === func.symbol))) { + !(isJavascriptConstructor(func) && aggregatedTypes.some(t => t.symbol === func.symbol))) { // Javascript "callable constructors", containing eg `if (!(this instanceof A)) return new A()` should not add undefined pushIfUnique(aggregatedTypes, undefinedType); } @@ -25561,8 +25575,9 @@ namespace ts { // that all instantiated base constructor signatures return the same type. We can simply compare the type // references (as opposed to checking the structure of the types) because elsewhere we have already checked // that the base type is a class or interface type (and not, for example, an anonymous object type). + // (Javascript constructor functions have this property trivially true since their return type is ignored.) const constructors = getInstantiatedConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode); - if (forEach(constructors, sig => getReturnTypeOfSignature(sig) !== baseType)) { + if (forEach(constructors, sig => !isJavascriptConstructor(sig.declaration) && getReturnTypeOfSignature(sig) !== baseType)) { error(baseTypeNode.expression, Diagnostics.Base_constructors_must_all_have_the_same_return_type); } } diff --git a/tests/baselines/reference/classCanExtendConstructorFunction.errors.txt b/tests/baselines/reference/classCanExtendConstructorFunction.errors.txt new file mode 100644 index 00000000000..fae98379558 --- /dev/null +++ b/tests/baselines/reference/classCanExtendConstructorFunction.errors.txt @@ -0,0 +1,132 @@ +tests/cases/conformance/salsa/first.js(18,9): error TS2554: Expected 1 arguments, but got 0. +tests/cases/conformance/salsa/first.js(26,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(36,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/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. + Type '(others: (typeof Wagon)[]) => number' is not assignable to type '(wagons?: Wagon[]) => number'. + Types of parameters 'others' and 'wagons' are incompatible. + Type 'Wagon[]' is not assignable to type '(typeof Wagon)[]'. + Type 'Wagon' is not assignable to type 'typeof Wagon'. + Property 'circle' is missing in type 'Wagon'. +tests/cases/conformance/salsa/second.ts(17,15): error TS2345: Argument of type '"nope"' is not assignable to parameter of type 'number'. + + +==== tests/cases/conformance/salsa/first.js (3 errors) ==== + /** + * @constructor + * @param {number} numberOxen + */ + function Wagon(numberOxen) { + this.numberOxen = numberOxen + } + /** @param {Wagon[]=} wagons */ + Wagon.circle = function (wagons) { + return wagons ? wagons.length : 3.14; + } + /** @param {*[]=} supplies - *[]= is my favourite type */ + Wagon.prototype.load = function (supplies) { + } + // ok + class Sql extends Wagon { + constructor() { + super(); // error: not enough arguments + ~~~~~~~ +!!! error TS2554: Expected 1 arguments, but got 0. + this.foonly = 12 + } + /** + * @param {Array.} files + * @param {"csv" | "json" | "xmlolololol"} format + * This is not assignable, so should have a type error + */ + load(files, format) { + ~~~~ +!!! error TS2416: Property 'load' in type 'Sql' is not assignable to the same property in base type 'Wagon'. +!!! error TS2416: Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[]) => void'. + if (format === "xmlolololol") { + throw new Error("please do not use XML. It was a joke."); + } + } + } + var db = new Sql(); + db.numberOxen = db.foonly + + // error, can't extend a TS constructor function + class Drakkhen extends Dragon { + ~~~~~~ +!!! error TS2507: Type '(numberEaten: number) => void' is not a constructor function type. + + } + +==== tests/cases/conformance/salsa/second.ts (3 errors) ==== + /** + * @constructor + */ + function Dragon(numberEaten: number) { + this.numberEaten = numberEaten + } + // error! + class Firedrake extends Dragon { + ~~~~~~ +!!! error TS2507: Type '(numberEaten: number) => void' is not a constructor function type. + constructor() { + super(); + } + } + // ok + class Conestoga extends Wagon { + ~~~~~~~~~ +!!! error TS2417: Class static side 'typeof Conestoga' incorrectly extends base class static side 'typeof Wagon'. +!!! error TS2417: Types of property 'circle' are incompatible. +!!! error TS2417: Type '(others: (typeof Wagon)[]) => number' is not assignable to type '(wagons?: Wagon[]) => number'. +!!! error TS2417: Types of parameters 'others' and 'wagons' are incompatible. +!!! error TS2417: Type 'Wagon[]' is not assignable to type '(typeof Wagon)[]'. +!!! error TS2417: Type 'Wagon' is not assignable to type 'typeof Wagon'. +!!! error TS2417: Property 'circle' is missing in type 'Wagon'. + constructor(public drunkOO: true) { + // error: wrong type + super('nope'); + ~~~~~~ +!!! error TS2345: Argument of type '"nope"' is not assignable to parameter of type 'number'. + } + // should error since others is not optional + static circle(others: (typeof Wagon)[]) { + return others.length + } + } + var c = new Conestoga(true); + c.drunkOO + c.numberOxen + +==== tests/cases/conformance/salsa/generic.js (3 errors) ==== + /** + * @template T + * @param {T} flavour + */ + function Soup(flavour) { + 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(); + chowder.flavour.claim + ~~~~~~~ +!!! error TS2339: Property 'flavour' does not exist on type 'Chowder'. + + \ No newline at end of file diff --git a/tests/baselines/reference/classCanExtendConstructorFunction.symbols b/tests/baselines/reference/classCanExtendConstructorFunction.symbols new file mode 100644 index 00000000000..88961849708 --- /dev/null +++ b/tests/baselines/reference/classCanExtendConstructorFunction.symbols @@ -0,0 +1,188 @@ +=== tests/cases/conformance/salsa/first.js === +/** + * @constructor + * @param {number} numberOxen + */ +function Wagon(numberOxen) { +>Wagon : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1)) +>numberOxen : Symbol(numberOxen, Decl(first.js, 4, 15)) + + this.numberOxen = numberOxen +>this.numberOxen : Symbol(Wagon.numberOxen, Decl(first.js, 4, 28)) +>this : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1)) +>numberOxen : Symbol(Wagon.numberOxen, Decl(first.js, 4, 28)) +>numberOxen : Symbol(numberOxen, Decl(first.js, 4, 15)) +} +/** @param {Wagon[]=} wagons */ +Wagon.circle = function (wagons) { +>Wagon.circle : Symbol(Wagon.circle, Decl(first.js, 6, 1)) +>Wagon : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1)) +>circle : Symbol(Wagon.circle, Decl(first.js, 6, 1)) +>wagons : Symbol(wagons, Decl(first.js, 8, 25)) + + return wagons ? wagons.length : 3.14; +>wagons : Symbol(wagons, Decl(first.js, 8, 25)) +>wagons.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) +>wagons : Symbol(wagons, Decl(first.js, 8, 25)) +>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) +} +/** @param {*[]=} supplies - *[]= is my favourite type */ +Wagon.prototype.load = function (supplies) { +>Wagon.prototype : Symbol(Wagon.load, Decl(first.js, 10, 1)) +>Wagon : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1)) +>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>load : Symbol(Wagon.load, Decl(first.js, 10, 1)) +>supplies : Symbol(supplies, Decl(first.js, 12, 33)) +} +// ok +class Sql extends Wagon { +>Sql : Symbol(Sql, Decl(first.js, 13, 1)) +>Wagon : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1)) + + constructor() { + super(); // error: not enough arguments +>super : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1)) + + this.foonly = 12 +>this.foonly : Symbol(Sql.foonly, Decl(first.js, 17, 16)) +>this : Symbol(Sql, Decl(first.js, 13, 1)) +>foonly : Symbol(Sql.foonly, Decl(first.js, 17, 16)) + } + /** + * @param {Array.} files + * @param {"csv" | "json" | "xmlolololol"} format + * This is not assignable, so should have a type error + */ + load(files, format) { +>load : Symbol(Sql.load, Decl(first.js, 19, 5)) +>files : Symbol(files, Decl(first.js, 25, 9)) +>format : Symbol(format, Decl(first.js, 25, 15)) + + if (format === "xmlolololol") { +>format : Symbol(format, Decl(first.js, 25, 15)) + + throw new Error("please do not use XML. It was a joke."); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } + } +} +var db = new Sql(); +>db : Symbol(db, Decl(first.js, 31, 3)) +>Sql : Symbol(Sql, Decl(first.js, 13, 1)) + +db.numberOxen = db.foonly +>db.numberOxen : Symbol(Wagon.numberOxen, Decl(first.js, 4, 28)) +>db : Symbol(db, Decl(first.js, 31, 3)) +>numberOxen : Symbol(Wagon.numberOxen, Decl(first.js, 4, 28)) +>db.foonly : Symbol(Sql.foonly, Decl(first.js, 17, 16)) +>db : Symbol(db, Decl(first.js, 31, 3)) +>foonly : Symbol(Sql.foonly, Decl(first.js, 17, 16)) + +// error, can't extend a TS constructor function +class Drakkhen extends Dragon { +>Drakkhen : Symbol(Drakkhen, Decl(first.js, 32, 25)) +>Dragon : Symbol(Dragon, Decl(second.ts, 0, 0)) + +} + +=== tests/cases/conformance/salsa/second.ts === +/** + * @constructor + */ +function Dragon(numberEaten: number) { +>Dragon : Symbol(Dragon, Decl(second.ts, 0, 0)) +>numberEaten : Symbol(numberEaten, Decl(second.ts, 3, 16)) + + this.numberEaten = numberEaten +>numberEaten : Symbol(numberEaten, Decl(second.ts, 3, 16)) +} +// error! +class Firedrake extends Dragon { +>Firedrake : Symbol(Firedrake, Decl(second.ts, 5, 1)) +>Dragon : Symbol(Dragon, Decl(second.ts, 0, 0)) + + constructor() { + super(); + } +} +// ok +class Conestoga extends Wagon { +>Conestoga : Symbol(Conestoga, Decl(second.ts, 11, 1)) +>Wagon : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1)) + + constructor(public drunkOO: true) { +>drunkOO : Symbol(Conestoga.drunkOO, Decl(second.ts, 14, 16)) + + // error: wrong type + super('nope'); +>super : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1)) + } + // should error since others is not optional + static circle(others: (typeof Wagon)[]) { +>circle : Symbol(Conestoga.circle, Decl(second.ts, 17, 5)) +>others : Symbol(others, Decl(second.ts, 19, 18)) +>Wagon : Symbol(Wagon, Decl(first.js, 0, 0), Decl(first.js, 6, 1)) + + return others.length +>others.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) +>others : Symbol(others, Decl(second.ts, 19, 18)) +>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) + } +} +var c = new Conestoga(true); +>c : Symbol(c, Decl(second.ts, 23, 3)) +>Conestoga : Symbol(Conestoga, Decl(second.ts, 11, 1)) + +c.drunkOO +>c.drunkOO : Symbol(Conestoga.drunkOO, Decl(second.ts, 14, 16)) +>c : Symbol(c, Decl(second.ts, 23, 3)) +>drunkOO : Symbol(Conestoga.drunkOO, Decl(second.ts, 14, 16)) + +c.numberOxen +>c.numberOxen : Symbol(Wagon.numberOxen, Decl(first.js, 4, 28)) +>c : Symbol(c, Decl(second.ts, 23, 3)) +>numberOxen : Symbol(Wagon.numberOxen, Decl(first.js, 4, 28)) + +=== tests/cases/conformance/salsa/generic.js === +/** + * @template T + * @param {T} flavour + */ +function Soup(flavour) { +>Soup : Symbol(Soup, Decl(generic.js, 0, 0)) +>flavour : Symbol(flavour, Decl(generic.js, 4, 14)) + + this.flavour = flavour +>flavour : Symbol(Soup.flavour, Decl(generic.js, 4, 24)) +>flavour : Symbol(flavour, Decl(generic.js, 4, 14)) +} +/** @extends {Soup<{ claim: "ignorant" | "malicious" }>} */ +class Chowder extends Soup { +>Chowder : Symbol(Chowder, Decl(generic.js, 6, 1)) +>Soup : Symbol(Soup, Decl(generic.js, 0, 0)) + + log() { +>log : Symbol(Chowder.log, Decl(generic.js, 8, 28)) + + return this.flavour +>this : Symbol(Chowder, Decl(generic.js, 6, 1)) + } +} + +var soup = new Soup(1); +>soup : Symbol(soup, Decl(generic.js, 14, 3)) +>Soup : Symbol(Soup, Decl(generic.js, 0, 0)) + +soup.flavour +>soup.flavour : Symbol(Soup.flavour, Decl(generic.js, 4, 24)) +>soup : Symbol(soup, Decl(generic.js, 14, 3)) +>flavour : Symbol(Soup.flavour, Decl(generic.js, 4, 24)) + +var chowder = new Chowder(); +>chowder : Symbol(chowder, Decl(generic.js, 16, 3)) +>Chowder : Symbol(Chowder, Decl(generic.js, 6, 1)) + +chowder.flavour.claim +>chowder : Symbol(chowder, Decl(generic.js, 16, 3)) + + diff --git a/tests/baselines/reference/classCanExtendConstructorFunction.types b/tests/baselines/reference/classCanExtendConstructorFunction.types new file mode 100644 index 00000000000..1e5cf0ece91 --- /dev/null +++ b/tests/baselines/reference/classCanExtendConstructorFunction.types @@ -0,0 +1,228 @@ +=== tests/cases/conformance/salsa/first.js === +/** + * @constructor + * @param {number} numberOxen + */ +function Wagon(numberOxen) { +>Wagon : typeof Wagon +>numberOxen : number + + this.numberOxen = numberOxen +>this.numberOxen = numberOxen : number +>this.numberOxen : number +>this : Wagon +>numberOxen : number +>numberOxen : number +} +/** @param {Wagon[]=} wagons */ +Wagon.circle = function (wagons) { +>Wagon.circle = function (wagons) { return wagons ? wagons.length : 3.14;} : (wagons?: Wagon[]) => number +>Wagon.circle : (wagons?: Wagon[]) => number +>Wagon : typeof Wagon +>circle : (wagons?: Wagon[]) => number +>function (wagons) { return wagons ? wagons.length : 3.14;} : (wagons?: Wagon[]) => number +>wagons : Wagon[] + + return wagons ? wagons.length : 3.14; +>wagons ? wagons.length : 3.14 : number +>wagons : Wagon[] +>wagons.length : number +>wagons : Wagon[] +>length : number +>3.14 : 3.14 +} +/** @param {*[]=} supplies - *[]= is my favourite type */ +Wagon.prototype.load = function (supplies) { +>Wagon.prototype.load = function (supplies) {} : (supplies?: any[]) => void +>Wagon.prototype.load : any +>Wagon.prototype : any +>Wagon : typeof Wagon +>prototype : any +>load : any +>function (supplies) {} : (supplies?: any[]) => void +>supplies : any[] +} +// ok +class Sql extends Wagon { +>Sql : Sql +>Wagon : Wagon + + constructor() { + super(); // error: not enough arguments +>super() : void +>super : typeof Wagon + + this.foonly = 12 +>this.foonly = 12 : 12 +>this.foonly : number +>this : this +>foonly : number +>12 : 12 + } + /** + * @param {Array.} files + * @param {"csv" | "json" | "xmlolololol"} format + * This is not assignable, so should have a type error + */ + load(files, format) { +>load : (files: string[], format: "csv" | "json" | "xmlolololol") => void +>files : string[] +>format : "csv" | "json" | "xmlolololol" + + if (format === "xmlolololol") { +>format === "xmlolololol" : boolean +>format : "csv" | "json" | "xmlolololol" +>"xmlolololol" : "xmlolololol" + + throw new Error("please do not use XML. It was a joke."); +>new Error("please do not use XML. It was a joke.") : Error +>Error : ErrorConstructor +>"please do not use XML. It was a joke." : "please do not use XML. It was a joke." + } + } +} +var db = new Sql(); +>db : Sql +>new Sql() : Sql +>Sql : typeof Sql + +db.numberOxen = db.foonly +>db.numberOxen = db.foonly : number +>db.numberOxen : number +>db : Sql +>numberOxen : number +>db.foonly : number +>db : Sql +>foonly : number + +// error, can't extend a TS constructor function +class Drakkhen extends Dragon { +>Drakkhen : Drakkhen +>Dragon : (numberEaten: number) => void + +} + +=== tests/cases/conformance/salsa/second.ts === +/** + * @constructor + */ +function Dragon(numberEaten: number) { +>Dragon : (numberEaten: number) => void +>numberEaten : number + + this.numberEaten = numberEaten +>this.numberEaten = numberEaten : number +>this.numberEaten : any +>this : any +>numberEaten : any +>numberEaten : number +} +// error! +class Firedrake extends Dragon { +>Firedrake : Firedrake +>Dragon : (numberEaten: number) => void + + constructor() { + super(); +>super() : void +>super : any + } +} +// ok +class Conestoga extends Wagon { +>Conestoga : Conestoga +>Wagon : Wagon + + constructor(public drunkOO: true) { +>drunkOO : true +>true : true + + // error: wrong type + super('nope'); +>super('nope') : void +>super : typeof Wagon +>'nope' : "nope" + } + // should error since others is not optional + static circle(others: (typeof Wagon)[]) { +>circle : (others: (typeof Wagon)[]) => number +>others : (typeof Wagon)[] +>Wagon : typeof Wagon + + return others.length +>others.length : number +>others : (typeof Wagon)[] +>length : number + } +} +var c = new Conestoga(true); +>c : Conestoga +>new Conestoga(true) : Conestoga +>Conestoga : typeof Conestoga +>true : true + +c.drunkOO +>c.drunkOO : true +>c : Conestoga +>drunkOO : true + +c.numberOxen +>c.numberOxen : number +>c : Conestoga +>numberOxen : number + +=== tests/cases/conformance/salsa/generic.js === +/** + * @template T + * @param {T} flavour + */ +function Soup(flavour) { +>Soup : typeof Soup +>flavour : T + + this.flavour = flavour +>this.flavour = flavour : T +>this.flavour : any +>this : any +>flavour : any +>flavour : T +} +/** @extends {Soup<{ claim: "ignorant" | "malicious" }>} */ +class Chowder extends Soup { +>Chowder : Chowder +>Soup : typeof Soup + + log() { +>log : () => any + + return this.flavour +>this.flavour : any +>this : this +>flavour : any + } +} + +var soup = new Soup(1); +>soup : typeof Soup +>new Soup(1) : typeof Soup +>Soup : typeof Soup +>1 : 1 + +soup.flavour +>soup.flavour : number +>soup : typeof Soup +>flavour : number + +var chowder = new Chowder(); +>chowder : Chowder +>new Chowder() : Chowder +>Chowder : typeof Chowder + +chowder.flavour.claim +>chowder.flavour.claim : any +>chowder.flavour : any +>chowder : Chowder +>flavour : any +>claim : any + + diff --git a/tests/cases/conformance/salsa/classCanExtendConstructorFunction.ts b/tests/cases/conformance/salsa/classCanExtendConstructorFunction.ts new file mode 100644 index 00000000000..1e20e0040d8 --- /dev/null +++ b/tests/cases/conformance/salsa/classCanExtendConstructorFunction.ts @@ -0,0 +1,94 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: first.js + +/** + * @constructor + * @param {number} numberOxen + */ +function Wagon(numberOxen) { + this.numberOxen = numberOxen +} +/** @param {Wagon[]=} wagons */ +Wagon.circle = function (wagons) { + return wagons ? wagons.length : 3.14; +} +/** @param {*[]=} supplies - *[]= is my favourite type */ +Wagon.prototype.load = function (supplies) { +} +// ok +class Sql extends Wagon { + constructor() { + super(); // error: not enough arguments + this.foonly = 12 + } + /** + * @param {Array.} files + * @param {"csv" | "json" | "xmlolololol"} format + * This is not assignable, so should have a type error + */ + load(files, format) { + if (format === "xmlolololol") { + throw new Error("please do not use XML. It was a joke."); + } + } +} +var db = new Sql(); +db.numberOxen = db.foonly + +// error, can't extend a TS constructor function +class Drakkhen extends Dragon { + +} + +// @Filename: second.ts + +/** + * @constructor + */ +function Dragon(numberEaten: number) { + this.numberEaten = numberEaten +} +// error! +class Firedrake extends Dragon { + constructor() { + super(); + } +} +// ok +class Conestoga extends Wagon { + constructor(public drunkOO: true) { + // error: wrong type + super('nope'); + } + // should error since others is not optional + static circle(others: (typeof Wagon)[]) { + return others.length + } +} +var c = new Conestoga(true); +c.drunkOO +c.numberOxen + +// @Filename: generic.js + +/** + * @template T + * @param {T} flavour + */ +function Soup(flavour) { + this.flavour = flavour +} +/** @extends {Soup<{ claim: "ignorant" | "malicious" }>} */ +class Chowder extends Soup { + log() { + return this.flavour + } +} + +var soup = new Soup(1); +soup.flavour +var chowder = new Chowder(); +chowder.flavour.claim +