Fix resolution of properties from prototype assignment in JS (#29302)
* fix type derived from prototype assignment * accept new baselines * remove direct intersection with object literal assigned to prototype * add tests * change webpack submodule commit * fix submodule commits * comment and simplify getJSDocTypeReference * remove circularity guards that aren't hit anymore
This commit is contained in:
parent
20285e66e9
commit
d38c616e29
|
@ -8704,21 +8704,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8739,8 +8735,11 @@ namespace ts {
|
|||
|
||||
if (symbol.flags & SymbolFlags.Function &&
|
||||
isJSDocTypeReference(node) &&
|
||||
(symbol.members || getJSDocClassTag(symbol.valueDeclaration))) {
|
||||
return getInferredClassType(symbol);
|
||||
isJSConstructor(symbol.valueDeclaration)) {
|
||||
const resolved = resolveStructuredTypeMembers(<ObjectType>getTypeOfSymbol(symbol));
|
||||
if (resolved.callSignatures.length === 1) {
|
||||
return getReturnTypeOfSignature(resolved.callSignatures[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16877,7 +16876,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);
|
||||
}
|
||||
|
@ -21057,7 +21056,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;
|
||||
}
|
||||
|
@ -21076,10 +21075,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;
|
||||
|
@ -21119,12 +21114,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.
|
||||
|
@ -21146,21 +21135,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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 : {}
|
||||
>{} : {}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
|
@ -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))
|
||||
|
53
tests/baselines/reference/typeFromPrototypeAssignment3.types
Normal file
53
tests/baselines/reference/typeFromPrototypeAssignment3.types
Normal file
|
@ -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"
|
||||
|
|
@ -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 = {};
|
||||
|
|
|
@ -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')
|
Loading…
Reference in a new issue