Skip to content

Commit 08eb99d

Browse files
authored
For a this-property assignment with an empty object initializer, use type annotation if present (#26428)
* This-property w/empty object init: use type annotation * JS initializer type doesn't apply to this-property assignments * Move getJSExpandoType into getWidenedType* functions Plus some cleanup. * Improved style from PR comments * Parameters are not expando
1 parent dfef227 commit 08eb99d

6 files changed

+162
-92
lines changed

Diff for: src/compiler/checker.ts

+71-90
Original file line numberDiff line numberDiff line change
@@ -4696,6 +4696,12 @@ namespace ts {
46964696
return addOptionality(type, isOptional);
46974697
}
46984698
}
4699+
else if (isInJavaScriptFile(declaration)) {
4700+
const expandoType = getJSExpandoObjectType(declaration, getSymbolOfNode(declaration), getDeclaredJavascriptInitializer(declaration));
4701+
if (expandoType) {
4702+
return expandoType;
4703+
}
4704+
}
46994705

47004706
// Use the type of the initializer expression if one is present
47014707
if (declaration.initializer) {
@@ -4718,15 +4724,16 @@ namespace ts {
47184724
return undefined;
47194725
}
47204726

4721-
function getWidenedTypeFromJSSpecialPropertyDeclarations(symbol: Symbol, resolvedSymbol?: Symbol) {
4727+
function getWidenedTypeFromJSPropertyAssignments(symbol: Symbol, resolvedSymbol?: Symbol) {
47224728
// function/class/{} assignments are fresh declarations, not property assignments, so only add prototype assignments
47234729
const specialDeclaration = getAssignedJavascriptInitializer(symbol.valueDeclaration);
47244730
if (specialDeclaration) {
47254731
const tag = getJSDocTypeTag(specialDeclaration);
47264732
if (tag && tag.typeExpression) {
47274733
return getTypeFromTypeNode(tag.typeExpression);
47284734
}
4729-
return getWidenedLiteralType(checkExpressionCached(specialDeclaration));
4735+
const expando = getJSExpandoObjectType(symbol.valueDeclaration, symbol, specialDeclaration);
4736+
return expando || getWidenedLiteralType(checkExpressionCached(specialDeclaration));
47304737
}
47314738
let definedInConstructor = false;
47324739
let definedInMethod = false;
@@ -4778,6 +4785,27 @@ namespace ts {
47784785
return widened;
47794786
}
47804787

4788+
function getJSExpandoObjectType(decl: Node, symbol: Symbol, init: Expression | undefined): Type | undefined {
4789+
if (!init || !isObjectLiteralExpression(init) || init.properties.length) {
4790+
return undefined;
4791+
}
4792+
const exports = createSymbolTable();
4793+
while (isBinaryExpression(decl) || isPropertyAccessExpression(decl)) {
4794+
const s = getSymbolOfNode(decl);
4795+
if (s && hasEntries(s.exports)) {
4796+
mergeSymbolTable(exports, s.exports);
4797+
}
4798+
decl = isBinaryExpression(decl) ? decl.parent : decl.parent.parent;
4799+
}
4800+
const s = getSymbolOfNode(decl);
4801+
if (s && hasEntries(s.exports)) {
4802+
mergeSymbolTable(exports, s.exports);
4803+
}
4804+
const type = createAnonymousType(symbol, exports, emptyArray, emptyArray, undefined, undefined);
4805+
type.objectFlags |= ObjectFlags.JSLiteral;
4806+
return type;
4807+
}
4808+
47814809
function getJSDocTypeFromSpecialDeclarations(declaredType: Type | undefined, expression: Expression, _symbol: Symbol, declaration: Declaration) {
47824810
const typeNode = getJSDocType(expression.parent);
47834811
if (typeNode) {
@@ -5041,98 +5069,51 @@ namespace ts {
50415069
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
50425070
return errorType;
50435071
}
5044-
let type = getJSSpecialType(symbol, declaration);
5045-
if (!type) {
5046-
if (isJSDocPropertyLikeTag(declaration)
5047-
|| isPropertyAccessExpression(declaration)
5048-
|| isIdentifier(declaration)
5049-
|| isClassDeclaration(declaration)
5050-
|| isFunctionDeclaration(declaration)
5051-
|| (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration))
5052-
|| isMethodSignature(declaration)) {
5053-
// Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty`
5054-
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
5055-
return getTypeOfFuncClassEnumModule(symbol);
5056-
}
5057-
type = tryGetTypeFromEffectiveTypeNode(declaration) || anyType;
5058-
}
5059-
else if (isPropertyAssignment(declaration)) {
5060-
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration);
5061-
}
5062-
else if (isJsxAttribute(declaration)) {
5063-
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration);
5064-
}
5065-
else if (isShorthandPropertyAssignment(declaration)) {
5066-
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal);
5067-
}
5068-
else if (isObjectLiteralMethod(declaration)) {
5069-
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal);
5070-
}
5071-
else if (isParameter(declaration)
5072-
|| isPropertyDeclaration(declaration)
5073-
|| isPropertySignature(declaration)
5074-
|| isVariableDeclaration(declaration)
5075-
|| isBindingElement(declaration)) {
5076-
type = getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true);
5077-
}
5078-
else {
5079-
return Debug.fail("Unhandled declaration kind! " + Debug.showSyntaxKind(declaration) + " for " + Debug.showSymbol(symbol));
5080-
}
5072+
let type: Type | undefined;
5073+
if (isInJavaScriptFile(declaration) &&
5074+
(isBinaryExpression(declaration) || isPropertyAccessExpression(declaration) && isBinaryExpression(declaration.parent))) {
5075+
type = getWidenedTypeFromJSPropertyAssignments(symbol);
5076+
}
5077+
else if (isJSDocPropertyLikeTag(declaration)
5078+
|| isPropertyAccessExpression(declaration)
5079+
|| isIdentifier(declaration)
5080+
|| isClassDeclaration(declaration)
5081+
|| isFunctionDeclaration(declaration)
5082+
|| (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration))
5083+
|| isMethodSignature(declaration)) {
5084+
// Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty`
5085+
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
5086+
return getTypeOfFuncClassEnumModule(symbol);
5087+
}
5088+
type = tryGetTypeFromEffectiveTypeNode(declaration) || anyType;
5089+
}
5090+
else if (isPropertyAssignment(declaration)) {
5091+
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration);
5092+
}
5093+
else if (isJsxAttribute(declaration)) {
5094+
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration);
5095+
}
5096+
else if (isShorthandPropertyAssignment(declaration)) {
5097+
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal);
5098+
}
5099+
else if (isObjectLiteralMethod(declaration)) {
5100+
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal);
5101+
}
5102+
else if (isParameter(declaration)
5103+
|| isPropertyDeclaration(declaration)
5104+
|| isPropertySignature(declaration)
5105+
|| isVariableDeclaration(declaration)
5106+
|| isBindingElement(declaration)) {
5107+
type = getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true);
5108+
}
5109+
else {
5110+
return Debug.fail("Unhandled declaration kind! " + Debug.showSyntaxKind(declaration) + " for " + Debug.showSymbol(symbol));
50815111
}
50825112

50835113
if (!popTypeResolution()) {
50845114
type = reportCircularityError(symbol);
50855115
}
50865116
return type;
5087-
}
5088-
5089-
function getJSSpecialType(symbol: Symbol, decl: Declaration): Type | undefined {
5090-
if (!isInJavaScriptFile(decl)) {
5091-
return undefined;
5092-
}
5093-
// Handle certain special assignment kinds, which happen to union across multiple declarations:
5094-
// * module.exports = expr
5095-
// * exports.p = expr
5096-
// * this.p = expr
5097-
// * className.prototype.method = expr
5098-
else if (isBinaryExpression(decl) ||
5099-
isPropertyAccessExpression(decl) && isBinaryExpression(decl.parent)) {
5100-
return getJSInitializerType(decl, symbol, getAssignedJavascriptInitializer(isBinaryExpression(decl) ? decl.left : decl)) ||
5101-
getWidenedTypeFromJSSpecialPropertyDeclarations(symbol);
5102-
}
5103-
else if (isParameter(decl)
5104-
|| isPropertyDeclaration(decl)
5105-
|| isPropertySignature(decl)
5106-
|| isVariableDeclaration(decl)
5107-
|| isBindingElement(decl)) {
5108-
// Use type from type annotation if one is present
5109-
const isOptional = isParameter(decl) && isJSDocOptionalParameter(decl) ||
5110-
!isBindingElement(decl) && !isVariableDeclaration(decl) && !!decl.questionToken;
5111-
const declaredType = tryGetTypeFromEffectiveTypeNode(decl);
5112-
return declaredType && addOptionality(declaredType, isOptional) ||
5113-
getJSInitializerType(decl, symbol, getDeclaredJavascriptInitializer(decl)) ||
5114-
getWidenedTypeForVariableLikeDeclaration(decl, /*includeOptionality*/ true);
5115-
}
5116-
}
5117-
5118-
function getJSInitializerType(decl: Node, symbol: Symbol, init: Expression | undefined): Type | undefined {
5119-
if (init && isInJavaScriptFile(init) && isObjectLiteralExpression(init) && init.properties.length === 0) {
5120-
const exports = createSymbolTable();
5121-
while (isBinaryExpression(decl) || isPropertyAccessExpression(decl)) {
5122-
const s = getSymbolOfNode(decl);
5123-
if (s && hasEntries(s.exports)) {
5124-
mergeSymbolTable(exports, s.exports);
5125-
}
5126-
decl = isBinaryExpression(decl) ? decl.parent : decl.parent.parent;
5127-
}
5128-
const s = getSymbolOfNode(decl);
5129-
if (s && hasEntries(s.exports)) {
5130-
mergeSymbolTable(exports, s.exports);
5131-
}
5132-
const type = createAnonymousType(symbol, exports, emptyArray, emptyArray, undefined, undefined);
5133-
type.objectFlags |= ObjectFlags.JSLiteral;
5134-
return type;
5135-
}
51365117
}
51375118

51385119
function getAnnotatedAccessorTypeNode(accessor: AccessorDeclaration | undefined): TypeNode | undefined {
@@ -5264,7 +5245,7 @@ namespace ts {
52645245
}
52655246
else if (declaration.kind === SyntaxKind.BinaryExpression ||
52665247
declaration.kind === SyntaxKind.PropertyAccessExpression && declaration.parent.kind === SyntaxKind.BinaryExpression) {
5267-
return getWidenedTypeFromJSSpecialPropertyDeclarations(symbol);
5248+
return getWidenedTypeFromJSPropertyAssignments(symbol);
52685249
}
52695250
else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) {
52705251
const resolvedModule = resolveExternalModuleSymbol(symbol);
@@ -5273,7 +5254,7 @@ namespace ts {
52735254
return errorType;
52745255
}
52755256
const exportEquals = getMergedSymbol(symbol.exports!.get(InternalSymbolName.ExportEquals)!);
5276-
const type = getWidenedTypeFromJSSpecialPropertyDeclarations(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule);
5257+
const type = getWidenedTypeFromJSPropertyAssignments(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule);
52775258
if (!popTypeResolution()) {
52785259
return reportCircularityError(symbol);
52795260
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
=== tests/cases/conformance/salsa/Compilation.js ===
2+
// from webpack/lib/Compilation.js and filed at #26427
3+
/** @param {{ [s: string]: number }} map */
4+
function mappy(map) {}
5+
>mappy : Symbol(mappy, Decl(Compilation.js, 0, 0))
6+
>map : Symbol(map, Decl(Compilation.js, 2, 15))
7+
8+
export class C {
9+
>C : Symbol(C, Decl(Compilation.js, 2, 22))
10+
11+
constructor() {
12+
/** @type {{ [assetName: string]: number}} */
13+
this.assets = {};
14+
>this.assets : Symbol(C.assets, Decl(Compilation.js, 5, 19))
15+
>this : Symbol(C, Decl(Compilation.js, 2, 22))
16+
>assets : Symbol(C.assets, Decl(Compilation.js, 5, 19))
17+
}
18+
m() {
19+
>m : Symbol(C.m, Decl(Compilation.js, 8, 5))
20+
21+
mappy(this.assets)
22+
>mappy : Symbol(mappy, Decl(Compilation.js, 0, 0))
23+
>this.assets : Symbol(C.assets, Decl(Compilation.js, 5, 19))
24+
>this : Symbol(C, Decl(Compilation.js, 2, 22))
25+
>assets : Symbol(C.assets, Decl(Compilation.js, 5, 19))
26+
}
27+
}
28+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
=== tests/cases/conformance/salsa/Compilation.js ===
2+
// from webpack/lib/Compilation.js and filed at #26427
3+
/** @param {{ [s: string]: number }} map */
4+
function mappy(map) {}
5+
>mappy : (map: { [s: string]: number; }) => void
6+
>map : { [s: string]: number; }
7+
8+
export class C {
9+
>C : C
10+
11+
constructor() {
12+
/** @type {{ [assetName: string]: number}} */
13+
this.assets = {};
14+
>this.assets = {} : {}
15+
>this.assets : { [assetName: string]: number; }
16+
>this : this
17+
>assets : { [assetName: string]: number; }
18+
>{} : {}
19+
}
20+
m() {
21+
>m : () => void
22+
23+
mappy(this.assets)
24+
>mappy(this.assets) : void
25+
>mappy : (map: { [s: string]: number; }) => void
26+
>this.assets : { [assetName: string]: number; }
27+
>this : this
28+
>assets : { [assetName: string]: number; }
29+
}
30+
}
31+

Diff for: tests/baselines/reference/checkDestructuringShorthandAssigment.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
=== tests/cases/compiler/bug25434.js ===
22
// should not crash while checking
33
function Test({ b = '' } = {}) {}
4-
>Test : ({ b }?: {}) => void
4+
>Test : ({ b }?: { b?: string; }) => void
55
>b : string
66
>'' : ""
77
>{} : { b?: string; }
88

99
Test(({ b = '5' } = {}));
1010
>Test(({ b = '5' } = {})) : void
11-
>Test : ({ b }?: {}) => void
11+
>Test : ({ b }?: { b?: string; }) => void
1212
>({ b = '5' } = {}) : { b?: any; }
1313
>{ b = '5' } = {} : { b?: any; }
1414
>{ b = '5' } : { b?: any; }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
tests/cases/conformance/salsa/b.js(1,7): error TS2322: Type '{}' is not assignable to type 'typeof A'.
2+
Property 'prototype' is missing in type '{}'.
3+
4+
5+
==== tests/cases/conformance/salsa/a.d.ts (0 errors) ====
6+
declare class A {}
7+
==== tests/cases/conformance/salsa/b.js (1 errors) ====
8+
const A = { };
9+
~
10+
!!! error TS2322: Type '{}' is not assignable to type 'typeof A'.
11+
!!! error TS2322: Property 'prototype' is missing in type '{}'.
12+
A.d = { };
13+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// @noEmit: true
2+
// @allowJs: true
3+
// @checkJs: true
4+
// @Filename: Compilation.js
5+
// from webpack/lib/Compilation.js and filed at #26427
6+
/** @param {{ [s: string]: number }} map */
7+
function mappy(map) {}
8+
9+
export class C {
10+
constructor() {
11+
/** @type {{ [assetName: string]: number}} */
12+
this.assets = {};
13+
}
14+
m() {
15+
mappy(this.assets)
16+
}
17+
}

0 commit comments

Comments
 (0)